@3dn-builder/builder 0.644.0 → 0.646.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
//# sourceMappingURL=chunk-
|
|
1
|
+
var i="tdn-open-builder";function d(e){e.dispatchEvent(new CustomEvent(i,{bubbles:!0,composed:!0}))}var c="tdn-status-change";var g="exportRequested",t="tdn-export-complete";function E(e,o){e.dispatchEvent(new CustomEvent(t,{bubbles:!1,composed:!1,detail:o}))}var u="tdn-product-cloned";var f="tdn-login-required";var n="tdn-login-cancelled";function b(e,o={}){e.dispatchEvent(new CustomEvent(n,{bubbles:!1,composed:!1,detail:o}))}var p="tdn-license-error";var m="tdn-design-saved";var F="tdn-images-saved";var A="tdn-images-save-failed";var D="tdn-design-save-failed";var T="tdn-license-token-required";var s="tdn-save-request";function C(e){e.dispatchEvent(new CustomEvent(s,{bubbles:!1,composed:!1}))}var _="tdn-close-requested";var r=[{name:"Grays",colors:["#FFFFFF","#F8F9FA","#E9ECEF","#DEE2E6","#CED4DA","#ADB5BD","#6C757D","#495057","#343A40","#212529","#1A1A1A","#000000"]},{name:"Reds",colors:["#FFE6E6","#FFCCCC","#FF9999","#FF6666","#FF3333","#FF0000","#E60000","#CC0000","#990000","#660000","#330000","#1A0000"]},{name:"Oranges",colors:["#FFF4E6","#FFE6CC","#FFCC99","#FFB366","#FF9933","#FF8000","#E66A00","#CC5200","#993D00","#662900","#331400","#1A0A00"]},{name:"Yellows",colors:["#FFFEF0","#FFFDE6","#FFFACC","#FFF799","#FFF266","#FFED33","#E6D100","#CCBA00","#998C00","#665D00","#332E00","#1A1700"]},{name:"Greens",colors:["#F0FFF0","#E6FFE6","#CCFFCC","#99FF99","#66FF66","#33FF33","#00E600","#00CC00","#009900","#006600","#003300","#001A00"]},{name:"Blues",colors:["#F0F8FF","#E6F3FF","#CCE7FF","#99CFFF","#66B7FF","#339FFF","#0080E6","#0066CC","#004D99","#003366","#001A33","#000D1A"]},{name:"Purples",colors:["#F8F0FF","#F0E6FF","#E0CCFF","#C199FF","#A266FF","#8333FF","#6600E6","#5200CC","#3D0099","#290066","#140033","#0A001A"]},{name:"Pinks",colors:["#FFE6F0","#FFCCE0","#FF99C1","#FF66A2","#FF3383","#FF0064","#E60057","#CC0051","#99003E","#66002A","#330015","#1A000A"]},{name:"Teals",colors:["#F0FFFF","#E0FFFF","#B2FFFF","#66FFFF","#1AFFFF","#00CCCC","#00B3B3","#009999","#006666","#003333","#001A1A","#000D0D"]},{name:"Cyans",colors:["#E0F7FA","#B2EBF2","#80DEEA","#4DD0E1","#26C6DA","#00BCD4","#00ACC1","#0097A7","#00796B","#004D40","#00251A","#00120D"]},{name:"Indigos",colors:["#E8EAF6","#C5CAE9","#9FA8DA","#7986CB","#5C6BC0","#3F51B5","#303F9F","#283593","#1A237E","#0D1442","#060A21","#030510"]},{name:"Violets",colors:["#F3E5F5","#E1BEE7","#CE93D8","#BA68C8","#AB47BC","#9C27B0","#8E24AA","#7B1FA2","#6A1B9A","#4A148C","#2E1065","#1A0D3D"]},{name:"Magentas",colors:["#FCE4EC","#F8BBD9","#F48FB1","#F06292","#EC407A","#E91E63","#D81B60","#C2185B","#AD1457","#880E4F","#4A0A2A","#2D0519"]},{name:"Limes",colors:["#F9FBE7","#F0F4C3","#E6EE9C","#DCE775","#D4E157","#CDDC39","#C0CA33","#AFB42B","#9E9D24","#827717","#4A4514","#2D2A0C"]},{name:"Ambers",colors:["#FFF8E1","#FFECB3","#FFE082","#FFD54F","#FFCA28","#FFC107","#FFB300","#FFA000","#FF8F00","#FF6F00","#994200","#5A2700"]},{name:"Deep Oranges",colors:["#FBE9E7","#FFCCBC","#FFAB91","#FF8A65","#FF7043","#FF5722","#F4511E","#E64A19","#D84315","#BF360C","#7A2408","#4A1605"]},{name:"Light Blues",colors:["#E1F5FE","#B3E5FC","#81D4FA","#4FC3F7","#29B6F6","#03A9F4","#039BE5","#0288D1","#0277BD","#01579B","#003A66","#00223D"]},{name:"Light Greens",colors:["#E8F5E8","#C8E6C9","#A5D6A7","#81C784","#66BB6A","#4CAF50","#43A047","#388E3C","#2E7D32","#1B5E20","#0F3A14","#09230C"]},{name:"Browns",colors:["#F5F5DC","#F0E68C","#D2B48C","#BC8F8F","#A0522D","#8B4513","#654321","#4A2C2A","#2F1B14","#1A0E0A","#0F0705","#090503"]},{name:"Grey-Blues",colors:["#ECEFF1","#CFD8DC","#B0BEC5","#90A4AE","#78909C","#607D8B","#546E7A","#455A64","#37474F","#263238","#1A2126","#0F1316"]}],I=r.flatMap(e=>e.colors);var x=1024*1024;var a=4096,h=600*1024;var l=["active","suspended","revoked"];var H=[...l,"expired"];var Y=1440*60*1e3;var q={full:{tier:"full",texture:{maxSide:a,maxArea:a*a},maxCaptureArea:33554432,maxOversample:2},mobile:{tier:"mobile",texture:{maxSide:2048,maxArea:2048*2048},maxCaptureArea:4194304,maxOversample:1},light:{tier:"light",texture:{maxSide:1536,maxArea:1536*1536},maxCaptureArea:2359296,maxOversample:1}};export{i as a,d as b,c,g as d,t as e,E as f,u as g,f as h,n as i,b as j,p as k,m as l,F as m,A as n,D as o,T as p,s as q,C as r,_ as s};
|
|
2
|
+
//# sourceMappingURL=chunk-FRDLXZMM.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../../libs/constants/src/lib/builder-webcomponent.ts", "../../../../libs/constants/src/lib/colors.ts", "../../../../libs/constants/src/lib/constants.ts", "../../../../libs/constants/src/lib/license.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Programmatic open for `<threed-builder>` (avoids synthetic `click()` / ghost clicks).\n * Dispatched on the custom element host with composed path so the inner app can subscribe.\n */\nexport const THREED_BUILDER_OPEN_EVENT = 'tdn-open-builder' as const;\n\nexport function dispatchOpenThreedBuilder(host: HTMLElement): void {\n host.dispatchEvent(\n new CustomEvent(THREED_BUILDER_OPEN_EVENT, {\n bubbles: true,\n composed: true,\n })\n );\n}\n\n/**\n * Builder lifecycle statuses emitted via `tdn-status-change` CustomEvent.\n */\nexport type BuilderStatus = 'idle' | 'opening' | 'loading' | 'ready' | 'error';\n\nexport const THREED_BUILDER_STATUS_EVENT = 'tdn-status-change' as const;\n\nexport interface BuilderStatusDetail {\n status: BuilderStatus;\n step?: string;\n message?: string;\n error?: string;\n}\n\nexport function dispatchBuilderStatus(\n host: HTMLElement,\n detail: BuilderStatusDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<BuilderStatusDetail>(THREED_BUILDER_STATUS_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * External export bridge.\n *\n * When `<threed-builder external-export=\"true\">` is set, the webcomponent\n * skips its built-in admin POST and instead emits `exportRequested` so the\n * embedding host can run the export through its own backend (e.g. the\n * store app proxy). The host then hands the resulting file URLs back via\n * `dispatchExportComplete`.\n *\n * NOT FOR A DIELINE PRODUCT (task 3.2, ruling P3-6). A dieline's print files\n * come from ONE pure exporter run over the design's own immutable snapshot\n * (Document 1 sections 12 and 13); a host backend cannot produce that file,\n * and a file it produces instead is a second drawing with nothing holding it\n * to what the customer approved on screen. The webcomponent therefore routes\n * a DIELINE export to the admin API whatever this attribute says, and emits\n * `exportRequested` for GLB products only. A host that needs its own pipeline\n * for dielines needs a different contract, not this one.\n */\nexport const THREED_BUILDER_EXPORT_REQUESTED_EVENT = 'exportRequested' as const;\n\n/**\n * What a GLB product's export asks the host for. A GLB product is printed by\n * the same exporter as a dieline (print-export Phase 2), so a request can name\n * any of its formats and modes \u2014 the host's pipeline has to honour them, not\n * treat the request as one fixed SVG.\n *\n * The builder's own gate runs first, exactly as for an export through 3DN: a\n * request is only emitted once the design is saved, every print area it\n * exports has a print size, and the 3DN preflight has no blocking error for\n * the chosen mode. A host therefore never receives a request for an unsized\n * area. The detail carries the format and mode the user chose, and\n * `printAreaNames` when they asked for one area by itself.\n */\nexport interface ExportRequestedDetail {\n productId: string;\n /** The file formats wanted: `'svg'` and `'pdf'`. */\n formats: string[];\n surfaces: string[];\n /**\n * The export mode the user chose, `'proof'` or `'production-artwork'`\n * (Document 1 section 12). A proof carries every guide and a PROOF banner;\n * production artwork is the printable content only. Neither can be made for\n * an area with no print size (`print-size-missing` blocks both), so neither\n * is requested for one. Absent from an older builder's request.\n */\n mode?: string;\n /**\n * The names of the print areas to export, as the model declares them. Set\n * when one area is asked for by itself (an area's Download); absent for the\n * whole design, which means every print area on the requested surfaces.\n */\n printAreaNames?: string[];\n}\n\nexport const THREED_BUILDER_EXPORT_COMPLETE_EVENT =\n 'tdn-export-complete' as const;\n\nexport interface ExportFile {\n url: string;\n format: string;\n surface: string;\n}\n\nexport interface ExportCompleteDetail {\n files: ExportFile[];\n}\n\nexport function dispatchExportComplete(\n host: HTMLElement,\n detail: ExportCompleteDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<ExportCompleteDetail>(\n THREED_BUILDER_EXPORT_COMPLETE_EVENT,\n {\n bubbles: false,\n composed: false,\n detail,\n }\n )\n );\n}\n\n/**\n * Fired after a save that created a new product: \"Save as copy\", or a host\n * shopper's Save of a product they don't own (their first save). The builder\n * has switched to editing the new product. The host should persist\n * `newProductId` so subsequent loads of the builder open it.\n */\nexport const THREED_BUILDER_PRODUCT_CLONED_EVENT =\n 'tdn-product-cloned' as const;\n\nexport interface ProductClonedDetail {\n originalProductId: string;\n newProductId: string;\n name: string;\n /**\n * The user asked for a copy (\"Save as copy\", \"Save as my copy\", \"keep both\"\n * after a conflict) and expects to keep the original too. `false` when Save\n * had to fork because the user doesn't own the product: the copy takes the\n * original's place.\n */\n asCopy: boolean;\n}\n\nexport function dispatchProductCloned(\n host: HTMLElement,\n detail: ProductClonedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<ProductClonedDetail>(THREED_BUILDER_PRODUCT_CLONED_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * Fired when a protected action needs a logged-in user (save / screenshot /\n * export / upload / share), or \u2014 with `action: 'session'` \u2014 when the builder's\n * session could not be recovered (refresh failed). The host shows its own\n * login if its user is not logged in, has its BACKEND mint a token pair from\n * its own session, and sets `bearer-token` / `refresh-token` on the element;\n * the builder then resumes the pending action. If the user dismisses the login\n * or the mint fails, the host dispatches `tdn-login-cancelled` instead.\n * One event per login round-trip: further gated clicks while a login is\n * pending do not fire it again.\n */\nexport const THREED_BUILDER_LOGIN_REQUIRED_EVENT =\n 'tdn-login-required' as const;\n\nexport type LoginRequiredAction =\n | 'save'\n | 'screenshot'\n | 'export'\n | 'upload'\n | 'share'\n | 'session';\n\nexport interface LoginRequiredDetail {\n action: LoginRequiredAction;\n}\n\nexport function dispatchLoginRequired(\n host: HTMLElement,\n detail: LoginRequiredDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<LoginRequiredDetail>(THREED_BUILDER_LOGIN_REQUIRED_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * Host -> builder: the login the builder asked for (`tdn-login-required`) will\n * not happen \u2014 the user closed the host's login, or the host's mint failed.\n * The builder drops the pending action, so a stale Save never runs after a\n * later, unrelated login. A pending Save reports `tdn-design-save-failed`\n * with reason `login-cancelled`.\n */\nexport const THREED_BUILDER_LOGIN_CANCELLED_EVENT =\n 'tdn-login-cancelled' as const;\n\nexport interface LoginCancelledDetail {\n action?: LoginRequiredAction;\n}\n\nexport function dispatchLoginCancelled(\n host: HTMLElement,\n detail: LoginCancelledDetail = {}\n): void {\n host.dispatchEvent(\n new CustomEvent<LoginCancelledDetail>(\n THREED_BUILDER_LOGIN_CANCELLED_EVENT,\n { bubbles: false, composed: false, detail }\n )\n );\n}\n\n/**\n * Builder -> host: the builder cannot work on this page because of the\n * license, not because of the user. Fired once.\n * - `ORIGIN_NOT_ALLOWED`: this page's hostname is not the license's domain or\n * an alias. Do not answer it with a new token: a fresh mint for the same\n * page is refused the same way. Fix the license (add the hostname as an\n * alias) instead.\n * - `LICENSE_TOKEN_INVALID`: the `license-token` was rejected, the builder\n * asked for a fresh one (`tdn-license-token-required`), and the fresh one\n * was rejected too (or never came) \u2014 the license is suspended, revoked or\n * expired, or the host is minting against the wrong installation.\n */\nexport const THREED_BUILDER_LICENSE_ERROR_EVENT = 'tdn-license-error' as const;\n\nexport type LicenseErrorCode = 'ORIGIN_NOT_ALLOWED' | 'LICENSE_TOKEN_INVALID';\n\nexport interface LicenseErrorDetail {\n code: LicenseErrorCode;\n}\n\nexport function dispatchLicenseError(\n host: HTMLElement,\n detail: LicenseErrorDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<LicenseErrorDetail>(THREED_BUILDER_LICENSE_ERROR_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * The design was saved to 3dn. Fired as soon as the save is stored (before\n * the product's images are refreshed; `tdn-images-saved` or\n * `tdn-images-save-failed` follows), and after \"Save as copy\" too (then\n * followed by `tdn-product-cloned`). This \u2014 not `tdn-status-change` `ready`,\n * which also fires after every load step \u2014 is the signal a host should\n * continue on (add to cart, show a confirmation).\n */\nexport const THREED_BUILDER_DESIGN_SAVED_EVENT = 'tdn-design-saved' as const;\n\nexport interface DesignSavedDetail {\n productId: string;\n /** The stored serialized version after this save. */\n version: number;\n}\n\nexport function dispatchDesignSaved(\n host: HTMLElement,\n detail: DesignSavedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<DesignSavedDetail>(THREED_BUILDER_DESIGN_SAVED_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * The product's gallery images (its thumbnail and camera-angle screenshots)\n * now show the design at `version`, and reading the product returns them.\n * Fired after every `tdn-design-saved`, once the screenshots are uploaded and\n * stored on the product \u2014 before `tdn-product-cloned` when the save made a\n * copy. Also fired by the builder's Close when autosave had stored the design\n * without screenshots: the images are refreshed before `tdn-close-requested`.\n * This \u2014 not `tdn-design-saved` or `tdn-status-change` \u2014 is when a host may\n * read the product's images.\n */\nexport const THREED_BUILDER_IMAGES_SAVED_EVENT = 'tdn-images-saved' as const;\n\nexport interface ImagesSavedDetail {\n productId: string;\n /** The stored serialized version the images show. */\n version: number;\n}\n\nexport function dispatchImagesSaved(\n host: HTMLElement,\n detail: ImagesSavedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<ImagesSavedDetail>(THREED_BUILDER_IMAGES_SAVED_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * The design was saved but its images could not be refreshed (capture or\n * upload failed); the product keeps its previous images. Fired in place of\n * `tdn-images-saved`. The builder has already warned the user; the design\n * itself is safe.\n */\nexport const THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT =\n 'tdn-images-save-failed' as const;\n\nexport function dispatchImagesSaveFailed(\n host: HTMLElement,\n detail: ImagesSavedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<ImagesSavedDetail>(\n THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT,\n { bubbles: true, composed: true, detail }\n )\n );\n}\n\n/**\n * Why a save did not happen:\n * - `conflict`: someone saved first; the builder shows its conflict dialog.\n * - `forbidden`: this user may not write to the product; the builder offers\n * request-access or save-as-copy.\n * - `unauthorized`: login recovery ran and the save was still refused.\n * - `login-cancelled`: the host sent `tdn-login-cancelled` for a pending save.\n * - `installation-inactive`: the host installation is not active (setup\n * problem on the host side, not a user error).\n * - `license`: refused with `ORIGIN_NOT_ALLOWED` \u2014 this page isn't licensed,\n * not a permissions or session problem; see `tdn-license-error`.\n * - `outdated`: the builder on the page is older than the server and can no\n * longer save; the builder offers a page reload, which carries the unsaved\n * design across where it can.\n * - `error`: network or server failure.\n */\nexport type DesignSaveFailureReason =\n | 'conflict'\n | 'forbidden'\n | 'unauthorized'\n | 'login-cancelled'\n | 'installation-inactive'\n | 'license'\n | 'outdated'\n | 'error';\n\n/**\n * A save the user asked for did not happen, after any retry and login\n * recovery ran out. Never fired while a login the builder asked for is still\n * pending. The builder has already told the user; the host may add its own\n * handling and, when `retryable`, let the user try again.\n */\nexport const THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT =\n 'tdn-design-save-failed' as const;\n\nexport interface DesignSaveFailedDetail {\n productId?: string;\n reason: DesignSaveFailureReason;\n retryable: boolean;\n}\n\nexport function dispatchDesignSaveFailed(\n host: HTMLElement,\n detail: DesignSaveFailedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<DesignSaveFailedDetail>(\n THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT,\n { bubbles: true, composed: true, detail }\n )\n );\n}\n\n/**\n * Builder -> host: the `license-token` (the no-user token from the host\n * backend's `POST /plugin/license-token`) was rejected \u2014 it expired, or the\n * installation's key was rotated. The host fetches a fresh license token from\n * its backend and sets `license-token` again; the builder holds the failed\n * request and retries it once with the new token. This is NEVER a login\n * prompt: answer it without involving the user. A second rejection ends in\n * `tdn-license-error` `LICENSE_TOKEN_INVALID`.\n */\nexport const THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT =\n 'tdn-license-token-required' as const;\n\nexport function dispatchLicenseTokenRequired(host: HTMLElement): void {\n host.dispatchEvent(\n new CustomEvent(THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT, {\n bubbles: true,\n composed: true,\n })\n );\n}\n\n/**\n * Host -> builder: save the design now, exactly as if the user pressed Save\n * (login gate, save-as-copy for a product the user does not own, and the\n * usual `tdn-design-saved` / `tdn-design-save-failed` outcome). A host sends\n * it before Add to cart when the design has not been saved yet, then waits\n * for one of those two events.\n */\nexport const THREED_BUILDER_SAVE_REQUEST_EVENT = 'tdn-save-request' as const;\n\nexport function dispatchSaveRequest(host: HTMLElement): void {\n host.dispatchEvent(\n new CustomEvent(THREED_BUILDER_SAVE_REQUEST_EVENT, {\n bubbles: false,\n composed: false,\n })\n );\n}\n\n/**\n * Builder -> host: the user pressed the builder's close button. The builder\n * does nothing else \u2014 it stays open and keeps its state. The host decides\n * what closing means on its page (hide an overlay, navigate back, ask first\n * when `hasUnsavedChanges`) and removes or hides the element itself.\n */\nexport const THREED_BUILDER_CLOSE_REQUESTED_EVENT =\n 'tdn-close-requested' as const;\n\nexport interface CloseRequestedDetail {\n productId?: string;\n /**\n * What is on screen differs from what was last saved (manual save or\n * autosave), in 2D or 3D: elements, the dieline/board, finish styles, or a\n * switched design. Opening and closing the box (fold) does not count, and\n * an edit undone by hand is no change.\n */\n hasUnsavedChanges: boolean;\n}\n\nexport function dispatchCloseRequested(\n host: HTMLElement,\n detail: CloseRequestedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<CloseRequestedDetail>(\n THREED_BUILDER_CLOSE_REQUESTED_EVENT,\n { bubbles: true, composed: true, detail }\n )\n );\n}\n\n/**\n * Fired when the user picks \"AI Video\" inside the builder. The embedding host\n * navigates to its AI-video editor for `productId`, which then drives this same\n * product (loaded headlessly) to render the template's reference views.\n */\nexport const THREED_BUILDER_OPEN_AI_VIDEO_EVENT = 'tdn-open-ai-video' as const;\n\nexport interface OpenAiVideoDetail {\n productId: string;\n}\n\nexport function dispatchOpenAiVideo(\n host: HTMLElement,\n detail: OpenAiVideoDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<OpenAiVideoDetail>(THREED_BUILDER_OPEN_AI_VIDEO_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n", "// Color groups with 12 variants each\nexport const colorGroups = [\n {\n name: 'Grays',\n colors: [\n '#FFFFFF',\n '#F8F9FA',\n '#E9ECEF',\n '#DEE2E6',\n '#CED4DA',\n '#ADB5BD',\n '#6C757D',\n '#495057',\n '#343A40',\n '#212529',\n '#1A1A1A',\n '#000000',\n ],\n },\n {\n name: 'Reds',\n colors: [\n '#FFE6E6',\n '#FFCCCC',\n '#FF9999',\n '#FF6666',\n '#FF3333',\n '#FF0000',\n '#E60000',\n '#CC0000',\n '#990000',\n '#660000',\n '#330000',\n '#1A0000',\n ],\n },\n {\n name: 'Oranges',\n colors: [\n '#FFF4E6',\n '#FFE6CC',\n '#FFCC99',\n '#FFB366',\n '#FF9933',\n '#FF8000',\n '#E66A00',\n '#CC5200',\n '#993D00',\n '#662900',\n '#331400',\n '#1A0A00',\n ],\n },\n {\n name: 'Yellows',\n colors: [\n '#FFFEF0',\n '#FFFDE6',\n '#FFFACC',\n '#FFF799',\n '#FFF266',\n '#FFED33',\n '#E6D100',\n '#CCBA00',\n '#998C00',\n '#665D00',\n '#332E00',\n '#1A1700',\n ],\n },\n {\n name: 'Greens',\n colors: [\n '#F0FFF0',\n '#E6FFE6',\n '#CCFFCC',\n '#99FF99',\n '#66FF66',\n '#33FF33',\n '#00E600',\n '#00CC00',\n '#009900',\n '#006600',\n '#003300',\n '#001A00',\n ],\n },\n {\n name: 'Blues',\n colors: [\n '#F0F8FF',\n '#E6F3FF',\n '#CCE7FF',\n '#99CFFF',\n '#66B7FF',\n '#339FFF',\n '#0080E6',\n '#0066CC',\n '#004D99',\n '#003366',\n '#001A33',\n '#000D1A',\n ],\n },\n {\n name: 'Purples',\n colors: [\n '#F8F0FF',\n '#F0E6FF',\n '#E0CCFF',\n '#C199FF',\n '#A266FF',\n '#8333FF',\n '#6600E6',\n '#5200CC',\n '#3D0099',\n '#290066',\n '#140033',\n '#0A001A',\n ],\n },\n {\n name: 'Pinks',\n colors: [\n '#FFE6F0',\n '#FFCCE0',\n '#FF99C1',\n '#FF66A2',\n '#FF3383',\n '#FF0064',\n '#E60057',\n '#CC0051',\n '#99003E',\n '#66002A',\n '#330015',\n '#1A000A',\n ],\n },\n {\n name: 'Teals',\n colors: [\n '#F0FFFF',\n '#E0FFFF',\n '#B2FFFF',\n '#66FFFF',\n '#1AFFFF',\n '#00CCCC',\n '#00B3B3',\n '#009999',\n '#006666',\n '#003333',\n '#001A1A',\n '#000D0D',\n ],\n },\n {\n name: 'Cyans',\n colors: [\n '#E0F7FA',\n '#B2EBF2',\n '#80DEEA',\n '#4DD0E1',\n '#26C6DA',\n '#00BCD4',\n '#00ACC1',\n '#0097A7',\n '#00796B',\n '#004D40',\n '#00251A',\n '#00120D',\n ],\n },\n {\n name: 'Indigos',\n colors: [\n '#E8EAF6',\n '#C5CAE9',\n '#9FA8DA',\n '#7986CB',\n '#5C6BC0',\n '#3F51B5',\n '#303F9F',\n '#283593',\n '#1A237E',\n '#0D1442',\n '#060A21',\n '#030510',\n ],\n },\n {\n name: 'Violets',\n colors: [\n '#F3E5F5',\n '#E1BEE7',\n '#CE93D8',\n '#BA68C8',\n '#AB47BC',\n '#9C27B0',\n '#8E24AA',\n '#7B1FA2',\n '#6A1B9A',\n '#4A148C',\n '#2E1065',\n '#1A0D3D',\n ],\n },\n {\n name: 'Magentas',\n colors: [\n '#FCE4EC',\n '#F8BBD9',\n '#F48FB1',\n '#F06292',\n '#EC407A',\n '#E91E63',\n '#D81B60',\n '#C2185B',\n '#AD1457',\n '#880E4F',\n '#4A0A2A',\n '#2D0519',\n ],\n },\n {\n name: 'Limes',\n colors: [\n '#F9FBE7',\n '#F0F4C3',\n '#E6EE9C',\n '#DCE775',\n '#D4E157',\n '#CDDC39',\n '#C0CA33',\n '#AFB42B',\n '#9E9D24',\n '#827717',\n '#4A4514',\n '#2D2A0C',\n ],\n },\n {\n name: 'Ambers',\n colors: [\n '#FFF8E1',\n '#FFECB3',\n '#FFE082',\n '#FFD54F',\n '#FFCA28',\n '#FFC107',\n '#FFB300',\n '#FFA000',\n '#FF8F00',\n '#FF6F00',\n '#994200',\n '#5A2700',\n ],\n },\n {\n name: 'Deep Oranges',\n colors: [\n '#FBE9E7',\n '#FFCCBC',\n '#FFAB91',\n '#FF8A65',\n '#FF7043',\n '#FF5722',\n '#F4511E',\n '#E64A19',\n '#D84315',\n '#BF360C',\n '#7A2408',\n '#4A1605',\n ],\n },\n {\n name: 'Light Blues',\n colors: [\n '#E1F5FE',\n '#B3E5FC',\n '#81D4FA',\n '#4FC3F7',\n '#29B6F6',\n '#03A9F4',\n '#039BE5',\n '#0288D1',\n '#0277BD',\n '#01579B',\n '#003A66',\n '#00223D',\n ],\n },\n {\n name: 'Light Greens',\n colors: [\n '#E8F5E8',\n '#C8E6C9',\n '#A5D6A7',\n '#81C784',\n '#66BB6A',\n '#4CAF50',\n '#43A047',\n '#388E3C',\n '#2E7D32',\n '#1B5E20',\n '#0F3A14',\n '#09230C',\n ],\n },\n {\n name: 'Browns',\n colors: [\n '#F5F5DC',\n '#F0E68C',\n '#D2B48C',\n '#BC8F8F',\n '#A0522D',\n '#8B4513',\n '#654321',\n '#4A2C2A',\n '#2F1B14',\n '#1A0E0A',\n '#0F0705',\n '#090503',\n ],\n },\n {\n name: 'Grey-Blues',\n colors: [\n '#ECEFF1',\n '#CFD8DC',\n '#B0BEC5',\n '#90A4AE',\n '#78909C',\n '#607D8B',\n '#546E7A',\n '#455A64',\n '#37474F',\n '#263238',\n '#1A2126',\n '#0F1316',\n ],\n },\n];\n\n// Flattened array for backward compatibility\nexport const colors: string[] = colorGroups.flatMap((group) => group.colors);\n", "export const USER_ROLES = ['ADMIN', 'DESIGNER', 'USER', 'CUSTOMER'] as const;\nexport const AUTH_PROVIDERS = ['REGULAR', 'GOOGLE'] as const;\nexport const TIER_TYPES = ['PAID', 'TRAIL', 'FREE', 'EXPIRED'] as const;\nexport const GLB_MATERIALS = ['PBR', 'STANDARD', 'UNKNOWN'] as const;\nexport const VISIBILITY_STATUS = [\n 'private',\n 'public',\n 'store',\n 'unlisted',\n] as const;\n\nexport const HDRI_CATEGORIES = [\n 'outdoor',\n 'indoor',\n 'studio',\n 'sunset',\n 'night',\n 'abstract',\n] as const;\nexport type HdriCategory = (typeof HDRI_CATEGORIES)[number];\n\nexport const HDRI_CUBE_SIZES = [64, 128, 256, 512] as const;\nexport type HdriCubeSize = (typeof HDRI_CUBE_SIZES)[number];\n\n/** Maximum byte length of a published HDRI's .env file. Enforced by API and UI. */\nexport const HDRI_PUBLISH_MAX_ENV_BYTES = 1024 * 1024; // 1 MB\n\n/** Default pixel size of an imported GLB's dynamic texture. Admins can set any\n * size from 1 to MAX_DYNAMIC_TEXTURE_SIZE per axis. */\nexport const DEFAULT_DYNAMIC_TEXTURE_RESOLUTION = {\n width: 2048,\n height: 2048,\n} as const;\n\n/** Upper bound on dynamic texture dimensions. Beyond this, quality gains are\n * negligible on most GPUs but memory cost scales quadratically. Enforced by\n * API and UI; the builder clamps to it too. */\nexport const MAX_DYNAMIC_TEXTURE_SIZE = 4096;\n\n/** Above this, a design layer's combined path data may slow canvas rendering. */\nexport const MAX_LAYER_PATH_BYTES = 600 * 1024; // 600 KB\n\nexport const MAX_FILE_SIZES = {\n MODEL: 10,\n IMAGE: 20,\n THUMBNAIL: 5,\n DEBUG: 0.5,\n ENV: 10,\n FONT: 20,\n HDRI_SOURCE: 25,\n} as const;\n\n/**\n * Default max upload size (in MB) applied to upload endpoints when no\n * explicit override is provided. Shared between the API middleware and the\n * frontend so that client-side validation matches the server limit.\n */\nexport const DEFAULT_MAX_UPLOAD_SIZE_MB = 10;\n\n/**\n * Centralized upload file-type definitions.\n *\n * Each entry describes a named group of files that the API accepts at a\n * single upload endpoint. The API's upload middleware uses `mimeTypes` /\n * `extensions` for validation, and the Angular frontend uses `accept`\n * directly on `<input type=\"file\">` elements so both layers stay in sync.\n *\n * - `IMAGE` \u2013 any raster or vector image (generic image uploads).\n * - `RASTER_IMAGE` \u2013 raster only (design images the AI separates into layers).\n * - `SVG` \u2013 vector only (design layer uploads, pattern SVG uploads).\n * - `MODEL` \u2013 3D model bundles (glb, gltf + referenced textures).\n * - `FONT` \u2013 font files.\n * - `ENV` \u2013 environment maps for 3D scenes.\n */\nexport type UploadFileTypeKey =\n | 'IMAGE'\n | 'RASTER_IMAGE'\n | 'SVG'\n | 'MODEL'\n | 'FONT'\n | 'ENV';\n\nexport interface UploadFileTypeConfig {\n /** Human readable label used in error messages and UI copy. */\n readonly label: string;\n /** Allowed MIME types (lowercase). */\n readonly mimeTypes: readonly string[];\n /** Allowed file extensions including the leading dot (lowercase). */\n readonly extensions: readonly string[];\n /** Value for the HTML `<input accept>` attribute. */\n readonly accept: string;\n}\n\nexport const UPLOAD_FILE_TYPES: Readonly<\n Record<UploadFileTypeKey, UploadFileTypeConfig>\n> = {\n IMAGE: {\n label: 'PNG, JPG, JPEG, WEBP or SVG',\n mimeTypes: [\n 'image/png',\n 'image/jpeg',\n 'image/jpg',\n 'image/webp',\n 'image/svg+xml',\n ],\n extensions: ['.png', '.jpg', '.jpeg', '.webp', '.svg'],\n accept: 'image/png,image/jpeg,image/jpg,image/webp,image/svg+xml',\n },\n RASTER_IMAGE: {\n label: 'PNG, JPG, JPEG or WEBP',\n mimeTypes: ['image/png', 'image/jpeg', 'image/jpg', 'image/webp'],\n extensions: ['.png', '.jpg', '.jpeg', '.webp'],\n accept: 'image/png,image/jpeg,image/jpg,image/webp',\n },\n SVG: {\n label: 'SVG',\n mimeTypes: ['image/svg+xml'],\n extensions: ['.svg'],\n accept: 'image/svg+xml,.svg',\n },\n MODEL: {\n label: 'GLB, GLTF, BIN, PNG, JPG, JPEG or WEBP',\n mimeTypes: [\n 'model/gltf-binary',\n 'model/gltf+json',\n 'application/octet-stream',\n 'image/png',\n 'image/jpeg',\n 'image/jpg',\n 'image/webp',\n ],\n extensions: ['.glb', '.gltf', '.bin', '.png', '.jpg', '.jpeg', '.webp'],\n accept: '.glb,.gltf,.bin,.png,.jpg,.jpeg,.webp',\n },\n FONT: {\n label: 'OTF, TTF or TXT',\n mimeTypes: [\n 'font/ttf',\n 'font/otf',\n 'application/octet-stream',\n 'text/plain',\n ],\n extensions: ['.ttf', '.otf', '.txt'],\n accept: '.ttf,.otf,.txt',\n },\n ENV: {\n label: '.env or .hdr',\n mimeTypes: ['application/octet-stream'],\n extensions: ['.env', '.hdr'],\n accept: '.env,.hdr',\n },\n};\n\n/**\n * Stable string codes the upload middleware emits on 4xx responses. The\n * frontend matches against these to render specific user-facing messages\n * (oversize, wrong type, etc.) without relying on the backend message text.\n */\nexport const UPLOAD_ERROR_CODES = {\n FILE_TOO_LARGE: 'FILE_TOO_LARGE',\n INVALID_FILE_TYPE: 'INVALID_FILE_TYPE',\n FILE_REQUIRED: 'FILE_REQUIRED',\n TOO_MANY_FILES: 'TOO_MANY_FILES',\n UNEXPECTED_FILE_FIELD: 'UNEXPECTED_FILE_FIELD',\n UPLOAD_FAILED: 'UPLOAD_FAILED',\n} as const;\n\nexport type UploadErrorCode =\n (typeof UPLOAD_ERROR_CODES)[keyof typeof UPLOAD_ERROR_CODES];\n\nexport interface UploadErrorDetails {\n fileType?: UploadFileTypeKey;\n maxSizeMB?: number;\n allowedTypes?: readonly string[];\n filename?: string;\n mimeType?: string;\n field?: string;\n [key: string]: unknown;\n}\n\nexport const ROLES = {\n ADMIN: 'ADMIN',\n DESIGNER: 'DESIGNER',\n USER: 'USER',\n} as const;\n\nexport const ACTIVITY_EVENT_TYPES = [\n 'product.created',\n 'product.updated',\n 'product.published',\n 'product.deleted',\n 'product.design_added',\n 'product.design_removed',\n 'product.serialized_saved',\n 'product.serialized_restored',\n 'product.dieline_resnapshotted',\n 'product.screenshot_captured',\n 'product.shared',\n 'product.unshared',\n 'product.access_requested',\n 'product.access_approved',\n 'product.access_denied',\n 'design.created',\n 'design.updated',\n 'design.published',\n 'design.deleted',\n 'design.layers_updated',\n 'design.thumbnail_generated',\n 'glb.uploaded',\n 'glb.updated',\n 'glb.deleted',\n 'user.login',\n 'user.logout',\n // Installation key lifecycle. Rotation and revocation can lock a host's\n // users out, so they need a durable trail, not just a log line.\n 'installation.key_issued',\n 'installation.key_rotated',\n 'installation.disabled',\n 'installation.enabled',\n 'installation.revoked',\n // License terms (Phase 2).\n 'installation.terms_changed',\n 'installation.ips_changed',\n 'installation.seat_limit_reached',\n] as const;\n\nexport const ACTIVITY_ENTITY_TYPES = [\n 'product',\n 'design',\n 'glb',\n 'user',\n 'print_area',\n 'installation',\n] as const;\n\n// Global requests: the entities that go global only when a reviewer makes a\n// request global. HDRIs, AI themes and fonts are made global by any admin.\nexport const GLOBAL_REQUEST_ENTITY_TYPES = [\n 'product',\n 'design',\n 'pattern',\n 'library_item',\n 'glb',\n] as const;\n/** What people call each type that goes global by request. */\nexport const GLOBAL_REQUEST_ENTITY_LABELS = {\n product: 'Product',\n design: 'Design',\n pattern: 'Pattern',\n library_item: 'Library item',\n glb: '3D model',\n} as const satisfies Record<\n (typeof GLOBAL_REQUEST_ENTITY_TYPES)[number],\n string\n>;\n\n// Installation & Access Grant constants\nexport const INSTALLATION_PLATFORMS = [\n 'wordpress',\n 'shopify',\n 'web-app',\n] as const;\nexport const INSTALLATION_STATUS = ['active', 'suspended', 'revoked'] as const;\n", "import { INSTALLATION_STATUS } from './constants';\n\n/**\n * Whether a license works is decided here and only here. `expired` is never\n * stored: it is computed from `expiresAt` at the moment of the check, so an\n * expiry takes effect without any job.\n */\nexport const LICENSE_STATES = [...INSTALLATION_STATUS, 'expired'] as const;\nexport type LicenseState = (typeof LICENSE_STATES)[number];\n\n/** Owners and admins are warned this many days before a license expires. */\nexport const LICENSE_EXPIRY_WARNING_DAYS = 14;\n/** Owners and admins are warned once this share of the seat cap is used. */\nexport const LICENSE_SEAT_WARNING_RATIO = 0.9;\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nexport interface LicenseTerms {\n status: (typeof INSTALLATION_STATUS)[number];\n expiresAt?: Date | string | null;\n}\n\n/** Status wins over expiry: a revoked license reports `revoked`, not `expired`. */\nexport function licenseState(\n license: LicenseTerms,\n now: Date = new Date()\n): LicenseState {\n if (license.status !== 'active') return license.status;\n if (\n license.expiresAt != null &&\n new Date(license.expiresAt).getTime() <= now.getTime()\n ) {\n return 'expired';\n }\n return 'active';\n}\n\nexport type SeatLevel = 'unlimited' | 'ok' | 'warning' | 'full';\n\nexport function seatLevel(\n endUserCount: number,\n seatLimit: number | null | undefined\n): SeatLevel {\n if (seatLimit == null) return 'unlimited';\n if (endUserCount >= seatLimit) return 'full';\n if (endUserCount >= seatLimit * LICENSE_SEAT_WARNING_RATIO) return 'warning';\n return 'ok';\n}\n\nexport type ExpiryLevel = 'none' | 'ok' | 'soon' | 'expired';\n\nexport function expiryLevel(\n expiresAt: Date | string | null | undefined,\n now: Date = new Date()\n): ExpiryLevel {\n if (expiresAt == null) return 'none';\n const remaining = new Date(expiresAt).getTime() - now.getTime();\n if (remaining <= 0) return 'expired';\n if (remaining <= LICENSE_EXPIRY_WARNING_DAYS * DAY_MS) return 'soon';\n return 'ok';\n}\n\n/**\n * The 401 error code admin-api answers when a LICENSE token (the no-user\n * bearer a host sets as `license-token`) is rejected \u2014 expired, its license\n * inactive, or its key rotated. The builder answers it by asking the host for\n * a fresh license token (`tdn-license-token-required`), never with a login\n * prompt; a route's own \"needs a user\" 401 keeps the generic code.\n */\nexport const LICENSE_TOKEN_INVALID_CODE = 'LICENSE_TOKEN_INVALID';\n"],
|
|
5
|
-
"mappings": "AAIO,IAAMA,EAA4B,mBAElC,SAASC,EAA0BC,EAAyB,CACjEA,EAAK,cACH,IAAI,YAAYF,EAA2B,CACzC,QAAS,GACT,SAAU,EACZ,CAAC,CACH,CACF,CAOO,IAAMG,EAA8B,oBAwCpC,IAAMC,EAAwC,kBAoCxCC,EACX,sBAYK,SAASC,EACdC,EACAC,EACM,CACND,EAAK,cACH,IAAI,YACFF,EACA,CACE,QAAS,GACT,SAAU,GACV,OAAAG,CACF,CACF,CACF,CACF,CAQO,IAAMC,EACX,qBAuCK,IAAMC,EACX,qBAkCK,IAAMC,EACX,sBAMK,SAASC,EACdC,EACAC,EAA+B,CAAC,EAC1B,CACND,EAAK,cACH,IAAI,YACFF,EACA,CAAE,QAAS,GAAO,SAAU,GAAO,OAAAG,CAAO,CAC5C,CACF,CACF,CAcO,IAAMC,EAAqC,oBA6B3C,IAAMC,EAAoC,mBA+B1C,IAAMC,EAAoC,mBA2B1C,IAAMC,EACX,yBA8CK,IAAMC,EACX,yBA6BK,IAAMC,EACX,6BAkBK,IAAMC,EAAoC,mBAE1C,SAASC,EAAoBC,EAAyB,CAC3DA,EAAK,cACH,IAAI,YAAYF,EAAmC,CACjD,QAAS,GACT,SAAU,EACZ,CAAC,CACH,CACF,CAQO,IAAMG,EACX,sBCrbK,IAAMC,EAAc,CACzB,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,OACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,SACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,WACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,SACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,eACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,cACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,eACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,SACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,aACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,CACF,EAGaC,EAAmBD,EAAY,QAASE,GAAUA,EAAM,MAAM,EChUpE,IAAMC,EAA6B,KAAO,
|
|
6
|
-
"names": ["THREED_BUILDER_OPEN_EVENT", "dispatchOpenThreedBuilder", "host", "THREED_BUILDER_STATUS_EVENT", "THREED_BUILDER_EXPORT_REQUESTED_EVENT", "THREED_BUILDER_EXPORT_COMPLETE_EVENT", "dispatchExportComplete", "host", "detail", "THREED_BUILDER_PRODUCT_CLONED_EVENT", "THREED_BUILDER_LOGIN_REQUIRED_EVENT", "THREED_BUILDER_LOGIN_CANCELLED_EVENT", "dispatchLoginCancelled", "host", "detail", "THREED_BUILDER_LICENSE_ERROR_EVENT", "THREED_BUILDER_DESIGN_SAVED_EVENT", "THREED_BUILDER_IMAGES_SAVED_EVENT", "THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT", "THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT", "THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT", "THREED_BUILDER_SAVE_REQUEST_EVENT", "dispatchSaveRequest", "host", "THREED_BUILDER_CLOSE_REQUESTED_EVENT", "colorGroups", "colors", "group", "HDRI_PUBLISH_MAX_ENV_BYTES", "MAX_LAYER_PATH_BYTES", "INSTALLATION_STATUS", "LICENSE_STATES", "INSTALLATION_STATUS", "DAY_MS"]
|
|
3
|
+
"sources": ["../../../../libs/constants/src/lib/builder-webcomponent.ts", "../../../../libs/constants/src/lib/colors.ts", "../../../../libs/constants/src/lib/constants.ts", "../../../../libs/constants/src/lib/license.ts", "../../../../libs/constants/src/lib/texture-budget.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Programmatic open for `<threed-builder>` (avoids synthetic `click()` / ghost clicks).\n * Dispatched on the custom element host with composed path so the inner app can subscribe.\n */\nexport const THREED_BUILDER_OPEN_EVENT = 'tdn-open-builder' as const;\n\nexport function dispatchOpenThreedBuilder(host: HTMLElement): void {\n host.dispatchEvent(\n new CustomEvent(THREED_BUILDER_OPEN_EVENT, {\n bubbles: true,\n composed: true,\n })\n );\n}\n\n/**\n * Builder lifecycle statuses emitted via `tdn-status-change` CustomEvent.\n */\nexport type BuilderStatus = 'idle' | 'opening' | 'loading' | 'ready' | 'error';\n\nexport const THREED_BUILDER_STATUS_EVENT = 'tdn-status-change' as const;\n\nexport interface BuilderStatusDetail {\n status: BuilderStatus;\n step?: string;\n message?: string;\n error?: string;\n}\n\nexport function dispatchBuilderStatus(\n host: HTMLElement,\n detail: BuilderStatusDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<BuilderStatusDetail>(THREED_BUILDER_STATUS_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * External export bridge.\n *\n * When `<threed-builder external-export=\"true\">` is set, the webcomponent\n * skips its built-in admin POST and instead emits `exportRequested` so the\n * embedding host can run the export through its own backend (e.g. the\n * store app proxy). The host then hands the resulting file URLs back via\n * `dispatchExportComplete`.\n *\n * NOT FOR A DIELINE PRODUCT (task 3.2, ruling P3-6). A dieline's print files\n * come from ONE pure exporter run over the design's own immutable snapshot\n * (Document 1 sections 12 and 13); a host backend cannot produce that file,\n * and a file it produces instead is a second drawing with nothing holding it\n * to what the customer approved on screen. The webcomponent therefore routes\n * a DIELINE export to the admin API whatever this attribute says, and emits\n * `exportRequested` for GLB products only. A host that needs its own pipeline\n * for dielines needs a different contract, not this one.\n */\nexport const THREED_BUILDER_EXPORT_REQUESTED_EVENT = 'exportRequested' as const;\n\n/**\n * What a GLB product's export asks the host for. A GLB product is printed by\n * the same exporter as a dieline (print-export Phase 2), so a request can name\n * any of its formats and modes \u2014 the host's pipeline has to honour them, not\n * treat the request as one fixed SVG.\n *\n * The builder's own gate runs first, exactly as for an export through 3DN: a\n * request is only emitted once the design is saved, every print area it\n * exports has a print size, and the 3DN preflight has no blocking error for\n * the chosen mode. A host therefore never receives a request for an unsized\n * area. The detail carries the format and mode the user chose, and\n * `printAreaNames` when they asked for one area by itself.\n */\nexport interface ExportRequestedDetail {\n productId: string;\n /** The file formats wanted: `'svg'` and `'pdf'`. */\n formats: string[];\n surfaces: string[];\n /**\n * The export mode the user chose, `'proof'` or `'production-artwork'`\n * (Document 1 section 12). A proof carries every guide and a PROOF banner;\n * production artwork is the printable content only. Neither can be made for\n * an area with no print size (`print-size-missing` blocks both), so neither\n * is requested for one. Absent from an older builder's request.\n */\n mode?: string;\n /**\n * The names of the print areas to export, as the model declares them. Set\n * when one area is asked for by itself (an area's Download); absent for the\n * whole design, which means every print area on the requested surfaces.\n */\n printAreaNames?: string[];\n}\n\nexport const THREED_BUILDER_EXPORT_COMPLETE_EVENT =\n 'tdn-export-complete' as const;\n\nexport interface ExportFile {\n url: string;\n format: string;\n surface: string;\n}\n\nexport interface ExportCompleteDetail {\n files: ExportFile[];\n}\n\nexport function dispatchExportComplete(\n host: HTMLElement,\n detail: ExportCompleteDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<ExportCompleteDetail>(\n THREED_BUILDER_EXPORT_COMPLETE_EVENT,\n {\n bubbles: false,\n composed: false,\n detail,\n }\n )\n );\n}\n\n/**\n * Fired after a save that created a new product: \"Save as copy\", or a host\n * shopper's Save of a product they don't own (their first save). The builder\n * has switched to editing the new product. The host should persist\n * `newProductId` so subsequent loads of the builder open it.\n */\nexport const THREED_BUILDER_PRODUCT_CLONED_EVENT =\n 'tdn-product-cloned' as const;\n\nexport interface ProductClonedDetail {\n originalProductId: string;\n newProductId: string;\n name: string;\n /**\n * The user asked for a copy (\"Save as copy\", \"Save as my copy\", \"keep both\"\n * after a conflict) and expects to keep the original too. `false` when Save\n * had to fork because the user doesn't own the product: the copy takes the\n * original's place.\n */\n asCopy: boolean;\n}\n\nexport function dispatchProductCloned(\n host: HTMLElement,\n detail: ProductClonedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<ProductClonedDetail>(THREED_BUILDER_PRODUCT_CLONED_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * Fired when a protected action needs a logged-in user (save / screenshot /\n * export / upload / share), or \u2014 with `action: 'session'` \u2014 when the builder's\n * session could not be recovered (refresh failed). The host shows its own\n * login if its user is not logged in, has its BACKEND mint a token pair from\n * its own session, and sets `bearer-token` / `refresh-token` on the element;\n * the builder then resumes the pending action. If the user dismisses the login\n * or the mint fails, the host dispatches `tdn-login-cancelled` instead.\n * One event per login round-trip: further gated clicks while a login is\n * pending do not fire it again.\n */\nexport const THREED_BUILDER_LOGIN_REQUIRED_EVENT =\n 'tdn-login-required' as const;\n\nexport type LoginRequiredAction =\n | 'save'\n | 'screenshot'\n | 'export'\n | 'upload'\n | 'share'\n | 'session';\n\nexport interface LoginRequiredDetail {\n action: LoginRequiredAction;\n}\n\nexport function dispatchLoginRequired(\n host: HTMLElement,\n detail: LoginRequiredDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<LoginRequiredDetail>(THREED_BUILDER_LOGIN_REQUIRED_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * Host -> builder: the login the builder asked for (`tdn-login-required`) will\n * not happen \u2014 the user closed the host's login, or the host's mint failed.\n * The builder drops the pending action, so a stale Save never runs after a\n * later, unrelated login. A pending Save reports `tdn-design-save-failed`\n * with reason `login-cancelled`.\n */\nexport const THREED_BUILDER_LOGIN_CANCELLED_EVENT =\n 'tdn-login-cancelled' as const;\n\nexport interface LoginCancelledDetail {\n action?: LoginRequiredAction;\n}\n\nexport function dispatchLoginCancelled(\n host: HTMLElement,\n detail: LoginCancelledDetail = {}\n): void {\n host.dispatchEvent(\n new CustomEvent<LoginCancelledDetail>(\n THREED_BUILDER_LOGIN_CANCELLED_EVENT,\n { bubbles: false, composed: false, detail }\n )\n );\n}\n\n/**\n * Builder -> host: the builder cannot work on this page because of the\n * license, not because of the user. Fired once.\n * - `ORIGIN_NOT_ALLOWED`: this page's hostname is not the license's domain or\n * an alias. Do not answer it with a new token: a fresh mint for the same\n * page is refused the same way. Fix the license (add the hostname as an\n * alias) instead.\n * - `LICENSE_TOKEN_INVALID`: the `license-token` was rejected, the builder\n * asked for a fresh one (`tdn-license-token-required`), and the fresh one\n * was rejected too (or never came) \u2014 the license is suspended, revoked or\n * expired, or the host is minting against the wrong installation.\n */\nexport const THREED_BUILDER_LICENSE_ERROR_EVENT = 'tdn-license-error' as const;\n\nexport type LicenseErrorCode = 'ORIGIN_NOT_ALLOWED' | 'LICENSE_TOKEN_INVALID';\n\nexport interface LicenseErrorDetail {\n code: LicenseErrorCode;\n}\n\nexport function dispatchLicenseError(\n host: HTMLElement,\n detail: LicenseErrorDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<LicenseErrorDetail>(THREED_BUILDER_LICENSE_ERROR_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * The design was saved to 3dn. Fired as soon as the save is stored (before\n * the product's images are refreshed; `tdn-images-saved` or\n * `tdn-images-save-failed` follows), and after \"Save as copy\" too (then\n * followed by `tdn-product-cloned`). This \u2014 not `tdn-status-change` `ready`,\n * which also fires after every load step \u2014 is the signal a host should\n * continue on (add to cart, show a confirmation).\n */\nexport const THREED_BUILDER_DESIGN_SAVED_EVENT = 'tdn-design-saved' as const;\n\nexport interface DesignSavedDetail {\n productId: string;\n /** The stored serialized version after this save. */\n version: number;\n}\n\nexport function dispatchDesignSaved(\n host: HTMLElement,\n detail: DesignSavedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<DesignSavedDetail>(THREED_BUILDER_DESIGN_SAVED_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * The product's gallery images (its thumbnail and camera-angle screenshots)\n * now show the design at `version`, and reading the product returns them.\n * Fired after every `tdn-design-saved`, once the screenshots are uploaded and\n * stored on the product \u2014 before `tdn-product-cloned` when the save made a\n * copy. Also fired by the builder's Close when autosave had stored the design\n * without screenshots: the images are refreshed before `tdn-close-requested`.\n * This \u2014 not `tdn-design-saved` or `tdn-status-change` \u2014 is when a host may\n * read the product's images.\n */\nexport const THREED_BUILDER_IMAGES_SAVED_EVENT = 'tdn-images-saved' as const;\n\nexport interface ImagesSavedDetail {\n productId: string;\n /** The stored serialized version the images show. */\n version: number;\n}\n\nexport function dispatchImagesSaved(\n host: HTMLElement,\n detail: ImagesSavedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<ImagesSavedDetail>(THREED_BUILDER_IMAGES_SAVED_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n\n/**\n * The design was saved but its images could not be refreshed (capture or\n * upload failed); the product keeps its previous images. Fired in place of\n * `tdn-images-saved`. The builder has already warned the user; the design\n * itself is safe.\n */\nexport const THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT =\n 'tdn-images-save-failed' as const;\n\nexport function dispatchImagesSaveFailed(\n host: HTMLElement,\n detail: ImagesSavedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<ImagesSavedDetail>(\n THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT,\n { bubbles: true, composed: true, detail }\n )\n );\n}\n\n/**\n * Why a save did not happen:\n * - `conflict`: someone saved first; the builder shows its conflict dialog.\n * - `forbidden`: this user may not write to the product; the builder offers\n * request-access or save-as-copy.\n * - `unauthorized`: login recovery ran and the save was still refused.\n * - `login-cancelled`: the host sent `tdn-login-cancelled` for a pending save.\n * - `installation-inactive`: the host installation is not active (setup\n * problem on the host side, not a user error).\n * - `license`: refused with `ORIGIN_NOT_ALLOWED` \u2014 this page isn't licensed,\n * not a permissions or session problem; see `tdn-license-error`.\n * - `outdated`: the builder on the page is older than the server and can no\n * longer save; the builder offers a page reload, which carries the unsaved\n * design across where it can.\n * - `error`: network or server failure.\n */\nexport type DesignSaveFailureReason =\n | 'conflict'\n | 'forbidden'\n | 'unauthorized'\n | 'login-cancelled'\n | 'installation-inactive'\n | 'license'\n | 'outdated'\n | 'error';\n\n/**\n * A save the user asked for did not happen, after any retry and login\n * recovery ran out. Never fired while a login the builder asked for is still\n * pending. The builder has already told the user; the host may add its own\n * handling and, when `retryable`, let the user try again.\n */\nexport const THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT =\n 'tdn-design-save-failed' as const;\n\nexport interface DesignSaveFailedDetail {\n productId?: string;\n reason: DesignSaveFailureReason;\n retryable: boolean;\n}\n\nexport function dispatchDesignSaveFailed(\n host: HTMLElement,\n detail: DesignSaveFailedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<DesignSaveFailedDetail>(\n THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT,\n { bubbles: true, composed: true, detail }\n )\n );\n}\n\n/**\n * Builder -> host: the `license-token` (the no-user token from the host\n * backend's `POST /plugin/license-token`) was rejected \u2014 it expired, or the\n * installation's key was rotated. The host fetches a fresh license token from\n * its backend and sets `license-token` again; the builder holds the failed\n * request and retries it once with the new token. This is NEVER a login\n * prompt: answer it without involving the user. A second rejection ends in\n * `tdn-license-error` `LICENSE_TOKEN_INVALID`.\n */\nexport const THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT =\n 'tdn-license-token-required' as const;\n\nexport function dispatchLicenseTokenRequired(host: HTMLElement): void {\n host.dispatchEvent(\n new CustomEvent(THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT, {\n bubbles: true,\n composed: true,\n })\n );\n}\n\n/**\n * Host -> builder: save the design now, exactly as if the user pressed Save\n * (login gate, save-as-copy for a product the user does not own, and the\n * usual `tdn-design-saved` / `tdn-design-save-failed` outcome). A host sends\n * it before Add to cart when the design has not been saved yet, then waits\n * for one of those two events.\n */\nexport const THREED_BUILDER_SAVE_REQUEST_EVENT = 'tdn-save-request' as const;\n\nexport function dispatchSaveRequest(host: HTMLElement): void {\n host.dispatchEvent(\n new CustomEvent(THREED_BUILDER_SAVE_REQUEST_EVENT, {\n bubbles: false,\n composed: false,\n })\n );\n}\n\n/**\n * Builder -> host: the user pressed the builder's close button. The builder\n * does nothing else \u2014 it stays open and keeps its state. The host decides\n * what closing means on its page (hide an overlay, navigate back, ask first\n * when `hasUnsavedChanges`) and removes or hides the element itself.\n */\nexport const THREED_BUILDER_CLOSE_REQUESTED_EVENT =\n 'tdn-close-requested' as const;\n\nexport interface CloseRequestedDetail {\n productId?: string;\n /**\n * What is on screen differs from what was last saved (manual save or\n * autosave), in 2D or 3D: elements, the dieline/board, finish styles, or a\n * switched design. Opening and closing the box (fold) does not count, and\n * an edit undone by hand is no change.\n */\n hasUnsavedChanges: boolean;\n}\n\nexport function dispatchCloseRequested(\n host: HTMLElement,\n detail: CloseRequestedDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<CloseRequestedDetail>(\n THREED_BUILDER_CLOSE_REQUESTED_EVENT,\n { bubbles: true, composed: true, detail }\n )\n );\n}\n\n/**\n * Fired when the user picks \"AI Video\" inside the builder. The embedding host\n * navigates to its AI-video editor for `productId`, which then drives this same\n * product (loaded headlessly) to render the template's reference views.\n */\nexport const THREED_BUILDER_OPEN_AI_VIDEO_EVENT = 'tdn-open-ai-video' as const;\n\nexport interface OpenAiVideoDetail {\n productId: string;\n}\n\nexport function dispatchOpenAiVideo(\n host: HTMLElement,\n detail: OpenAiVideoDetail\n): void {\n host.dispatchEvent(\n new CustomEvent<OpenAiVideoDetail>(THREED_BUILDER_OPEN_AI_VIDEO_EVENT, {\n bubbles: true,\n composed: true,\n detail,\n })\n );\n}\n", "// Color groups with 12 variants each\nexport const colorGroups = [\n {\n name: 'Grays',\n colors: [\n '#FFFFFF',\n '#F8F9FA',\n '#E9ECEF',\n '#DEE2E6',\n '#CED4DA',\n '#ADB5BD',\n '#6C757D',\n '#495057',\n '#343A40',\n '#212529',\n '#1A1A1A',\n '#000000',\n ],\n },\n {\n name: 'Reds',\n colors: [\n '#FFE6E6',\n '#FFCCCC',\n '#FF9999',\n '#FF6666',\n '#FF3333',\n '#FF0000',\n '#E60000',\n '#CC0000',\n '#990000',\n '#660000',\n '#330000',\n '#1A0000',\n ],\n },\n {\n name: 'Oranges',\n colors: [\n '#FFF4E6',\n '#FFE6CC',\n '#FFCC99',\n '#FFB366',\n '#FF9933',\n '#FF8000',\n '#E66A00',\n '#CC5200',\n '#993D00',\n '#662900',\n '#331400',\n '#1A0A00',\n ],\n },\n {\n name: 'Yellows',\n colors: [\n '#FFFEF0',\n '#FFFDE6',\n '#FFFACC',\n '#FFF799',\n '#FFF266',\n '#FFED33',\n '#E6D100',\n '#CCBA00',\n '#998C00',\n '#665D00',\n '#332E00',\n '#1A1700',\n ],\n },\n {\n name: 'Greens',\n colors: [\n '#F0FFF0',\n '#E6FFE6',\n '#CCFFCC',\n '#99FF99',\n '#66FF66',\n '#33FF33',\n '#00E600',\n '#00CC00',\n '#009900',\n '#006600',\n '#003300',\n '#001A00',\n ],\n },\n {\n name: 'Blues',\n colors: [\n '#F0F8FF',\n '#E6F3FF',\n '#CCE7FF',\n '#99CFFF',\n '#66B7FF',\n '#339FFF',\n '#0080E6',\n '#0066CC',\n '#004D99',\n '#003366',\n '#001A33',\n '#000D1A',\n ],\n },\n {\n name: 'Purples',\n colors: [\n '#F8F0FF',\n '#F0E6FF',\n '#E0CCFF',\n '#C199FF',\n '#A266FF',\n '#8333FF',\n '#6600E6',\n '#5200CC',\n '#3D0099',\n '#290066',\n '#140033',\n '#0A001A',\n ],\n },\n {\n name: 'Pinks',\n colors: [\n '#FFE6F0',\n '#FFCCE0',\n '#FF99C1',\n '#FF66A2',\n '#FF3383',\n '#FF0064',\n '#E60057',\n '#CC0051',\n '#99003E',\n '#66002A',\n '#330015',\n '#1A000A',\n ],\n },\n {\n name: 'Teals',\n colors: [\n '#F0FFFF',\n '#E0FFFF',\n '#B2FFFF',\n '#66FFFF',\n '#1AFFFF',\n '#00CCCC',\n '#00B3B3',\n '#009999',\n '#006666',\n '#003333',\n '#001A1A',\n '#000D0D',\n ],\n },\n {\n name: 'Cyans',\n colors: [\n '#E0F7FA',\n '#B2EBF2',\n '#80DEEA',\n '#4DD0E1',\n '#26C6DA',\n '#00BCD4',\n '#00ACC1',\n '#0097A7',\n '#00796B',\n '#004D40',\n '#00251A',\n '#00120D',\n ],\n },\n {\n name: 'Indigos',\n colors: [\n '#E8EAF6',\n '#C5CAE9',\n '#9FA8DA',\n '#7986CB',\n '#5C6BC0',\n '#3F51B5',\n '#303F9F',\n '#283593',\n '#1A237E',\n '#0D1442',\n '#060A21',\n '#030510',\n ],\n },\n {\n name: 'Violets',\n colors: [\n '#F3E5F5',\n '#E1BEE7',\n '#CE93D8',\n '#BA68C8',\n '#AB47BC',\n '#9C27B0',\n '#8E24AA',\n '#7B1FA2',\n '#6A1B9A',\n '#4A148C',\n '#2E1065',\n '#1A0D3D',\n ],\n },\n {\n name: 'Magentas',\n colors: [\n '#FCE4EC',\n '#F8BBD9',\n '#F48FB1',\n '#F06292',\n '#EC407A',\n '#E91E63',\n '#D81B60',\n '#C2185B',\n '#AD1457',\n '#880E4F',\n '#4A0A2A',\n '#2D0519',\n ],\n },\n {\n name: 'Limes',\n colors: [\n '#F9FBE7',\n '#F0F4C3',\n '#E6EE9C',\n '#DCE775',\n '#D4E157',\n '#CDDC39',\n '#C0CA33',\n '#AFB42B',\n '#9E9D24',\n '#827717',\n '#4A4514',\n '#2D2A0C',\n ],\n },\n {\n name: 'Ambers',\n colors: [\n '#FFF8E1',\n '#FFECB3',\n '#FFE082',\n '#FFD54F',\n '#FFCA28',\n '#FFC107',\n '#FFB300',\n '#FFA000',\n '#FF8F00',\n '#FF6F00',\n '#994200',\n '#5A2700',\n ],\n },\n {\n name: 'Deep Oranges',\n colors: [\n '#FBE9E7',\n '#FFCCBC',\n '#FFAB91',\n '#FF8A65',\n '#FF7043',\n '#FF5722',\n '#F4511E',\n '#E64A19',\n '#D84315',\n '#BF360C',\n '#7A2408',\n '#4A1605',\n ],\n },\n {\n name: 'Light Blues',\n colors: [\n '#E1F5FE',\n '#B3E5FC',\n '#81D4FA',\n '#4FC3F7',\n '#29B6F6',\n '#03A9F4',\n '#039BE5',\n '#0288D1',\n '#0277BD',\n '#01579B',\n '#003A66',\n '#00223D',\n ],\n },\n {\n name: 'Light Greens',\n colors: [\n '#E8F5E8',\n '#C8E6C9',\n '#A5D6A7',\n '#81C784',\n '#66BB6A',\n '#4CAF50',\n '#43A047',\n '#388E3C',\n '#2E7D32',\n '#1B5E20',\n '#0F3A14',\n '#09230C',\n ],\n },\n {\n name: 'Browns',\n colors: [\n '#F5F5DC',\n '#F0E68C',\n '#D2B48C',\n '#BC8F8F',\n '#A0522D',\n '#8B4513',\n '#654321',\n '#4A2C2A',\n '#2F1B14',\n '#1A0E0A',\n '#0F0705',\n '#090503',\n ],\n },\n {\n name: 'Grey-Blues',\n colors: [\n '#ECEFF1',\n '#CFD8DC',\n '#B0BEC5',\n '#90A4AE',\n '#78909C',\n '#607D8B',\n '#546E7A',\n '#455A64',\n '#37474F',\n '#263238',\n '#1A2126',\n '#0F1316',\n ],\n },\n];\n\n// Flattened array for backward compatibility\nexport const colors: string[] = colorGroups.flatMap((group) => group.colors);\n", "export const USER_ROLES = ['ADMIN', 'DESIGNER', 'USER', 'CUSTOMER'] as const;\nexport const AUTH_PROVIDERS = ['REGULAR', 'GOOGLE'] as const;\nexport const TIER_TYPES = ['PAID', 'TRAIL', 'FREE', 'EXPIRED'] as const;\nexport const GLB_MATERIALS = ['PBR', 'STANDARD', 'UNKNOWN'] as const;\nexport const VISIBILITY_STATUS = [\n 'private',\n 'public',\n 'store',\n 'unlisted',\n] as const;\n\nexport const HDRI_CATEGORIES = [\n 'outdoor',\n 'indoor',\n 'studio',\n 'sunset',\n 'night',\n 'abstract',\n] as const;\nexport type HdriCategory = (typeof HDRI_CATEGORIES)[number];\n\nexport const HDRI_CUBE_SIZES = [64, 128, 256, 512] as const;\nexport type HdriCubeSize = (typeof HDRI_CUBE_SIZES)[number];\n\n/** Maximum byte length of a published HDRI's .env file. Enforced by API and UI. */\nexport const HDRI_PUBLISH_MAX_ENV_BYTES = 1024 * 1024; // 1 MB\n\n/** Default pixel size of an imported GLB's dynamic texture. Admins can set any\n * size from 1 to MAX_DYNAMIC_TEXTURE_SIZE per axis. */\nexport const DEFAULT_DYNAMIC_TEXTURE_RESOLUTION = {\n width: 2048,\n height: 2048,\n} as const;\n\n/** Upper bound on dynamic texture dimensions. Beyond this, quality gains are\n * negligible on most GPUs but memory cost scales quadratically. Enforced by\n * API and UI; the builder clamps to it too. */\nexport const MAX_DYNAMIC_TEXTURE_SIZE = 4096;\n\n/** Above this, a design layer's combined path data may slow canvas rendering. */\nexport const MAX_LAYER_PATH_BYTES = 600 * 1024; // 600 KB\n\nexport const MAX_FILE_SIZES = {\n MODEL: 10,\n IMAGE: 20,\n THUMBNAIL: 5,\n DEBUG: 0.5,\n ENV: 10,\n FONT: 20,\n HDRI_SOURCE: 25,\n} as const;\n\n/**\n * Default max upload size (in MB) applied to upload endpoints when no\n * explicit override is provided. Shared between the API middleware and the\n * frontend so that client-side validation matches the server limit.\n */\nexport const DEFAULT_MAX_UPLOAD_SIZE_MB = 10;\n\n/**\n * Centralized upload file-type definitions.\n *\n * Each entry describes a named group of files that the API accepts at a\n * single upload endpoint. The API's upload middleware uses `mimeTypes` /\n * `extensions` for validation, and the Angular frontend uses `accept`\n * directly on `<input type=\"file\">` elements so both layers stay in sync.\n *\n * - `IMAGE` \u2013 any raster or vector image (generic image uploads).\n * - `RASTER_IMAGE` \u2013 raster only (design images the AI separates into layers).\n * - `SVG` \u2013 vector only (design layer uploads, pattern SVG uploads).\n * - `MODEL` \u2013 3D model bundles (glb, gltf + referenced textures).\n * - `FONT` \u2013 font files.\n * - `ENV` \u2013 environment maps for 3D scenes.\n */\nexport type UploadFileTypeKey =\n | 'IMAGE'\n | 'RASTER_IMAGE'\n | 'SVG'\n | 'MODEL'\n | 'FONT'\n | 'ENV';\n\nexport interface UploadFileTypeConfig {\n /** Human readable label used in error messages and UI copy. */\n readonly label: string;\n /** Allowed MIME types (lowercase). */\n readonly mimeTypes: readonly string[];\n /** Allowed file extensions including the leading dot (lowercase). */\n readonly extensions: readonly string[];\n /** Value for the HTML `<input accept>` attribute. */\n readonly accept: string;\n}\n\nexport const UPLOAD_FILE_TYPES: Readonly<\n Record<UploadFileTypeKey, UploadFileTypeConfig>\n> = {\n IMAGE: {\n label: 'PNG, JPG, JPEG, WEBP or SVG',\n mimeTypes: [\n 'image/png',\n 'image/jpeg',\n 'image/jpg',\n 'image/webp',\n 'image/svg+xml',\n ],\n extensions: ['.png', '.jpg', '.jpeg', '.webp', '.svg'],\n accept: 'image/png,image/jpeg,image/jpg,image/webp,image/svg+xml',\n },\n RASTER_IMAGE: {\n label: 'PNG, JPG, JPEG or WEBP',\n mimeTypes: ['image/png', 'image/jpeg', 'image/jpg', 'image/webp'],\n extensions: ['.png', '.jpg', '.jpeg', '.webp'],\n accept: 'image/png,image/jpeg,image/jpg,image/webp',\n },\n SVG: {\n label: 'SVG',\n mimeTypes: ['image/svg+xml'],\n extensions: ['.svg'],\n accept: 'image/svg+xml,.svg',\n },\n MODEL: {\n label: 'GLB, GLTF, BIN, PNG, JPG, JPEG or WEBP',\n mimeTypes: [\n 'model/gltf-binary',\n 'model/gltf+json',\n 'application/octet-stream',\n 'image/png',\n 'image/jpeg',\n 'image/jpg',\n 'image/webp',\n ],\n extensions: ['.glb', '.gltf', '.bin', '.png', '.jpg', '.jpeg', '.webp'],\n accept: '.glb,.gltf,.bin,.png,.jpg,.jpeg,.webp',\n },\n FONT: {\n label: 'OTF, TTF or TXT',\n mimeTypes: [\n 'font/ttf',\n 'font/otf',\n 'application/octet-stream',\n 'text/plain',\n ],\n extensions: ['.ttf', '.otf', '.txt'],\n accept: '.ttf,.otf,.txt',\n },\n ENV: {\n label: '.env or .hdr',\n mimeTypes: ['application/octet-stream'],\n extensions: ['.env', '.hdr'],\n accept: '.env,.hdr',\n },\n};\n\n/**\n * Stable string codes the upload middleware emits on 4xx responses. The\n * frontend matches against these to render specific user-facing messages\n * (oversize, wrong type, etc.) without relying on the backend message text.\n */\nexport const UPLOAD_ERROR_CODES = {\n FILE_TOO_LARGE: 'FILE_TOO_LARGE',\n INVALID_FILE_TYPE: 'INVALID_FILE_TYPE',\n FILE_REQUIRED: 'FILE_REQUIRED',\n TOO_MANY_FILES: 'TOO_MANY_FILES',\n UNEXPECTED_FILE_FIELD: 'UNEXPECTED_FILE_FIELD',\n UPLOAD_FAILED: 'UPLOAD_FAILED',\n} as const;\n\nexport type UploadErrorCode =\n (typeof UPLOAD_ERROR_CODES)[keyof typeof UPLOAD_ERROR_CODES];\n\nexport interface UploadErrorDetails {\n fileType?: UploadFileTypeKey;\n maxSizeMB?: number;\n allowedTypes?: readonly string[];\n filename?: string;\n mimeType?: string;\n field?: string;\n [key: string]: unknown;\n}\n\nexport const ROLES = {\n ADMIN: 'ADMIN',\n DESIGNER: 'DESIGNER',\n USER: 'USER',\n} as const;\n\nexport const ACTIVITY_EVENT_TYPES = [\n 'product.created',\n 'product.updated',\n 'product.published',\n 'product.deleted',\n 'product.design_added',\n 'product.design_removed',\n 'product.serialized_saved',\n 'product.serialized_restored',\n 'product.dieline_resnapshotted',\n 'product.screenshot_captured',\n 'product.shared',\n 'product.unshared',\n 'product.access_requested',\n 'product.access_approved',\n 'product.access_denied',\n 'design.created',\n 'design.updated',\n 'design.published',\n 'design.deleted',\n 'design.layers_updated',\n 'design.thumbnail_generated',\n 'glb.uploaded',\n 'glb.updated',\n 'glb.deleted',\n 'user.login',\n 'user.logout',\n // Installation key lifecycle. Rotation and revocation can lock a host's\n // users out, so they need a durable trail, not just a log line.\n 'installation.key_issued',\n 'installation.key_rotated',\n 'installation.disabled',\n 'installation.enabled',\n 'installation.revoked',\n // License terms (Phase 2).\n 'installation.terms_changed',\n 'installation.ips_changed',\n 'installation.seat_limit_reached',\n] as const;\n\nexport const ACTIVITY_ENTITY_TYPES = [\n 'product',\n 'design',\n 'glb',\n 'user',\n 'print_area',\n 'installation',\n] as const;\n\n// Global requests: the entities that go global only when a reviewer makes a\n// request global. HDRIs, AI themes and fonts are made global by any admin.\nexport const GLOBAL_REQUEST_ENTITY_TYPES = [\n 'product',\n 'design',\n 'pattern',\n 'library_item',\n 'glb',\n] as const;\n/** What people call each type that goes global by request. */\nexport const GLOBAL_REQUEST_ENTITY_LABELS = {\n product: 'Product',\n design: 'Design',\n pattern: 'Pattern',\n library_item: 'Library item',\n glb: '3D model',\n} as const satisfies Record<\n (typeof GLOBAL_REQUEST_ENTITY_TYPES)[number],\n string\n>;\n\n// Installation & Access Grant constants\nexport const INSTALLATION_PLATFORMS = [\n 'wordpress',\n 'shopify',\n 'web-app',\n] as const;\nexport const INSTALLATION_STATUS = ['active', 'suspended', 'revoked'] as const;\n", "import { INSTALLATION_STATUS } from './constants';\n\n/**\n * Whether a license works is decided here and only here. `expired` is never\n * stored: it is computed from `expiresAt` at the moment of the check, so an\n * expiry takes effect without any job.\n */\nexport const LICENSE_STATES = [...INSTALLATION_STATUS, 'expired'] as const;\nexport type LicenseState = (typeof LICENSE_STATES)[number];\n\n/** Owners and admins are warned this many days before a license expires. */\nexport const LICENSE_EXPIRY_WARNING_DAYS = 14;\n/** Owners and admins are warned once this share of the seat cap is used. */\nexport const LICENSE_SEAT_WARNING_RATIO = 0.9;\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nexport interface LicenseTerms {\n status: (typeof INSTALLATION_STATUS)[number];\n expiresAt?: Date | string | null;\n}\n\n/** Status wins over expiry: a revoked license reports `revoked`, not `expired`. */\nexport function licenseState(\n license: LicenseTerms,\n now: Date = new Date()\n): LicenseState {\n if (license.status !== 'active') return license.status;\n if (\n license.expiresAt != null &&\n new Date(license.expiresAt).getTime() <= now.getTime()\n ) {\n return 'expired';\n }\n return 'active';\n}\n\nexport type SeatLevel = 'unlimited' | 'ok' | 'warning' | 'full';\n\nexport function seatLevel(\n endUserCount: number,\n seatLimit: number | null | undefined\n): SeatLevel {\n if (seatLimit == null) return 'unlimited';\n if (endUserCount >= seatLimit) return 'full';\n if (endUserCount >= seatLimit * LICENSE_SEAT_WARNING_RATIO) return 'warning';\n return 'ok';\n}\n\nexport type ExpiryLevel = 'none' | 'ok' | 'soon' | 'expired';\n\nexport function expiryLevel(\n expiresAt: Date | string | null | undefined,\n now: Date = new Date()\n): ExpiryLevel {\n if (expiresAt == null) return 'none';\n const remaining = new Date(expiresAt).getTime() - now.getTime();\n if (remaining <= 0) return 'expired';\n if (remaining <= LICENSE_EXPIRY_WARNING_DAYS * DAY_MS) return 'soon';\n return 'ok';\n}\n\n/**\n * The 401 error code admin-api answers when a LICENSE token (the no-user\n * bearer a host sets as `license-token`) is rejected \u2014 expired, its license\n * inactive, or its key rotated. The builder answers it by asking the host for\n * a fresh license token (`tdn-license-token-required`), never with a login\n * prompt; a route's own \"needs a user\" 401 keeps the generic code.\n */\nexport const LICENSE_TOKEN_INVALID_CODE = 'LICENSE_TOKEN_INVALID';\n", "import { MAX_DYNAMIC_TEXTURE_SIZE } from './constants';\n\n/**\n * How much offscreen rendering memory the builder may use, per device class.\n *\n * Phones die quietly when the builder asks for too much: iOS Safari refuses\n * any canvas over {@link IOS_CANVAS_AREA_LIMIT} pixels (the draw does\n * nothing, the design goes blank) and kills the tab when total canvas or GPU\n * memory runs out, with no error for Sentry to see; Android's GPU process\n * crashes until Chrome blocks WebGL for the site. Every 3D preview texture\n * and every capture canvas is therefore sized from one of these budgets.\n *\n * The budget sizes only what the 3D model SAMPLES. Design coordinates (the\n * 10 px/mm dieline grid, a GLB's stored dynamicTextureResolution) and print\n * output never read it.\n */\nexport type DeviceTier = 'full' | 'mobile' | 'light';\n\n/** A texture may be at most `maxSide` on either axis and `maxArea` pixels. */\nexport interface TextureLimit {\n maxSide: number;\n maxArea: number;\n}\n\nexport interface DeviceBudget {\n tier: DeviceTier;\n /** Largest 3D preview texture. */\n texture: TextureLimit;\n /** Largest single offscreen capture canvas, pixels. */\n maxCaptureArea: number;\n /** Most a final-quality capture supersamples its texture by. */\n maxOversample: number;\n}\n\n/** What the browser tells us about the device; see `readDeviceSignals`. */\nexport interface DeviceSignals {\n /** `matchMedia('(pointer: coarse)')`: the primary pointer is a finger. */\n coarsePointer: boolean;\n maxTouchPoints: number;\n userAgent: string;\n /** `navigator.deviceMemory` (Chromium only, rounded GB); undefined elsewhere. */\n deviceMemoryGB: number | undefined;\n}\n\n/** WebKit's per-canvas pixel limit (4096\u00B2). Over it a canvas silently draws nothing. */\nexport const IOS_CANVAS_AREA_LIMIT = 16_777_216;\n\nexport const DEVICE_BUDGETS: Readonly<Record<DeviceTier, DeviceBudget>> = {\n full: {\n tier: 'full',\n texture: {\n maxSide: MAX_DYNAMIC_TEXTURE_SIZE,\n maxArea: MAX_DYNAMIC_TEXTURE_SIZE * MAX_DYNAMIC_TEXTURE_SIZE,\n },\n maxCaptureArea: 33_554_432,\n maxOversample: 2,\n },\n // Phones and tablets: no supersampling. The texture is already minified on\n // a phone screen, so 2x buys nothing visible and quadruples the capture.\n mobile: {\n tier: 'mobile',\n texture: { maxSide: 2048, maxArea: 2048 * 2048 },\n maxCaptureArea: 4_194_304,\n maxOversample: 1,\n },\n light: {\n tier: 'light',\n texture: { maxSide: 1536, maxArea: 1536 * 1536 },\n maxCaptureArea: 2_359_296,\n maxOversample: 1,\n },\n};\n\n/**\n * Touch-first devices that report `navigator.deviceMemory` (Chromium only) at\n * or below this many GB get the light tier. Chrome rounds memory down to a\n * power of two, so a 6 GB phone reports 4 and lands here too \u2014 intentionally\n * conservative.\n */\nconst LIGHT_TIER_MAX_MEMORY_GB = 4;\n\n/**\n * The tier for a device. Touch-first devices (phones, tablets, an iPad that\n * sends the desktop Mac user agent) get `mobile`; those that also report\n * little memory get `light`; everything else gets `full`.\n */\nexport function deviceTierFor(signals: DeviceSignals): DeviceTier {\n const iPadAsMac =\n /Macintosh/.test(signals.userAgent) && signals.maxTouchPoints > 1;\n const touchFirst =\n signals.coarsePointer ||\n iPadAsMac ||\n /iPhone|iPad|iPod|Android/.test(signals.userAgent);\n if (!touchFirst) return 'full';\n if (\n signals.deviceMemoryGB !== undefined &&\n signals.deviceMemoryGB <= LIGHT_TIER_MAX_MEMORY_GB\n ) {\n return 'light';\n }\n return 'mobile';\n}\n\n/**\n * `size` scaled down UNIFORMLY until it fits `limit` (never up). Uniform, so\n * px-per-mm stays the same on both axes; floored, so the result never\n * exceeds either limit.\n */\nexport function fitToTextureLimit(\n size: { width: number; height: number },\n limit: TextureLimit\n): { width: number; height: number } {\n const w = Math.max(1, size.width);\n const h = Math.max(1, size.height);\n const scale = Math.min(\n 1,\n limit.maxSide / w,\n limit.maxSide / h,\n Math.sqrt(limit.maxArea / (w * h))\n );\n if (scale === 1) return { width: size.width, height: size.height };\n return {\n width: Math.max(1, Math.floor(w * scale)),\n height: Math.max(1, Math.floor(h * scale)),\n };\n}\n\n/**\n * How much a final-quality capture of an `output`-sized texture may\n * supersample: the tier's maximum, lowered until the capture canvas fits\n * `maxCaptureArea`, never below 1\u00D7.\n */\nexport function captureOversample(\n output: { width: number; height: number },\n budget: DeviceBudget\n): number {\n const area = Math.max(1, output.width) * Math.max(1, output.height);\n return Math.max(\n 1,\n Math.min(budget.maxOversample, Math.sqrt(budget.maxCaptureArea / area))\n );\n}\n"],
|
|
5
|
+
"mappings": "AAIO,IAAMA,EAA4B,mBAElC,SAASC,EAA0BC,EAAyB,CACjEA,EAAK,cACH,IAAI,YAAYF,EAA2B,CACzC,QAAS,GACT,SAAU,EACZ,CAAC,CACH,CACF,CAOO,IAAMG,EAA8B,oBAwCpC,IAAMC,EAAwC,kBAoCxCC,EACX,sBAYK,SAASC,EACdC,EACAC,EACM,CACND,EAAK,cACH,IAAI,YACFF,EACA,CACE,QAAS,GACT,SAAU,GACV,OAAAG,CACF,CACF,CACF,CACF,CAQO,IAAMC,EACX,qBAuCK,IAAMC,EACX,qBAkCK,IAAMC,EACX,sBAMK,SAASC,EACdC,EACAC,EAA+B,CAAC,EAC1B,CACND,EAAK,cACH,IAAI,YACFF,EACA,CAAE,QAAS,GAAO,SAAU,GAAO,OAAAG,CAAO,CAC5C,CACF,CACF,CAcO,IAAMC,EAAqC,oBA6B3C,IAAMC,EAAoC,mBA+B1C,IAAMC,EAAoC,mBA2B1C,IAAMC,EACX,yBA8CK,IAAMC,EACX,yBA6BK,IAAMC,EACX,6BAkBK,IAAMC,EAAoC,mBAE1C,SAASC,EAAoBC,EAAyB,CAC3DA,EAAK,cACH,IAAI,YAAYF,EAAmC,CACjD,QAAS,GACT,SAAU,EACZ,CAAC,CACH,CACF,CAQO,IAAMG,EACX,sBCrbK,IAAMC,EAAc,CACzB,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,OACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,SACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,UACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,WACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,QACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,SACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,eACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,cACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,eACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,SACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,EACA,CACE,KAAM,aACN,OAAQ,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,CACF,CACF,EAGaC,EAAmBD,EAAY,QAASE,GAAUA,EAAM,MAAM,EChUpE,IAAMC,EAA6B,KAAO,KAY1C,IAAMC,EAA2B,KAG3BC,EAAuB,IAAM,KA8NnC,IAAMC,EAAsB,CAAC,SAAU,YAAa,SAAS,EC/P7D,IAAMC,EAAiB,CAAC,GAAGC,EAAqB,SAAS,EAQhE,IAAMC,EAAS,KAAU,GAAK,ICgCvB,IAAMC,EAA6D,CACxE,KAAM,CACJ,KAAM,OACN,QAAS,CACP,QAASC,EACT,QAASA,EAA2BA,CACtC,EACA,eAAgB,SAChB,cAAe,CACjB,EAGA,OAAQ,CACN,KAAM,SACN,QAAS,CAAE,QAAS,KAAM,QAAS,KAAO,IAAK,EAC/C,eAAgB,QAChB,cAAe,CACjB,EACA,MAAO,CACL,KAAM,QACN,QAAS,CAAE,QAAS,KAAM,QAAS,KAAO,IAAK,EAC/C,eAAgB,QAChB,cAAe,CACjB,CACF",
|
|
6
|
+
"names": ["THREED_BUILDER_OPEN_EVENT", "dispatchOpenThreedBuilder", "host", "THREED_BUILDER_STATUS_EVENT", "THREED_BUILDER_EXPORT_REQUESTED_EVENT", "THREED_BUILDER_EXPORT_COMPLETE_EVENT", "dispatchExportComplete", "host", "detail", "THREED_BUILDER_PRODUCT_CLONED_EVENT", "THREED_BUILDER_LOGIN_REQUIRED_EVENT", "THREED_BUILDER_LOGIN_CANCELLED_EVENT", "dispatchLoginCancelled", "host", "detail", "THREED_BUILDER_LICENSE_ERROR_EVENT", "THREED_BUILDER_DESIGN_SAVED_EVENT", "THREED_BUILDER_IMAGES_SAVED_EVENT", "THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT", "THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT", "THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT", "THREED_BUILDER_SAVE_REQUEST_EVENT", "dispatchSaveRequest", "host", "THREED_BUILDER_CLOSE_REQUESTED_EVENT", "colorGroups", "colors", "group", "HDRI_PUBLISH_MAX_ENV_BYTES", "MAX_DYNAMIC_TEXTURE_SIZE", "MAX_LAYER_PATH_BYTES", "INSTALLATION_STATUS", "LICENSE_STATES", "INSTALLATION_STATUS", "DAY_MS", "DEVICE_BUDGETS", "MAX_DYNAMIC_TEXTURE_SIZE"]
|
|
7
7
|
}
|
package/events.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as E,c as _,d as D,e,g as R,h as T,i as L,k as t,l as I,m as i,n as N,o as a,p as S,q as U,s as l}from"./chunks/chunk-
|
|
1
|
+
import{a as E,c as _,d as D,e,g as R,h as T,i as L,k as t,l as I,m as i,n as N,o as a,p as S,q as U,s as l}from"./chunks/chunk-FRDLXZMM.js";export{l as THREED_BUILDER_CLOSE_REQUESTED_EVENT,I as THREED_BUILDER_DESIGN_SAVED_EVENT,a as THREED_BUILDER_DESIGN_SAVE_FAILED_EVENT,e as THREED_BUILDER_EXPORT_COMPLETE_EVENT,D as THREED_BUILDER_EXPORT_REQUESTED_EVENT,i as THREED_BUILDER_IMAGES_SAVED_EVENT,N as THREED_BUILDER_IMAGES_SAVE_FAILED_EVENT,t as THREED_BUILDER_LICENSE_ERROR_EVENT,S as THREED_BUILDER_LICENSE_TOKEN_REQUIRED_EVENT,L as THREED_BUILDER_LOGIN_CANCELLED_EVENT,T as THREED_BUILDER_LOGIN_REQUIRED_EVENT,E as THREED_BUILDER_OPEN_EVENT,R as THREED_BUILDER_PRODUCT_CLONED_EVENT,U as THREED_BUILDER_SAVE_REQUEST_EVENT,_ as THREED_BUILDER_STATUS_EVENT};
|
|
2
2
|
//# sourceMappingURL=events.js.map
|
package/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as k,c as P,d as _,e as B,f as O,g as C,h as A,j as m,k as N,l as M,m as F,n as U,o as V,p as H,r as $,s as q}from"./chunks/chunk-LZ76K5OM.js";var s=class t extends Error{constructor(e,n,r={}){var i,o;super(n),this.name="BuilderError",this.code=e,this.retryable=(i=r.retryable)!=null?i:!1,this.fatal=(o=r.fatal)!=null?o:!1,this.reason=r.reason,this.cause=r.cause,Object.setPrototypeOf(this,t.prototype)}},j={conflict:"Someone else saved this design first.",forbidden:"This shopper may not save this design.",unauthorized:"The shopper could not be signed in to save.","login-cancelled":"The design was not saved because the shopper did not log in.","installation-inactive":"This site's 3DN installation is not active.",license:"This page is not licensed to use the 3DN builder.",outdated:"The builder on this page is out of date. Reload the page to save the design.",error:"The design could not be saved. Check the connection and try again."};function z(t){var n;let e=(n=j[t.reason])!=null?n:j.error;return t.reason==="login-cancelled"?new s("LOGIN_CANCELLED",e,{retryable:!0,reason:t.reason,cause:t}):new s("SAVE_FAILED",e,{retryable:t.retryable,reason:t.reason,cause:t})}function W(t){let e=t==="ORIGIN_NOT_ALLOWED"?"This website is not on the 3DN license. Add its hostname to the license.":"The 3DN license token was rejected. The license may be suspended, revoked or expired.";return new s("LICENSE_ERROR",e,{fatal:!0,reason:t,cause:{code:t}})}function g(){return new s("DESTROYED","The builder was destroyed.")}function a(t){return new s("CONFIG_INVALID",t,{fatal:!0})}var se=45e3,G=12e4,ae=new Set(["environment","version","saveTimeoutMs","session","login","customization","onSaved","onImagesSaved","onLicenseError","onCloseRequested","onStatusChange","debug"]),de=new Set(["target","hostProductId","mode","designId","externalExport","observabilityContext"]),ue=/^\d+\.\d+\.\d+$/,le=/^tdn_inst_/;function b(t){return ue.test(t)}function u(t){return typeof t=="object"&&t!==null}function f(t){return typeof t=="function"}function X(t){return typeof t=="number"&&Number.isFinite(t)&&t>0}function K(t,e=0){return typeof t=="string"?le.test(t):!u(t)||e>3?!1:Object.values(t).some(n=>K(n,e+1))}function Y(t,e,n){for(let r of Object.keys(t))if(!e.has(r))throw/url|src|host|origin/i.test(r)?a(`${n}: "${r}" is not allowed. The SDK decides where the builder loads from; choose an environment and a version instead.`):a(`${n}: unknown option "${r}".`)}function J(t){var h,l,c;if(!u(t))throw a("createBuilder() needs a configuration object.");if(K(t))throw a("createBuilder(): an installation key was passed to the browser. Installation keys stay on your server; pass a session callback instead.");Y(t,ae,"createBuilder()");let e=(h=t.environment)!=null?h:"production";if(e!=="production"&&e!=="staging")throw a(`createBuilder(): environment must be "production" or "staging", got ${JSON.stringify(e)}.`);let n=(l=t.version)!=null?l:"latest";if(typeof n!="string"||n!=="latest"&&!b(n))throw a(`createBuilder(): version must be "latest" or an exact release such as "1.2.3", got ${JSON.stringify(n)}.`);let r=(c=t.saveTimeoutMs)!=null?c:se;if(!X(r))throw a("createBuilder(): saveTimeoutMs must be a positive number of milliseconds.");let i=t.session;if(!u(i)||!f(i.get)||!f(i.refreshLicense))throw a("createBuilder(): session.get() and session.refreshLicense() are required. They call your session endpoint.");let o=t.login;if(o!==void 0){if(!u(o)||!f(o.getTokens)||!f(o.onRequired))throw a("createBuilder(): login needs getTokens() (your login-token endpoint) and onRequired() (your login UI).");if(o.onMintFailed!==void 0&&!f(o.onMintFailed))throw a("createBuilder(): login.onMintFailed must be a function.")}let d=t.customization;if(d!==void 0&&(!u(d)||!f(d.record)))throw a("createBuilder(): customization.record() is required when customization is set. It calls your customization endpoint.");for(let T of["onSaved","onImagesSaved","onLicenseError","onCloseRequested","onStatusChange"])if(t[T]!==void 0&&!f(t[T]))throw a(`createBuilder(): ${T} must be a function.`);return{...t,environment:e,version:n,saveTimeoutMs:r}}function Q(t){var d;if(!u(t))throw a("mount() needs an options object.");Y(t,de,"mount()");let e=t.target;if(typeof HTMLElement=="undefined"||!(e instanceof HTMLElement))throw a("mount(): target must be an HTMLElement on the page.");let n=t.hostProductId;if(typeof n!="string"||n.trim()==="")throw a("mount(): hostProductId must be your own, non-empty product reference.");let r=(d=t.mode)!=null?d:"designer";if(r!=="designer"&&r!=="viewer")throw a(`mount(): mode must be "designer" or "viewer", got ${JSON.stringify(r)}.`);let i=t.designId;if(i!==void 0&&(typeof i!="string"||i===""))throw a("mount(): designId must be a non-empty string.");t.observabilityContext!==void 0&&S(t.observabilityContext,"mount()");let o=t.externalExport;if(o!==void 0){if(!u(o)||!f(o.run))throw a("mount(): externalExport.run() is required when externalExport is set.");if(o.timeoutMs!==void 0&&!X(o.timeoutMs))throw a("mount(): externalExport.timeoutMs must be a positive number of milliseconds.")}return t}function S(t,e){let n;try{n=typeof t=="string"?JSON.parse(t):void 0}catch{n=void 0}if(!u(n)||Array.isArray(n))throw a(`${e}: observabilityContext must be a JSON object string.`);return t}function w(t){let e=i=>new s("SESSION_FAILED",`session.get() returned an invalid session: ${i}.`,{retryable:!1});if(!u(t))throw e("expected an object");if(typeof t.licenseToken!="string"||t.licenseToken==="")throw e("licenseToken is missing");if(typeof t.productId!="string"||t.productId==="")throw e("productId is missing");if(t.viewToken!=null&&typeof t.viewToken!="string")throw e("viewToken must be a string");let n=t.token!=null,r=t.refreshToken!=null;if(n!==r)throw e("token and refreshToken must be returned together");if(n&&(typeof t.token!="string"||typeof t.refreshToken!="string"))throw e("token and refreshToken must be strings");return t}function Z(t){if(t===null)return null;if(!u(t)||typeof t.token!="string"||t.token===""||typeof t.refreshToken!="string"||t.refreshToken==="")throw new s("SESSION_FAILED","login.getTokens() must resolve { token, refreshToken } or null.");return t}var ce=/token|secret|key|authorization/i;function I(t,e=0){if(t===null||typeof t!="object"||e>4)return t;if(Array.isArray(t))return t.map(r=>I(r,e+1));let n={};for(let[r,i]of Object.entries(t))n[r]=ce.test(r)&&i!=null?"[redacted]":I(i,e+1);return n}function ee(t){return t?(e,n)=>{n===void 0?console.debug(`[3DN] ${e}`):console.debug(`[3DN] ${e}`,I(n))}:()=>{}}var v="threed-builder",re={production:"https://3d-builder.3dn.app",staging:void 0},he=3e4,te=Symbol.for("@3dn-builder/builder/loader");function fe(){let t=globalThis,e=t[te];return e||(e={},t[te]=e),e}function pe(){return typeof window!="undefined"&&typeof document!="undefined"&&typeof customElements!="undefined"}function ie(t){if(!re[t])throw a(`createBuilder(): the "${t}" environment is not available in this SDK release.`)}function p(t,e){return new s("LOAD_FAILED",t,{retryable:!0,cause:e})}async function ge(t,e){if(e!=="latest")return e;let n;try{n=await fetch(`${t}/version-info.json`,{cache:"no-store",credentials:"omit"})}catch(o){throw p("The 3DN builder could not be reached.",o)}if(!n.ok)throw p(`The 3DN builder release could not be resolved (HTTP ${n.status}).`);let r=await n.json().catch(()=>null),i=r==null?void 0:r.version;if(typeof i!="string"||!b(i))throw p("The 3DN builder release information was not valid.");return i}function me(t){return new Promise((e,n)=>{let r=document.createElement("script");r.type="module",r.src=t,r.crossOrigin="anonymous",r.onload=()=>e(r),r.onerror=()=>{r.remove(),n(p("The 3DN builder failed to load."))},document.head.appendChild(r)})}function ve(){return new Promise((t,e)=>{let n=setTimeout(()=>e(p("The 3DN builder loaded but did not start.")),he);customElements.whenDefined(v).then(()=>{clearTimeout(n),t()},e)})}function ne(t,e,n){if(t.environment&&t.environment!==e)throw new s("VERSION_CONFLICT",`The ${t.environment} builder already runs on this page; ${e} cannot load beside it.`,{fatal:!0});if(n!=="latest"&&t.version&&t.version!==n)throw new s("VERSION_CONFLICT",`Builder ${t.version} already runs on this page; ${n} cannot load beside it.`,{fatal:!0})}async function oe(t,e){if(!pe())throw new s("NOT_IN_BROWSER","The 3DN builder can only load in a browser.");let n=fe();if(n.loading){await n.loading,ne(n,t,e);return}if(customElements.get(v))return;let r=re[t];if(!r)throw a(`The "${t}" environment is not available in this SDK release.`);n.environment=t,n.loading=(async()=>{let i=await ge(r,e);n.version=i;let o=await me(`${r}/builder-${i}.js`);try{await ve()}catch(d){throw o.remove(),d}})();try{await n.loading}catch(i){throw n.loading=void 0,n.environment=void 0,n.version=void 0,i instanceof s?i:p("The 3DN builder failed to load.",i)}ne(n,t,e)}function D(){let t,e,n=new Promise((r,i)=>{t=r,e=i});return n.catch(()=>{}),{promise:n,resolve:t,reject:e}}var R=class{constructor(e,n){this.onExpire=n;this.startedAt=0;this.remaining=e}start(){this.handle===void 0&&(this.startedAt=Date.now(),this.handle=setTimeout(this.onExpire,this.remaining))}pause(){this.handle!==void 0&&(clearTimeout(this.handle),this.handle=void 0,this.remaining=Math.max(0,this.remaining-(Date.now()-this.startedAt)))}stop(){this.handle!==void 0&&clearTimeout(this.handle),this.handle=void 0}},E=class t{constructor(e,n,r){this.ctx=e;this.options=n;this.epoch=0;this.destroyed=!1;this.loginPending=!1;this.exports=new Set;this.listeners=[];this.currentProductId=r.productId,this.element=this.createElement(r)}static async mount(e,n){let r;try{r=await e.config.session.get({hostProductId:n.hostProductId})}catch(d){throw new s("SESSION_FAILED","The builder session could not be loaded.",{retryable:!0,cause:d})}let i=w(r),o=new t(e,n,i);return n.target.appendChild(o.element),e.log("mounted",{hostProductId:n.hostProductId,productId:i.productId}),o}get productId(){return this.currentProductId}get hostProductId(){return this.options.hostProductId}createElement(e){let n=document.createElement(v);return this.attachListeners(n),n.setAttribute("product",e.productId),n.setAttribute("license-token",e.licenseToken),e.viewToken&&n.setAttribute("view-token",e.viewToken),e.token&&e.refreshToken&&L(n,{token:e.token,refreshToken:e.refreshToken}),this.options.designId&&n.setAttribute("design-id",this.options.designId),this.options.mode==="viewer"&&n.setAttribute("display-only","true"),this.options.externalExport&&n.setAttribute("external-export","true"),this.options.observabilityContext&&n.setAttribute("observability-context",this.options.observabilityContext),n.style.display="block",n.style.width="100%",n.style.height="100%",n}attachListeners(e){let n=(r,i)=>{let o=d=>{d.target!==e||this.destroyed||(this.ctx.log(r,d.detail),i(d.detail))};e.addEventListener(r,o),this.listeners.push([r,o])};n(A,r=>{var i;this.answerLogin((i=r==null?void 0:r.action)!=null?i:"save")}),n(H,()=>{this.ctx.refreshLicense()}),n(N,r=>this.onLicenseError(r)),n(M,r=>{this.onDesignSaved(r)}),n(V,r=>this.finishSave(z(r))),n(F,r=>this.onImagesSettled(r,!0)),n(U,r=>this.onImagesSettled(r,!1)),n(C,r=>{this.onProductCloned(r)}),n(q,r=>{var i,o;return(o=(i=this.ctx.config).onCloseRequested)==null?void 0:o.call(i,r)}),n(P,r=>{var i,o;return(o=(i=this.ctx.config).onStatusChange)==null?void 0:o.call(i,r)}),this.options.externalExport&&n(_,r=>{this.answerExport(r)})}open(){this.assertUsable(),k(this.element)}save(){if(this.destroyed)return Promise.reject(g());if(this.fatal)return Promise.reject(this.fatal);if(this.saveRun)return this.saveRun.result.promise;let e={result:D(),timer:new R(this.ctx.config.saveTimeoutMs,()=>this.finishSave(new s("SAVE_TIMEOUT","Saving the design took too long. Try again.",{retryable:!0})))};return this.saveRun=e,this.loginPending||e.timer.start(),$(this.element),e.result.promise}async refreshSession(){this.assertUsable();let e=this.epoch,n;try{n=await this.ctx.config.session.get({hostProductId:this.options.hostProductId})}catch(i){throw new s("SESSION_FAILED","The builder session could not be refreshed.",{retryable:!0,cause:i})}let r=w(n);e!==this.epoch||this.destroyed||(this.applyLicense(r.licenseToken),r.viewToken&&this.element.setAttribute("view-token",r.viewToken),r.token&&r.refreshToken&&L(this.element,{token:r.token,refreshToken:r.refreshToken}),this.fatal=void 0)}logout(){this.destroyed||(this.epoch++,this.element.removeAttribute("bearer-token"),this.element.removeAttribute("refresh-token"),this.loginPending&&(this.loginPending=!1,this.finishSave(new s("LOGIN_CANCELLED","The shopper logged out before the design was saved.",{retryable:!0}))))}destroy(){var e,n;if(!this.destroyed){this.epoch++,this.finishSave(g()),(e=this.cloneWait)==null||e.reject(g()),this.cloneWait=void 0,(n=this.imagesWait)==null||n.resolve(!1),this.imagesWait=void 0;for(let r of this.exports)r.abort();this.destroyed=!0;for(let[r,i]of this.listeners)this.element.removeEventListener(r,i);this.listeners.length=0,this.element.remove(),this.ctx.onDestroyed(this),this.ctx.log("destroyed")}}setObservabilityContext(e){this.assertUsable(),this.element.setAttribute("observability-context",S(e,"setObservabilityContext()"))}applyLicense(e){this.destroyed||this.element.setAttribute("license-token",e)}get isDestroyed(){return this.destroyed}async answerLogin(e){if(this.loginPending)return;let n=this.ctx.config.login;if(!n){m(this.element,{action:e});return}let r=this.epoch,i=()=>r!==this.epoch||this.destroyed;this.setLoginPending(!0);let o=!1;try{for(;;){let d;try{d=Z(await n.getTokens())}catch(l){if(i())return;this.ctx.log("login.getTokens() failed",{cause:String(l)});let c=n.onMintFailed?await n.onMintFailed():"cancel";if(i())return;if(c==="retry")continue;m(this.element,{action:e});return}if(i())return;if(d){L(this.element,d);return}let h=await n.onRequired(e);if(i())return;if(h!=="logged-in"){if(h==="redirected"){o=!0;return}m(this.element,{action:e});return}}}finally{!o&&r===this.epoch&&this.setLoginPending(!1)}}setLoginPending(e){this.loginPending=e,this.saveRun&&(e?this.saveRun.timer.pause():this.saveRun.timer.start())}onLicenseError(e){var r,i;let n=W(e.code);this.fatal=n,this.finishSave(n),(i=(r=this.ctx.config).onLicenseError)==null||i.call(r,n)}async onDesignSaved(e){var o;e.productId!==this.currentProductId&&!this.cloneWait&&(this.cloneWait=D()),(o=this.imagesWait)==null||o.resolve(!1);let n=this.imagesWait=D();try{this.cloneWait&&await this.cloneWait.promise,this.unrecordedLink&&await this.recordLink(this.unrecordedLink)}catch(d){this.finishSave(d instanceof s?d:new s("SAVE_FAILED","The design was saved but could not be linked to this shopper.",{retryable:!0,reason:"customization",cause:d}));return}if(this.destroyed)return;let r={productId:e.productId,version:e.version};this.notify("onSaved",r);let i=await n.promise;this.destroyed||(i&&this.notify("onImagesSaved",r),this.finishSave(void 0,r))}onImagesSettled(e,n){let r=this.imagesWait;if(r){this.imagesWait=void 0,r.resolve(n);return}n&&this.notify("onImagesSaved",{productId:e.productId,version:e.version})}notify(e,n){var r,i;try{(i=(r=this.ctx.config)[e])==null||i.call(r,{hostProductId:this.options.hostProductId,...n})}catch(o){this.ctx.log(`${e} threw`,{cause:String(o)})}}async onProductCloned(e){let n={hostProductId:this.options.hostProductId,sourceProductId:e.originalProductId,savedProductId:e.newProductId,asCopy:e.asCopy===!0};this.currentProductId=e.newProductId;let r=this.cloneWait;this.cloneWait=void 0;try{await this.recordLink(n),r==null||r.resolve()}catch(i){r==null||r.reject(i)}}async recordLink(e){let n=this.ctx.config.customization;if(n)try{await n.record(e),this.unrecordedLink===e&&(this.unrecordedLink=void 0)}catch(r){throw this.unrecordedLink=e,new s("SAVE_FAILED","The design was saved but could not be linked to this shopper. Try saving again.",{retryable:!0,reason:"customization",cause:r})}}finishSave(e,n){let r=this.saveRun;r&&(this.saveRun=void 0,r.timer.stop(),e?r.result.reject(e):n&&r.result.resolve(n))}async answerExport(e){var h;let n=this.options.externalExport;if(!n)return;let r=new AbortController;this.exports.add(r);let i=!1,o=setTimeout(()=>{i=!0,r.abort()},(h=n.timeoutMs)!=null?h:G),d=new Promise((l,c)=>r.signal.addEventListener("abort",()=>c(new Error("aborted"))));try{let l=await Promise.race([n.run(e,r.signal),d]);O(this.element,{files:l})}catch(l){let c=i?"EXPORT_TIMEOUT":"EXPORT_FAILED";this.ctx.log("export failed",{code:c,cause:String(l)}),this.replyExportFailure(c)}finally{clearTimeout(o),this.exports.delete(r)}}replyExportFailure(e){let n={files:[],error:{code:e}};this.element.dispatchEvent(new CustomEvent(B,{bubbles:!1,composed:!1,detail:n}))}assertUsable(){if(this.destroyed)throw g()}};function L(t,e){t.setAttribute("refresh-token",e.refreshToken),t.setAttribute("bearer-token",e.token)}var x=class{constructor(e){this.config=e;this.instances=[];this.log=ee(e.debug),this.context={config:e,log:this.log,refreshLicense:()=>this.refreshLicense().catch(()=>{}),onDestroyed:n=>{let r=this.instances.indexOf(n);r>=0&&this.instances.splice(r,1)}}}get current(){return this.instances[this.instances.length-1]}load(){return oe(this.config.environment,this.config.version)}async mount(e){let n=Q(e);await this.load();let r=await E.mount(this.context,n);return this.instances.push(r),r}open(){this.requireCurrent().open()}save(){let e=this.current;return e?e.save():Promise.reject(this.notMounted())}refreshSession(){let e=this.current;return e?e.refreshSession():Promise.reject(this.notMounted())}refreshLicense(){return this.licenseRefresh||(this.licenseRefresh=(async()=>{try{let e=await this.config.session.refreshLicense(),n=e==null?void 0:e.licenseToken;if(typeof n!="string"||n==="")throw new s("SESSION_FAILED","session.refreshLicense() must resolve { licenseToken }.");for(let r of this.instances)r.applyLicense(n);this.log("license refreshed")}catch(e){throw this.log("license refresh failed",{cause:String(e)}),e instanceof s?e:new s("SESSION_FAILED","A fresh license token could not be fetched.",{retryable:!0,cause:e})}finally{this.licenseRefresh=void 0}})()),this.licenseRefresh}logout(){for(let e of this.instances)e.logout();this.log("logged out")}destroy(){for(let e of[...this.instances])e.destroy()}requireCurrent(){let e=this.current;if(!e)throw this.notMounted();return e}notMounted(){return a("No builder is mounted. Call mount() first.")}};function Ee(t){let e=J(t);return ie(e.environment),new x(e)}var y="tdn_resume",ye="tdn-draft-index";function Te(){let t=new URL(window.location.href);return t.searchParams.set(y,"1"),t.pathname+t.search+t.hash}function be(t){return/^\/(?![/\\])/.test(t)}function Se(){if(typeof window=="undefined")return!1;let t=new URL(window.location.href);return t.searchParams.get(y)!=="1"?!1:(t.searchParams.delete(y),window.history.replaceState(window.history.state,"",t.pathname+t.search+t.hash),!0)}function we(t){var e;if(typeof localStorage=="undefined")return!1;try{let n=JSON.parse((e=localStorage.getItem(ye))!=null?e:"{}");return Object.values(n).some(r=>(r==null?void 0:r.productId)===t)}catch{return!1}}export{s as BuilderError,y as RESUME_PARAM,Se as consumeResumeMarker,Ee as createBuilder,we as hasDraft,be as isSafeReturnPath,Te as loginReturnPath};
|
|
1
|
+
import{b as k,c as P,d as _,e as B,f as O,g as C,h as A,j as m,k as N,l as M,m as F,n as U,o as V,p as H,r as $,s as q}from"./chunks/chunk-FRDLXZMM.js";var s=class t extends Error{constructor(e,n,r={}){var i,o;super(n),this.name="BuilderError",this.code=e,this.retryable=(i=r.retryable)!=null?i:!1,this.fatal=(o=r.fatal)!=null?o:!1,this.reason=r.reason,this.cause=r.cause,Object.setPrototypeOf(this,t.prototype)}},j={conflict:"Someone else saved this design first.",forbidden:"This shopper may not save this design.",unauthorized:"The shopper could not be signed in to save.","login-cancelled":"The design was not saved because the shopper did not log in.","installation-inactive":"This site's 3DN installation is not active.",license:"This page is not licensed to use the 3DN builder.",outdated:"The builder on this page is out of date. Reload the page to save the design.",error:"The design could not be saved. Check the connection and try again."};function z(t){var n;let e=(n=j[t.reason])!=null?n:j.error;return t.reason==="login-cancelled"?new s("LOGIN_CANCELLED",e,{retryable:!0,reason:t.reason,cause:t}):new s("SAVE_FAILED",e,{retryable:t.retryable,reason:t.reason,cause:t})}function W(t){let e=t==="ORIGIN_NOT_ALLOWED"?"This website is not on the 3DN license. Add its hostname to the license.":"The 3DN license token was rejected. The license may be suspended, revoked or expired.";return new s("LICENSE_ERROR",e,{fatal:!0,reason:t,cause:{code:t}})}function g(){return new s("DESTROYED","The builder was destroyed.")}function a(t){return new s("CONFIG_INVALID",t,{fatal:!0})}var se=45e3,G=12e4,ae=new Set(["environment","version","saveTimeoutMs","session","login","customization","onSaved","onImagesSaved","onLicenseError","onCloseRequested","onStatusChange","debug"]),de=new Set(["target","hostProductId","mode","designId","externalExport","observabilityContext"]),ue=/^\d+\.\d+\.\d+$/,le=/^tdn_inst_/;function b(t){return ue.test(t)}function u(t){return typeof t=="object"&&t!==null}function f(t){return typeof t=="function"}function X(t){return typeof t=="number"&&Number.isFinite(t)&&t>0}function K(t,e=0){return typeof t=="string"?le.test(t):!u(t)||e>3?!1:Object.values(t).some(n=>K(n,e+1))}function Y(t,e,n){for(let r of Object.keys(t))if(!e.has(r))throw/url|src|host|origin/i.test(r)?a(`${n}: "${r}" is not allowed. The SDK decides where the builder loads from; choose an environment and a version instead.`):a(`${n}: unknown option "${r}".`)}function J(t){var h,l,c;if(!u(t))throw a("createBuilder() needs a configuration object.");if(K(t))throw a("createBuilder(): an installation key was passed to the browser. Installation keys stay on your server; pass a session callback instead.");Y(t,ae,"createBuilder()");let e=(h=t.environment)!=null?h:"production";if(e!=="production"&&e!=="staging")throw a(`createBuilder(): environment must be "production" or "staging", got ${JSON.stringify(e)}.`);let n=(l=t.version)!=null?l:"latest";if(typeof n!="string"||n!=="latest"&&!b(n))throw a(`createBuilder(): version must be "latest" or an exact release such as "1.2.3", got ${JSON.stringify(n)}.`);let r=(c=t.saveTimeoutMs)!=null?c:se;if(!X(r))throw a("createBuilder(): saveTimeoutMs must be a positive number of milliseconds.");let i=t.session;if(!u(i)||!f(i.get)||!f(i.refreshLicense))throw a("createBuilder(): session.get() and session.refreshLicense() are required. They call your session endpoint.");let o=t.login;if(o!==void 0){if(!u(o)||!f(o.getTokens)||!f(o.onRequired))throw a("createBuilder(): login needs getTokens() (your login-token endpoint) and onRequired() (your login UI).");if(o.onMintFailed!==void 0&&!f(o.onMintFailed))throw a("createBuilder(): login.onMintFailed must be a function.")}let d=t.customization;if(d!==void 0&&(!u(d)||!f(d.record)))throw a("createBuilder(): customization.record() is required when customization is set. It calls your customization endpoint.");for(let T of["onSaved","onImagesSaved","onLicenseError","onCloseRequested","onStatusChange"])if(t[T]!==void 0&&!f(t[T]))throw a(`createBuilder(): ${T} must be a function.`);return{...t,environment:e,version:n,saveTimeoutMs:r}}function Q(t){var d;if(!u(t))throw a("mount() needs an options object.");Y(t,de,"mount()");let e=t.target;if(typeof HTMLElement=="undefined"||!(e instanceof HTMLElement))throw a("mount(): target must be an HTMLElement on the page.");let n=t.hostProductId;if(typeof n!="string"||n.trim()==="")throw a("mount(): hostProductId must be your own, non-empty product reference.");let r=(d=t.mode)!=null?d:"designer";if(r!=="designer"&&r!=="viewer")throw a(`mount(): mode must be "designer" or "viewer", got ${JSON.stringify(r)}.`);let i=t.designId;if(i!==void 0&&(typeof i!="string"||i===""))throw a("mount(): designId must be a non-empty string.");t.observabilityContext!==void 0&&S(t.observabilityContext,"mount()");let o=t.externalExport;if(o!==void 0){if(!u(o)||!f(o.run))throw a("mount(): externalExport.run() is required when externalExport is set.");if(o.timeoutMs!==void 0&&!X(o.timeoutMs))throw a("mount(): externalExport.timeoutMs must be a positive number of milliseconds.")}return t}function S(t,e){let n;try{n=typeof t=="string"?JSON.parse(t):void 0}catch{n=void 0}if(!u(n)||Array.isArray(n))throw a(`${e}: observabilityContext must be a JSON object string.`);return t}function w(t){let e=i=>new s("SESSION_FAILED",`session.get() returned an invalid session: ${i}.`,{retryable:!1});if(!u(t))throw e("expected an object");if(typeof t.licenseToken!="string"||t.licenseToken==="")throw e("licenseToken is missing");if(typeof t.productId!="string"||t.productId==="")throw e("productId is missing");if(t.viewToken!=null&&typeof t.viewToken!="string")throw e("viewToken must be a string");let n=t.token!=null,r=t.refreshToken!=null;if(n!==r)throw e("token and refreshToken must be returned together");if(n&&(typeof t.token!="string"||typeof t.refreshToken!="string"))throw e("token and refreshToken must be strings");return t}function Z(t){if(t===null)return null;if(!u(t)||typeof t.token!="string"||t.token===""||typeof t.refreshToken!="string"||t.refreshToken==="")throw new s("SESSION_FAILED","login.getTokens() must resolve { token, refreshToken } or null.");return t}var ce=/token|secret|key|authorization/i;function I(t,e=0){if(t===null||typeof t!="object"||e>4)return t;if(Array.isArray(t))return t.map(r=>I(r,e+1));let n={};for(let[r,i]of Object.entries(t))n[r]=ce.test(r)&&i!=null?"[redacted]":I(i,e+1);return n}function ee(t){return t?(e,n)=>{n===void 0?console.debug(`[3DN] ${e}`):console.debug(`[3DN] ${e}`,I(n))}:()=>{}}var v="threed-builder",re={production:"https://3d-builder.3dn.app",staging:void 0},he=3e4,te=Symbol.for("@3dn-builder/builder/loader");function fe(){let t=globalThis,e=t[te];return e||(e={},t[te]=e),e}function pe(){return typeof window!="undefined"&&typeof document!="undefined"&&typeof customElements!="undefined"}function ie(t){if(!re[t])throw a(`createBuilder(): the "${t}" environment is not available in this SDK release.`)}function p(t,e){return new s("LOAD_FAILED",t,{retryable:!0,cause:e})}async function ge(t,e){if(e!=="latest")return e;let n;try{n=await fetch(`${t}/version-info.json`,{cache:"no-store",credentials:"omit"})}catch(o){throw p("The 3DN builder could not be reached.",o)}if(!n.ok)throw p(`The 3DN builder release could not be resolved (HTTP ${n.status}).`);let r=await n.json().catch(()=>null),i=r==null?void 0:r.version;if(typeof i!="string"||!b(i))throw p("The 3DN builder release information was not valid.");return i}function me(t){return new Promise((e,n)=>{let r=document.createElement("script");r.type="module",r.src=t,r.crossOrigin="anonymous",r.onload=()=>e(r),r.onerror=()=>{r.remove(),n(p("The 3DN builder failed to load."))},document.head.appendChild(r)})}function ve(){return new Promise((t,e)=>{let n=setTimeout(()=>e(p("The 3DN builder loaded but did not start.")),he);customElements.whenDefined(v).then(()=>{clearTimeout(n),t()},e)})}function ne(t,e,n){if(t.environment&&t.environment!==e)throw new s("VERSION_CONFLICT",`The ${t.environment} builder already runs on this page; ${e} cannot load beside it.`,{fatal:!0});if(n!=="latest"&&t.version&&t.version!==n)throw new s("VERSION_CONFLICT",`Builder ${t.version} already runs on this page; ${n} cannot load beside it.`,{fatal:!0})}async function oe(t,e){if(!pe())throw new s("NOT_IN_BROWSER","The 3DN builder can only load in a browser.");let n=fe();if(n.loading){await n.loading,ne(n,t,e);return}if(customElements.get(v))return;let r=re[t];if(!r)throw a(`The "${t}" environment is not available in this SDK release.`);n.environment=t,n.loading=(async()=>{let i=await ge(r,e);n.version=i;let o=await me(`${r}/builder-${i}.js`);try{await ve()}catch(d){throw o.remove(),d}})();try{await n.loading}catch(i){throw n.loading=void 0,n.environment=void 0,n.version=void 0,i instanceof s?i:p("The 3DN builder failed to load.",i)}ne(n,t,e)}function D(){let t,e,n=new Promise((r,i)=>{t=r,e=i});return n.catch(()=>{}),{promise:n,resolve:t,reject:e}}var R=class{constructor(e,n){this.onExpire=n;this.startedAt=0;this.remaining=e}start(){this.handle===void 0&&(this.startedAt=Date.now(),this.handle=setTimeout(this.onExpire,this.remaining))}pause(){this.handle!==void 0&&(clearTimeout(this.handle),this.handle=void 0,this.remaining=Math.max(0,this.remaining-(Date.now()-this.startedAt)))}stop(){this.handle!==void 0&&clearTimeout(this.handle),this.handle=void 0}},E=class t{constructor(e,n,r){this.ctx=e;this.options=n;this.epoch=0;this.destroyed=!1;this.loginPending=!1;this.exports=new Set;this.listeners=[];this.currentProductId=r.productId,this.element=this.createElement(r)}static async mount(e,n){let r;try{r=await e.config.session.get({hostProductId:n.hostProductId})}catch(d){throw new s("SESSION_FAILED","The builder session could not be loaded.",{retryable:!0,cause:d})}let i=w(r),o=new t(e,n,i);return n.target.appendChild(o.element),e.log("mounted",{hostProductId:n.hostProductId,productId:i.productId}),o}get productId(){return this.currentProductId}get hostProductId(){return this.options.hostProductId}createElement(e){let n=document.createElement(v);return this.attachListeners(n),n.setAttribute("product",e.productId),n.setAttribute("license-token",e.licenseToken),e.viewToken&&n.setAttribute("view-token",e.viewToken),e.token&&e.refreshToken&&L(n,{token:e.token,refreshToken:e.refreshToken}),this.options.designId&&n.setAttribute("design-id",this.options.designId),this.options.mode==="viewer"&&n.setAttribute("display-only","true"),this.options.externalExport&&n.setAttribute("external-export","true"),this.options.observabilityContext&&n.setAttribute("observability-context",this.options.observabilityContext),n.style.display="block",n.style.width="100%",n.style.height="100%",n}attachListeners(e){let n=(r,i)=>{let o=d=>{d.target!==e||this.destroyed||(this.ctx.log(r,d.detail),i(d.detail))};e.addEventListener(r,o),this.listeners.push([r,o])};n(A,r=>{var i;this.answerLogin((i=r==null?void 0:r.action)!=null?i:"save")}),n(H,()=>{this.ctx.refreshLicense()}),n(N,r=>this.onLicenseError(r)),n(M,r=>{this.onDesignSaved(r)}),n(V,r=>this.finishSave(z(r))),n(F,r=>this.onImagesSettled(r,!0)),n(U,r=>this.onImagesSettled(r,!1)),n(C,r=>{this.onProductCloned(r)}),n(q,r=>{var i,o;return(o=(i=this.ctx.config).onCloseRequested)==null?void 0:o.call(i,r)}),n(P,r=>{var i,o;return(o=(i=this.ctx.config).onStatusChange)==null?void 0:o.call(i,r)}),this.options.externalExport&&n(_,r=>{this.answerExport(r)})}open(){this.assertUsable(),k(this.element)}save(){if(this.destroyed)return Promise.reject(g());if(this.fatal)return Promise.reject(this.fatal);if(this.saveRun)return this.saveRun.result.promise;let e={result:D(),timer:new R(this.ctx.config.saveTimeoutMs,()=>this.finishSave(new s("SAVE_TIMEOUT","Saving the design took too long. Try again.",{retryable:!0})))};return this.saveRun=e,this.loginPending||e.timer.start(),$(this.element),e.result.promise}async refreshSession(){this.assertUsable();let e=this.epoch,n;try{n=await this.ctx.config.session.get({hostProductId:this.options.hostProductId})}catch(i){throw new s("SESSION_FAILED","The builder session could not be refreshed.",{retryable:!0,cause:i})}let r=w(n);e!==this.epoch||this.destroyed||(this.applyLicense(r.licenseToken),r.viewToken&&this.element.setAttribute("view-token",r.viewToken),r.token&&r.refreshToken&&L(this.element,{token:r.token,refreshToken:r.refreshToken}),this.fatal=void 0)}logout(){this.destroyed||(this.epoch++,this.element.removeAttribute("bearer-token"),this.element.removeAttribute("refresh-token"),this.loginPending&&(this.loginPending=!1,this.finishSave(new s("LOGIN_CANCELLED","The shopper logged out before the design was saved.",{retryable:!0}))))}destroy(){var e,n;if(!this.destroyed){this.epoch++,this.finishSave(g()),(e=this.cloneWait)==null||e.reject(g()),this.cloneWait=void 0,(n=this.imagesWait)==null||n.resolve(!1),this.imagesWait=void 0;for(let r of this.exports)r.abort();this.destroyed=!0;for(let[r,i]of this.listeners)this.element.removeEventListener(r,i);this.listeners.length=0,this.element.remove(),this.ctx.onDestroyed(this),this.ctx.log("destroyed")}}setObservabilityContext(e){this.assertUsable(),this.element.setAttribute("observability-context",S(e,"setObservabilityContext()"))}applyLicense(e){this.destroyed||this.element.setAttribute("license-token",e)}get isDestroyed(){return this.destroyed}async answerLogin(e){if(this.loginPending)return;let n=this.ctx.config.login;if(!n){m(this.element,{action:e});return}let r=this.epoch,i=()=>r!==this.epoch||this.destroyed;this.setLoginPending(!0);let o=!1;try{for(;;){let d;try{d=Z(await n.getTokens())}catch(l){if(i())return;this.ctx.log("login.getTokens() failed",{cause:String(l)});let c=n.onMintFailed?await n.onMintFailed():"cancel";if(i())return;if(c==="retry")continue;m(this.element,{action:e});return}if(i())return;if(d){L(this.element,d);return}let h=await n.onRequired(e);if(i())return;if(h!=="logged-in"){if(h==="redirected"){o=!0;return}m(this.element,{action:e});return}}}finally{!o&&r===this.epoch&&this.setLoginPending(!1)}}setLoginPending(e){this.loginPending=e,this.saveRun&&(e?this.saveRun.timer.pause():this.saveRun.timer.start())}onLicenseError(e){var r,i;let n=W(e.code);this.fatal=n,this.finishSave(n),(i=(r=this.ctx.config).onLicenseError)==null||i.call(r,n)}async onDesignSaved(e){var o;e.productId!==this.currentProductId&&!this.cloneWait&&(this.cloneWait=D()),(o=this.imagesWait)==null||o.resolve(!1);let n=this.imagesWait=D();try{this.cloneWait&&await this.cloneWait.promise,this.unrecordedLink&&await this.recordLink(this.unrecordedLink)}catch(d){this.finishSave(d instanceof s?d:new s("SAVE_FAILED","The design was saved but could not be linked to this shopper.",{retryable:!0,reason:"customization",cause:d}));return}if(this.destroyed)return;let r={productId:e.productId,version:e.version};this.notify("onSaved",r);let i=await n.promise;this.destroyed||(i&&this.notify("onImagesSaved",r),this.finishSave(void 0,r))}onImagesSettled(e,n){let r=this.imagesWait;if(r){this.imagesWait=void 0,r.resolve(n);return}n&&this.notify("onImagesSaved",{productId:e.productId,version:e.version})}notify(e,n){var r,i;try{(i=(r=this.ctx.config)[e])==null||i.call(r,{hostProductId:this.options.hostProductId,...n})}catch(o){this.ctx.log(`${e} threw`,{cause:String(o)})}}async onProductCloned(e){let n={hostProductId:this.options.hostProductId,sourceProductId:e.originalProductId,savedProductId:e.newProductId,asCopy:e.asCopy===!0};this.currentProductId=e.newProductId;let r=this.cloneWait;this.cloneWait=void 0;try{await this.recordLink(n),r==null||r.resolve()}catch(i){r==null||r.reject(i)}}async recordLink(e){let n=this.ctx.config.customization;if(n)try{await n.record(e),this.unrecordedLink===e&&(this.unrecordedLink=void 0)}catch(r){throw this.unrecordedLink=e,new s("SAVE_FAILED","The design was saved but could not be linked to this shopper. Try saving again.",{retryable:!0,reason:"customization",cause:r})}}finishSave(e,n){let r=this.saveRun;r&&(this.saveRun=void 0,r.timer.stop(),e?r.result.reject(e):n&&r.result.resolve(n))}async answerExport(e){var h;let n=this.options.externalExport;if(!n)return;let r=new AbortController;this.exports.add(r);let i=!1,o=setTimeout(()=>{i=!0,r.abort()},(h=n.timeoutMs)!=null?h:G),d=new Promise((l,c)=>r.signal.addEventListener("abort",()=>c(new Error("aborted"))));try{let l=await Promise.race([n.run(e,r.signal),d]);O(this.element,{files:l})}catch(l){let c=i?"EXPORT_TIMEOUT":"EXPORT_FAILED";this.ctx.log("export failed",{code:c,cause:String(l)}),this.replyExportFailure(c)}finally{clearTimeout(o),this.exports.delete(r)}}replyExportFailure(e){let n={files:[],error:{code:e}};this.element.dispatchEvent(new CustomEvent(B,{bubbles:!1,composed:!1,detail:n}))}assertUsable(){if(this.destroyed)throw g()}};function L(t,e){t.setAttribute("refresh-token",e.refreshToken),t.setAttribute("bearer-token",e.token)}var x=class{constructor(e){this.config=e;this.instances=[];this.log=ee(e.debug),this.context={config:e,log:this.log,refreshLicense:()=>this.refreshLicense().catch(()=>{}),onDestroyed:n=>{let r=this.instances.indexOf(n);r>=0&&this.instances.splice(r,1)}}}get current(){return this.instances[this.instances.length-1]}load(){return oe(this.config.environment,this.config.version)}async mount(e){let n=Q(e);await this.load();let r=await E.mount(this.context,n);return this.instances.push(r),r}open(){this.requireCurrent().open()}save(){let e=this.current;return e?e.save():Promise.reject(this.notMounted())}refreshSession(){let e=this.current;return e?e.refreshSession():Promise.reject(this.notMounted())}refreshLicense(){return this.licenseRefresh||(this.licenseRefresh=(async()=>{try{let e=await this.config.session.refreshLicense(),n=e==null?void 0:e.licenseToken;if(typeof n!="string"||n==="")throw new s("SESSION_FAILED","session.refreshLicense() must resolve { licenseToken }.");for(let r of this.instances)r.applyLicense(n);this.log("license refreshed")}catch(e){throw this.log("license refresh failed",{cause:String(e)}),e instanceof s?e:new s("SESSION_FAILED","A fresh license token could not be fetched.",{retryable:!0,cause:e})}finally{this.licenseRefresh=void 0}})()),this.licenseRefresh}logout(){for(let e of this.instances)e.logout();this.log("logged out")}destroy(){for(let e of[...this.instances])e.destroy()}requireCurrent(){let e=this.current;if(!e)throw this.notMounted();return e}notMounted(){return a("No builder is mounted. Call mount() first.")}};function Ee(t){let e=J(t);return ie(e.environment),new x(e)}var y="tdn_resume",ye="tdn-draft-index";function Te(){let t=new URL(window.location.href);return t.searchParams.set(y,"1"),t.pathname+t.search+t.hash}function be(t){return/^\/(?![/\\])/.test(t)}function Se(){if(typeof window=="undefined")return!1;let t=new URL(window.location.href);return t.searchParams.get(y)!=="1"?!1:(t.searchParams.delete(y),window.history.replaceState(window.history.state,"",t.pathname+t.search+t.hash),!0)}function we(t){var e;if(typeof localStorage=="undefined")return!1;try{let n=JSON.parse((e=localStorage.getItem(ye))!=null?e:"{}");return Object.values(n).some(r=>(r==null?void 0:r.productId)===t)}catch{return!1}}export{s as BuilderError,y as RESUME_PARAM,Se as consumeResumeMarker,Ee as createBuilder,we as hasDraft,be as isSafeReturnPath,Te as loginReturnPath};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
package/testing.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a,c as m,d as E,e as u,g as R,h as c,i as l,k as _,l as r,m as v,n as D,o as T,p as L,q as I,s as p}from"./chunks/chunk-
|
|
1
|
+
import{a,c as m,d as E,e as u,g as R,h as c,i as l,k as _,l as r,m as v,n as D,o as T,p as L,q as I,s as p}from"./chunks/chunk-FRDLXZMM.js";var s="threed-builder",S=[a,I,l,u],h=new Set([E]);function A(){class o extends HTMLElement{constructor(){super();this.received=[];this.attributesAtConnect={};for(let e of S)this.addEventListener(e,t=>{t instanceof CustomEvent&&t.fromFake||this.received.push({type:e,detail:t.detail})})}connectedCallback(){this.attributesAtConnect=Object.fromEntries(Array.from(this.attributes,e=>[e.name,e.value])),this.emit(m,{status:"idle"})}emit(e,t){let i=!h.has(e),n=new CustomEvent(e,{bubbles:i,composed:i,detail:t});n.fromFake=!0,this.dispatchEvent(n)}emitSaved(e=1){var t;this.emit(r,{productId:(t=this.getAttribute("product"))!=null?t:"",version:e}),this.emitImagesSaved(e)}async emitSavedAsClone(e,t=1,i=!1){var d;let n=(d=this.getAttribute("product"))!=null?d:"";this.emit(r,{productId:e,version:t}),this.emitImagesSaved(t,e),await new Promise(g=>setTimeout(g,0)),this.emit(R,{originalProductId:n,newProductId:e,name:"Customized",asCopy:i})}emitSaveFailed(e,t=!1){var i;this.emit(T,{productId:(i=this.getAttribute("product"))!=null?i:void 0,reason:e,retryable:t})}emitImagesSaved(e=1,t=(i=>(i=this.getAttribute("product"))!=null?i:"")()){this.emit(v,{productId:t,version:e})}emitImagesSaveFailed(e=1,t=(i=>(i=this.getAttribute("product"))!=null?i:"")()){this.emit(D,{productId:t,version:e})}emitLoginRequired(e="save"){this.emit(c,{action:e})}emitLicenseTokenRequired(){this.emit(L)}emitLicenseError(e){this.emit(_,{code:e})}emitCloseRequested(e){var t;this.emit(p,{productId:(t=this.getAttribute("product"))!=null?t:void 0,hasUnsavedChanges:e})}emitExportRequested(e){this.emit(E,e)}}customElements.define(s,o)}function f(){let o=customElements.get(s);if(o){if("prototype"in o&&"emitSaved"in o.prototype)return;throw new Error("A real <threed-builder> is already defined on this page.")}A()}function B(o=document){return Array.from(o.querySelectorAll(s))}export{B as findFakeBuilders,f as installFakeBuilder};
|
|
2
2
|
//# sourceMappingURL=testing.js.map
|