tango-app-ui-store-builder 1.2.22 → 1.2.23
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.
- package/esm2022/lib/components/onboard-store-plano/onboard-store-plano.component.mjs +90 -33
- package/fesm2022/{tango-app-ui-store-builder-dxf-source-viewer.component-KfK9D7DB.mjs → tango-app-ui-store-builder-dxf-source-viewer.component-BplzhK87.mjs} +2 -2
- package/fesm2022/{tango-app-ui-store-builder-dxf-source-viewer.component-KfK9D7DB.mjs.map → tango-app-ui-store-builder-dxf-source-viewer.component-BplzhK87.mjs.map} +1 -1
- package/fesm2022/{tango-app-ui-store-builder-tango-app-ui-store-builder-D1UC-7Qi.mjs → tango-app-ui-store-builder-tango-app-ui-store-builder-CXw6OsFW.mjs} +91 -34
- package/fesm2022/{tango-app-ui-store-builder-tango-app-ui-store-builder-D1UC-7Qi.mjs.map → tango-app-ui-store-builder-tango-app-ui-store-builder-CXw6OsFW.mjs.map} +1 -1
- package/fesm2022/tango-app-ui-store-builder.mjs +1 -1
- package/lib/components/manage-plano/onboard-table/onboard-table.component.d.ts +1 -1
- package/lib/components/onboard-store-plano/onboard-store-plano.component.d.ts +4 -2
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { CommonModule } from '@angular/common';
|
|
|
5
5
|
import * as FileSaver from 'file-saver';
|
|
6
6
|
import { firstValueFrom } from 'rxjs';
|
|
7
7
|
import * as i1 from '@ng-bootstrap/ng-bootstrap';
|
|
8
|
-
import { S as StoreBuilderService } from './tango-app-ui-store-builder-tango-app-ui-store-builder-
|
|
8
|
+
import { S as StoreBuilderService } from './tango-app-ui-store-builder-tango-app-ui-store-builder-CXw6OsFW.mjs';
|
|
9
9
|
import * as i4 from 'tango-app-ui-shared';
|
|
10
10
|
import '@angular/router';
|
|
11
11
|
import 'tango-app-ui-global';
|
|
@@ -233,4 +233,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
233
233
|
}] } });
|
|
234
234
|
|
|
235
235
|
export { DxfSourceViewerComponent };
|
|
236
|
-
//# sourceMappingURL=tango-app-ui-store-builder-dxf-source-viewer.component-
|
|
236
|
+
//# sourceMappingURL=tango-app-ui-store-builder-dxf-source-viewer.component-BplzhK87.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tango-app-ui-store-builder-dxf-source-viewer.component-KfK9D7DB.mjs","sources":["../../../projects/tango-store-builder/src/lib/components/dxf-source-viewer/dxf-source-viewer.component.ts","../../../projects/tango-store-builder/src/lib/components/dxf-source-viewer/dxf-source-viewer.component.html"],"sourcesContent":["import {\n AfterViewInit,\n Component,\n ElementRef,\n Input,\n OnDestroy,\n OnInit,\n ViewChild,\n} from \"@angular/core\";\nimport { CommonModule } from \"@angular/common\";\nimport { NgbActiveModal } from \"@ng-bootstrap/ng-bootstrap\";\nimport * as FileSaver from \"file-saver\";\nimport { firstValueFrom } from \"rxjs\";\nimport { ToastService } from \"tango-app-ui-shared\";\nimport { StoreBuilderService } from \"../../services/store-builder.service\";\n\n/**\n * Read-only viewer for a floor's SOURCE CAD/DXF files.\n *\n * Renders the floor's `cadFiles` (stored on the storelayout doc) as a timeline —\n * oldest first, latest last/highlighted — and shows the selected drawing in a 2D\n * `dxf-viewer` (WebGL, built-in pan/zoom + layer toggle). Each entry also has a\n * download button.\n *\n * `dxf-viewer` (and its heavy `three` peer) are dynamically imported here, and this\n * component itself is opened via a dynamic import (see CadSourceService), so the\n * whole thing lands in a lazy chunk and stays out of the main bundle.\n *\n * NOTE ON TEXT: dxf-viewer needs a font file to render TEXT/MTEXT entities. Drop a\n * .ttf into assets and list it in FONT_URLS to enable labels; with no font, geometry\n * renders but text is blank (no crash).\n */\n\n// Add a hosted TTF here (e.g. \"assets/fonts/Roboto-Regular.ttf\") to render DXF text.\nconst FONT_URLS: string[] = [];\n\ninterface CadFileEntry {\n key: string;\n fileName?: string;\n uploadedAt?: string | Date;\n inferred?: boolean;\n // resolved at runtime from listCadFiles:\n url?: string;\n size?: number;\n available?: boolean;\n}\n\n@Component({\n selector: \"lib-dxf-source-viewer\",\n standalone: true,\n imports: [CommonModule],\n templateUrl: \"./dxf-source-viewer.component.html\",\n styleUrl: \"./dxf-source-viewer.component.scss\",\n})\nexport class DxfSourceViewerComponent\n implements OnInit, AfterViewInit, OnDestroy\n{\n @Input() storeName = \"\";\n /** The floor's stored cadFiles array (oldest -> newest). */\n @Input() cadFiles: CadFileEntry[] = [];\n\n @ViewChild(\"viewerHost\") viewerHost!: ElementRef<HTMLDivElement>;\n\n entries: CadFileEntry[] = [];\n selected: CadFileEntry | null = null;\n\n isResolving = false;\n isRendering = false;\n loadError = \"\";\n\n layers: { name: string; displayName: string; visible: boolean }[] = [];\n\n private viewer: any;\n private viewReady = false;\n\n constructor(\n public activeModal: NgbActiveModal,\n private storeBuilderService: StoreBuilderService,\n private toastService: ToastService,\n ) {}\n\n async ngOnInit(): Promise<void> {\n await this.resolveUrls();\n }\n\n ngAfterViewInit(): void {\n this.viewReady = true;\n // If URLs resolved before the view was ready, render the default selection now.\n if (this.selected?.available) this.render(this.selected);\n }\n\n ngOnDestroy(): void {\n this.destroyViewer();\n }\n\n /** Match each stored cadFiles entry to a presigned URL from listCadFiles(storeName). */\n private async resolveUrls(): Promise<void> {\n this.isResolving = true;\n this.loadError = \"\";\n try {\n const res: any = await firstValueFrom(\n this.storeBuilderService.getCadFiles(this.storeName),\n );\n const s3Files: any[] = res?.data?.files ?? [];\n const byKey = new Map<string, any>(s3Files.map((f) => [f.key, f]));\n\n // Preserve stored order (oldest -> newest); render newest at top of timeline.\n this.entries = (this.cadFiles ?? []).map((c) => {\n const match = byKey.get(c.key);\n return {\n ...c,\n fileName: c.fileName ?? c.key?.split(\"/\").pop(),\n url: match?.url,\n size: match?.size,\n available: !!match?.url,\n };\n });\n\n // Default selection: latest available entry (last in stored order).\n const latestAvailable = [...this.entries]\n .reverse()\n .find((e) => e.available);\n if (latestAvailable) {\n this.select(latestAvailable);\n } else if (this.entries.length) {\n this.loadError =\n \"The source file(s) could not be found in storage. They may have been removed.\";\n }\n } catch (e: any) {\n this.loadError =\n e?.error?.error?.errMsg ??\n e?.error?.message ??\n \"Failed to load source CAD files.\";\n } finally {\n this.isResolving = false;\n }\n }\n\n /** Timeline shows newest first for scannability. */\n get timeline(): CadFileEntry[] {\n return [...this.entries].reverse();\n }\n\n isLatest(entry: CadFileEntry): boolean {\n return this.entries.length > 0 && entry === this.entries[this.entries.length - 1];\n }\n\n select(entry: CadFileEntry): void {\n if (!entry?.available || this.selected === entry) return;\n this.selected = entry;\n if (this.viewReady) this.render(entry);\n }\n\n private async render(entry: CadFileEntry): Promise<void> {\n if (!entry?.url) return;\n this.isRendering = true;\n this.loadError = \"\";\n this.layers = [];\n try {\n const { DxfViewer } = await import(\"dxf-viewer\");\n // three ships no type declarations; it's a runtime dep of dxf-viewer.\n // We only need Color here, so import it untyped rather than pulling @types/three.\n // @ts-ignore\n const THREE = await import(\"three\");\n\n if (!this.viewer) {\n this.viewer = new DxfViewer(this.viewerHost.nativeElement, {\n autoResize: true,\n // Correct entity colors for contrast against the (white) background so\n // light-coloured layers/text stay visible.\n colorCorrection: true,\n clearColor: new THREE.Color(0xffffff),\n clearAlpha: 1,\n });\n } else {\n this.viewer.Clear();\n }\n\n await this.viewer.Load({ url: entry.url, fonts: FONT_URLS });\n\n // Build the layer-toggle list.\n const layers = this.viewer.GetLayers?.() ?? [];\n this.layers = [...layers].map((l: any) => ({\n name: l.name,\n displayName: l.displayName ?? l.name,\n visible: true,\n }));\n } catch (e) {\n console.error(\"@@ ~ dxf render [ERR]:\", e);\n this.loadError = \"Failed to render this DXF drawing.\";\n } finally {\n this.isRendering = false;\n }\n }\n\n toggleLayer(layer: { name: string; visible: boolean }): void {\n layer.visible = !layer.visible;\n this.viewer?.ShowLayer?.(layer.name, layer.visible);\n }\n\n get allLayersVisible(): boolean {\n return this.layers.length > 0 && this.layers.every((l) => l.visible);\n }\n\n toggleAllLayers(event: Event): void {\n const checked = (event.target as HTMLInputElement).checked;\n for (const layer of this.layers) {\n layer.visible = checked;\n this.viewer?.ShowLayer?.(layer.name, checked);\n }\n }\n\n async download(entry: CadFileEntry, event: MouseEvent): Promise<void> {\n event.stopPropagation();\n if (!entry?.url) {\n this.toastService.getErrorToast(\"Download URL is not available.\");\n return;\n }\n try {\n const response = await fetch(entry.url);\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n const blob = await response.blob();\n FileSaver.saveAs(blob, entry.fileName ?? \"drawing.dxf\");\n } catch {\n this.toastService.getErrorToast(\n `Failed to download ${entry.fileName ?? \"file\"}.`,\n );\n }\n }\n\n private destroyViewer(): void {\n try {\n this.viewer?.Destroy?.();\n } catch {}\n this.viewer = null;\n }\n\n close(): void {\n this.activeModal.dismiss();\n }\n}\n","<div class=\"dxf-modal\">\n <div class=\"dxf-modal__header\">\n <div>\n <h3 class=\"dxf-modal__title\">Source CAD drawing</h3>\n <p class=\"dxf-modal__subtitle\">{{ storeName }}</p>\n </div>\n <button type=\"button\" class=\"dxf-icon-btn\" aria-label=\"Close\" (click)=\"close()\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"#475467\" stroke-width=\"1.6\" stroke-linecap=\"round\" />\n </svg>\n </button>\n </div>\n\n <div class=\"dxf-modal__body\">\n <!-- Timeline of source files -->\n <aside class=\"dxf-timeline\">\n <div class=\"dxf-timeline__head\">File history</div>\n\n <div *ngIf=\"isResolving\" class=\"dxf-muted\">Loading files…</div>\n\n <div *ngIf=\"!isResolving && !entries.length\" class=\"dxf-muted\">\n No source CAD files for this floor.\n </div>\n\n <ul class=\"dxf-timeline__list\" *ngIf=\"!isResolving && entries.length\">\n <li\n *ngFor=\"let entry of timeline\"\n class=\"dxf-timeline__item\"\n [class.is-selected]=\"entry === selected\"\n [class.is-disabled]=\"!entry.available\"\n (click)=\"select(entry)\"\n >\n <div class=\"dxf-timeline__dot\"></div>\n <div class=\"dxf-timeline__content\">\n <div class=\"dxf-timeline__name\" [title]=\"entry.fileName\">\n {{ entry.fileName }}\n <span *ngIf=\"isLatest(entry)\" class=\"dxf-badge dxf-badge--latest\">Latest</span>\n <span *ngIf=\"entry.inferred\" class=\"dxf-badge dxf-badge--inferred\" title=\"Matched from storage, not recorded at generation time\">Inferred</span>\n </div>\n <div class=\"dxf-timeline__meta\">\n <span *ngIf=\"entry.uploadedAt\">{{ entry.uploadedAt | date: \"medium\" }}</span>\n <span *ngIf=\"!entry.available\" class=\"dxf-unavailable\">Unavailable</span>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"dxf-icon-btn dxf-icon-btn--sm\"\n [disabled]=\"!entry.available\"\n aria-label=\"Download\"\n (click)=\"download(entry, $event)\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M8 2v8m0 0L5 7m3 3l3-3M3 13h10\" stroke=\"#475467\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </button>\n </li>\n </ul>\n </aside>\n\n <!-- Viewer -->\n <section class=\"dxf-viewer\">\n <div class=\"dxf-viewer__toolbar\" *ngIf=\"layers.length\">\n <div class=\"dxf-layers\">\n <span class=\"dxf-layers__label\">Layers</span>\n <label class=\"dxf-layers__item dxf-layers__item--all\">\n <input type=\"checkbox\" [checked]=\"allLayersVisible\" (change)=\"toggleAllLayers($event)\" />\n All\n </label>\n <label class=\"dxf-layers__item\" *ngFor=\"let layer of layers\">\n <input type=\"checkbox\" [checked]=\"layer.visible\" (change)=\"toggleLayer(layer)\" />\n {{ layer.displayName }}\n </label>\n </div>\n </div>\n\n <div class=\"dxf-viewer__stage\">\n <div #viewerHost class=\"dxf-viewer__host\"></div>\n\n <div *ngIf=\"isRendering\" class=\"dxf-overlay\">\n <svg class=\"dxf-spinner\" viewBox=\"0 0 50 50\">\n <circle class=\"path\" cx=\"25\" cy=\"25\" r=\"20\" fill=\"none\" stroke-width=\"4\"></circle>\n </svg>\n <span>Rendering drawing…</span>\n </div>\n\n <div *ngIf=\"loadError && !isRendering\" class=\"dxf-overlay dxf-overlay--error\">\n {{ loadError }}\n </div>\n\n <div *ngIf=\"!selected && !isResolving && !loadError\" class=\"dxf-overlay dxf-muted\">\n Select a file to view.\n </div>\n </div>\n\n <p class=\"dxf-viewer__hint\">Scroll to zoom · drag to pan</p>\n </section>\n </div>\n</div>\n"],"names":["i2.StoreBuilderService","i3","i4"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBA;;;;;;;;;;;;;;;AAeG;AAEH;AACA,MAAM,SAAS,GAAa,EAAE;MAoBjB,wBAAwB,CAAA;AAsB1B,IAAA,WAAA;AACC,IAAA,mBAAA;AACA,IAAA,YAAA;IArBD,SAAS,GAAG,EAAE;;IAEd,QAAQ,GAAmB,EAAE;AAEb,IAAA,UAAU;IAEnC,OAAO,GAAmB,EAAE;IAC5B,QAAQ,GAAwB,IAAI;IAEpC,WAAW,GAAG,KAAK;IACnB,WAAW,GAAG,KAAK;IACnB,SAAS,GAAG,EAAE;IAEd,MAAM,GAA8D,EAAE;AAE9D,IAAA,MAAM;IACN,SAAS,GAAG,KAAK;AAEzB,IAAA,WAAA,CACS,WAA2B,EAC1B,mBAAwC,EACxC,YAA0B,EAAA;QAF3B,IAAA,CAAA,WAAW,GAAX,WAAW;QACV,IAAA,CAAA,mBAAmB,GAAnB,mBAAmB;QACnB,IAAA,CAAA,YAAY,GAAZ,YAAY;IACnB;AAEH,IAAA,MAAM,QAAQ,GAAA;AACZ,QAAA,MAAM,IAAI,CAAC,WAAW,EAAE;IAC1B;IAEA,eAAe,GAAA;AACb,QAAA,IAAI,CAAC,SAAS,GAAG,IAAI;;AAErB,QAAA,IAAI,IAAI,CAAC,QAAQ,EAAE,SAAS;AAAE,YAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;IAC1D;IAEA,WAAW,GAAA;QACT,IAAI,CAAC,aAAa,EAAE;IACtB;;AAGQ,IAAA,MAAM,WAAW,GAAA;AACvB,QAAA,IAAI,CAAC,WAAW,GAAG,IAAI;AACvB,QAAA,IAAI,CAAC,SAAS,GAAG,EAAE;QACnB,IAAI;AACF,YAAA,MAAM,GAAG,GAAQ,MAAM,cAAc,CACnC,IAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CACrD;YACD,MAAM,OAAO,GAAU,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE;YAC7C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAc,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;;AAGlE,YAAA,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,KAAI;gBAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;gBAC9B,OAAO;AACL,oBAAA,GAAG,CAAC;AACJ,oBAAA,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE;oBAC/C,GAAG,EAAE,KAAK,EAAE,GAAG;oBACf,IAAI,EAAE,KAAK,EAAE,IAAI;AACjB,oBAAA,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG;iBACxB;AACH,YAAA,CAAC,CAAC;;AAGF,YAAA,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO;AACrC,iBAAA,OAAO;iBACP,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC;AAC3B,YAAA,IAAI,eAAe,EAAE;AACnB,gBAAA,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC;AAC7B,YAAA;AAAM,iBAAA,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;AAC9B,gBAAA,IAAI,CAAC,SAAS;AACZ,oBAAA,+EAA+E;AAClF,YAAA;AACF,QAAA;AAAC,QAAA,OAAO,CAAM,EAAE;AACf,YAAA,IAAI,CAAC,SAAS;AACZ,gBAAA,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM;oBACvB,CAAC,EAAE,KAAK,EAAE,OAAO;AACjB,oBAAA,kCAAkC;AACrC,QAAA;AAAS,gBAAA;AACR,YAAA,IAAI,CAAC,WAAW,GAAG,KAAK;AACzB,QAAA;IACH;;AAGA,IAAA,IAAI,QAAQ,GAAA;QACV,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IACpC;AAEA,IAAA,QAAQ,CAAC,KAAmB,EAAA;QAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IACnF;AAEA,IAAA,MAAM,CAAC,KAAmB,EAAA;QACxB,IAAI,CAAC,KAAK,EAAE,SAAS,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK;YAAE;AAClD,QAAA,IAAI,CAAC,QAAQ,GAAG,KAAK;QACrB,IAAI,IAAI,CAAC,SAAS;AAAE,YAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;IACxC;IAEQ,MAAM,MAAM,CAAC,KAAmB,EAAA;QACtC,IAAI,CAAC,KAAK,EAAE,GAAG;YAAE;AACjB,QAAA,IAAI,CAAC,WAAW,GAAG,IAAI;AACvB,QAAA,IAAI,CAAC,SAAS,GAAG,EAAE;AACnB,QAAA,IAAI,CAAC,MAAM,GAAG,EAAE;QAChB,IAAI;YACF,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,OAAO,YAAY,CAAC;;;;AAIhD,YAAA,MAAM,KAAK,GAAG,MAAM,OAAO,OAAO,CAAC;AAEnC,YAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;gBAChB,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE;AACzD,oBAAA,UAAU,EAAE,IAAI;;;AAGhB,oBAAA,eAAe,EAAE,IAAI;AACrB,oBAAA,UAAU,EAAE,IAAI,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC;AACrC,oBAAA,UAAU,EAAE,CAAC;AACd,iBAAA,CAAC;AACH,YAAA;AAAM,iBAAA;AACL,gBAAA,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;AACpB,YAAA;AAED,YAAA,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;;YAG5D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,IAAI,EAAE;AAC9C,YAAA,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,MAAM;gBACzC,IAAI,EAAE,CAAC,CAAC,IAAI;AACZ,gBAAA,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI;AACpC,gBAAA,OAAO,EAAE,IAAI;AACd,aAAA,CAAC,CAAC;AACJ,QAAA;AAAC,QAAA,OAAO,CAAC,EAAE;AACV,YAAA,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,CAAC,CAAC;AAC1C,YAAA,IAAI,CAAC,SAAS,GAAG,oCAAoC;AACtD,QAAA;AAAS,gBAAA;AACR,YAAA,IAAI,CAAC,WAAW,GAAG,KAAK;AACzB,QAAA;IACH;AAEA,IAAA,WAAW,CAAC,KAAyC,EAAA;AACnD,QAAA,KAAK,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO;AAC9B,QAAA,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC;IACrD;AAEA,IAAA,IAAI,gBAAgB,GAAA;QAClB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;IACtE;AAEA,IAAA,eAAe,CAAC,KAAY,EAAA;AAC1B,QAAA,MAAM,OAAO,GAAI,KAAK,CAAC,MAA2B,CAAC,OAAO;AAC1D,QAAA,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE;AAC/B,YAAA,KAAK,CAAC,OAAO,GAAG,OAAO;AACvB,YAAA,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC;AAC9C,QAAA;IACH;AAEA,IAAA,MAAM,QAAQ,CAAC,KAAmB,EAAE,KAAiB,EAAA;QACnD,KAAK,CAAC,eAAe,EAAE;AACvB,QAAA,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE;AACf,YAAA,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,gCAAgC,CAAC;YACjE;AACD,QAAA;QACD,IAAI;YACF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;YACvC,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,CAAA,KAAA,EAAQ,QAAQ,CAAC,MAAM,CAAA,CAAE,CAAC;AAC5D,YAAA,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE;YAClC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,IAAI,aAAa,CAAC;AACxD,QAAA;QAAC,MAAM;AACN,YAAA,IAAI,CAAC,YAAY,CAAC,aAAa,CAC7B,CAAA,mBAAA,EAAsB,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAA,CAAA,CAAG,CAClD;AACF,QAAA;IACH;IAEQ,aAAa,GAAA;QACnB,IAAI;AACF,YAAA,IAAI,CAAC,MAAM,EAAE,OAAO,IAAI;AACzB,QAAA;AAAC,QAAA,MAAM,EAAA;AACR,QAAA,IAAI,CAAC,MAAM,GAAG,IAAI;IACpB;IAEA,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE;IAC5B;wGAzLW,wBAAwB,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,CAAA,cAAA,EAAA,EAAA,EAAA,KAAA,EAAAA,mBAAA,EAAA,EAAA,EAAA,KAAA,EAAAC,EAAA,CAAA,YAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;4FAAxB,wBAAwB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,uBAAA,EAAA,MAAA,EAAA,EAAA,SAAA,EAAA,WAAA,EAAA,QAAA,EAAA,UAAA,EAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,YAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAAA,CAAA,YAAA,CAAA,EAAA,WAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECtDrC,2lIAkGA,EAAA,MAAA,EAAA,CAAA,m5GAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDhDY,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAAC,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,cAAA,EAAA,eAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAAA,EAAA,CAAA,IAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAAA,EAAA,CAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,EAAA,CAAA;;4FAIX,wBAAwB,EAAA,UAAA,EAAA,CAAA;kBAPpC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,uBAAuB,EAAA,UAAA,EACrB,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,CAAC,EAAA,QAAA,EAAA,2lIAAA,EAAA,MAAA,EAAA,CAAA,m5GAAA,CAAA,EAAA;6IAOd,SAAS,EAAA,CAAA;sBAAjB;gBAEQ,QAAQ,EAAA,CAAA;sBAAhB;gBAEwB,UAAU,EAAA,CAAA;sBAAlC,SAAS;uBAAC,YAAY;;;;;"}
|
|
1
|
+
{"version":3,"file":"tango-app-ui-store-builder-dxf-source-viewer.component-BplzhK87.mjs","sources":["../../../projects/tango-store-builder/src/lib/components/dxf-source-viewer/dxf-source-viewer.component.ts","../../../projects/tango-store-builder/src/lib/components/dxf-source-viewer/dxf-source-viewer.component.html"],"sourcesContent":["import {\n AfterViewInit,\n Component,\n ElementRef,\n Input,\n OnDestroy,\n OnInit,\n ViewChild,\n} from \"@angular/core\";\nimport { CommonModule } from \"@angular/common\";\nimport { NgbActiveModal } from \"@ng-bootstrap/ng-bootstrap\";\nimport * as FileSaver from \"file-saver\";\nimport { firstValueFrom } from \"rxjs\";\nimport { ToastService } from \"tango-app-ui-shared\";\nimport { StoreBuilderService } from \"../../services/store-builder.service\";\n\n/**\n * Read-only viewer for a floor's SOURCE CAD/DXF files.\n *\n * Renders the floor's `cadFiles` (stored on the storelayout doc) as a timeline —\n * oldest first, latest last/highlighted — and shows the selected drawing in a 2D\n * `dxf-viewer` (WebGL, built-in pan/zoom + layer toggle). Each entry also has a\n * download button.\n *\n * `dxf-viewer` (and its heavy `three` peer) are dynamically imported here, and this\n * component itself is opened via a dynamic import (see CadSourceService), so the\n * whole thing lands in a lazy chunk and stays out of the main bundle.\n *\n * NOTE ON TEXT: dxf-viewer needs a font file to render TEXT/MTEXT entities. Drop a\n * .ttf into assets and list it in FONT_URLS to enable labels; with no font, geometry\n * renders but text is blank (no crash).\n */\n\n// Add a hosted TTF here (e.g. \"assets/fonts/Roboto-Regular.ttf\") to render DXF text.\nconst FONT_URLS: string[] = [];\n\ninterface CadFileEntry {\n key: string;\n fileName?: string;\n uploadedAt?: string | Date;\n inferred?: boolean;\n // resolved at runtime from listCadFiles:\n url?: string;\n size?: number;\n available?: boolean;\n}\n\n@Component({\n selector: \"lib-dxf-source-viewer\",\n standalone: true,\n imports: [CommonModule],\n templateUrl: \"./dxf-source-viewer.component.html\",\n styleUrl: \"./dxf-source-viewer.component.scss\",\n})\nexport class DxfSourceViewerComponent\n implements OnInit, AfterViewInit, OnDestroy\n{\n @Input() storeName = \"\";\n /** The floor's stored cadFiles array (oldest -> newest). */\n @Input() cadFiles: CadFileEntry[] = [];\n\n @ViewChild(\"viewerHost\") viewerHost!: ElementRef<HTMLDivElement>;\n\n entries: CadFileEntry[] = [];\n selected: CadFileEntry | null = null;\n\n isResolving = false;\n isRendering = false;\n loadError = \"\";\n\n layers: { name: string; displayName: string; visible: boolean }[] = [];\n\n private viewer: any;\n private viewReady = false;\n\n constructor(\n public activeModal: NgbActiveModal,\n private storeBuilderService: StoreBuilderService,\n private toastService: ToastService,\n ) {}\n\n async ngOnInit(): Promise<void> {\n await this.resolveUrls();\n }\n\n ngAfterViewInit(): void {\n this.viewReady = true;\n // If URLs resolved before the view was ready, render the default selection now.\n if (this.selected?.available) this.render(this.selected);\n }\n\n ngOnDestroy(): void {\n this.destroyViewer();\n }\n\n /** Match each stored cadFiles entry to a presigned URL from listCadFiles(storeName). */\n private async resolveUrls(): Promise<void> {\n this.isResolving = true;\n this.loadError = \"\";\n try {\n const res: any = await firstValueFrom(\n this.storeBuilderService.getCadFiles(this.storeName),\n );\n const s3Files: any[] = res?.data?.files ?? [];\n const byKey = new Map<string, any>(s3Files.map((f) => [f.key, f]));\n\n // Preserve stored order (oldest -> newest); render newest at top of timeline.\n this.entries = (this.cadFiles ?? []).map((c) => {\n const match = byKey.get(c.key);\n return {\n ...c,\n fileName: c.fileName ?? c.key?.split(\"/\").pop(),\n url: match?.url,\n size: match?.size,\n available: !!match?.url,\n };\n });\n\n // Default selection: latest available entry (last in stored order).\n const latestAvailable = [...this.entries]\n .reverse()\n .find((e) => e.available);\n if (latestAvailable) {\n this.select(latestAvailable);\n } else if (this.entries.length) {\n this.loadError =\n \"The source file(s) could not be found in storage. They may have been removed.\";\n }\n } catch (e: any) {\n this.loadError =\n e?.error?.error?.errMsg ??\n e?.error?.message ??\n \"Failed to load source CAD files.\";\n } finally {\n this.isResolving = false;\n }\n }\n\n /** Timeline shows newest first for scannability. */\n get timeline(): CadFileEntry[] {\n return [...this.entries].reverse();\n }\n\n isLatest(entry: CadFileEntry): boolean {\n return this.entries.length > 0 && entry === this.entries[this.entries.length - 1];\n }\n\n select(entry: CadFileEntry): void {\n if (!entry?.available || this.selected === entry) return;\n this.selected = entry;\n if (this.viewReady) this.render(entry);\n }\n\n private async render(entry: CadFileEntry): Promise<void> {\n if (!entry?.url) return;\n this.isRendering = true;\n this.loadError = \"\";\n this.layers = [];\n try {\n const { DxfViewer } = await import(\"dxf-viewer\");\n // three ships no type declarations; it's a runtime dep of dxf-viewer.\n // We only need Color here, so import it untyped rather than pulling @types/three.\n // @ts-ignore\n const THREE = await import(\"three\");\n\n if (!this.viewer) {\n this.viewer = new DxfViewer(this.viewerHost.nativeElement, {\n autoResize: true,\n // Correct entity colors for contrast against the (white) background so\n // light-coloured layers/text stay visible.\n colorCorrection: true,\n clearColor: new THREE.Color(0xffffff),\n clearAlpha: 1,\n });\n } else {\n this.viewer.Clear();\n }\n\n await this.viewer.Load({ url: entry.url, fonts: FONT_URLS });\n\n // Build the layer-toggle list.\n const layers = this.viewer.GetLayers?.() ?? [];\n this.layers = [...layers].map((l: any) => ({\n name: l.name,\n displayName: l.displayName ?? l.name,\n visible: true,\n }));\n } catch (e) {\n console.error(\"@@ ~ dxf render [ERR]:\", e);\n this.loadError = \"Failed to render this DXF drawing.\";\n } finally {\n this.isRendering = false;\n }\n }\n\n toggleLayer(layer: { name: string; visible: boolean }): void {\n layer.visible = !layer.visible;\n this.viewer?.ShowLayer?.(layer.name, layer.visible);\n }\n\n get allLayersVisible(): boolean {\n return this.layers.length > 0 && this.layers.every((l) => l.visible);\n }\n\n toggleAllLayers(event: Event): void {\n const checked = (event.target as HTMLInputElement).checked;\n for (const layer of this.layers) {\n layer.visible = checked;\n this.viewer?.ShowLayer?.(layer.name, checked);\n }\n }\n\n async download(entry: CadFileEntry, event: MouseEvent): Promise<void> {\n event.stopPropagation();\n if (!entry?.url) {\n this.toastService.getErrorToast(\"Download URL is not available.\");\n return;\n }\n try {\n const response = await fetch(entry.url);\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n const blob = await response.blob();\n FileSaver.saveAs(blob, entry.fileName ?? \"drawing.dxf\");\n } catch {\n this.toastService.getErrorToast(\n `Failed to download ${entry.fileName ?? \"file\"}.`,\n );\n }\n }\n\n private destroyViewer(): void {\n try {\n this.viewer?.Destroy?.();\n } catch {}\n this.viewer = null;\n }\n\n close(): void {\n this.activeModal.dismiss();\n }\n}\n","<div class=\"dxf-modal\">\n <div class=\"dxf-modal__header\">\n <div>\n <h3 class=\"dxf-modal__title\">Source CAD drawing</h3>\n <p class=\"dxf-modal__subtitle\">{{ storeName }}</p>\n </div>\n <button type=\"button\" class=\"dxf-icon-btn\" aria-label=\"Close\" (click)=\"close()\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"#475467\" stroke-width=\"1.6\" stroke-linecap=\"round\" />\n </svg>\n </button>\n </div>\n\n <div class=\"dxf-modal__body\">\n <!-- Timeline of source files -->\n <aside class=\"dxf-timeline\">\n <div class=\"dxf-timeline__head\">File history</div>\n\n <div *ngIf=\"isResolving\" class=\"dxf-muted\">Loading files…</div>\n\n <div *ngIf=\"!isResolving && !entries.length\" class=\"dxf-muted\">\n No source CAD files for this floor.\n </div>\n\n <ul class=\"dxf-timeline__list\" *ngIf=\"!isResolving && entries.length\">\n <li\n *ngFor=\"let entry of timeline\"\n class=\"dxf-timeline__item\"\n [class.is-selected]=\"entry === selected\"\n [class.is-disabled]=\"!entry.available\"\n (click)=\"select(entry)\"\n >\n <div class=\"dxf-timeline__dot\"></div>\n <div class=\"dxf-timeline__content\">\n <div class=\"dxf-timeline__name\" [title]=\"entry.fileName\">\n {{ entry.fileName }}\n <span *ngIf=\"isLatest(entry)\" class=\"dxf-badge dxf-badge--latest\">Latest</span>\n <span *ngIf=\"entry.inferred\" class=\"dxf-badge dxf-badge--inferred\" title=\"Matched from storage, not recorded at generation time\">Inferred</span>\n </div>\n <div class=\"dxf-timeline__meta\">\n <span *ngIf=\"entry.uploadedAt\">{{ entry.uploadedAt | date: \"medium\" }}</span>\n <span *ngIf=\"!entry.available\" class=\"dxf-unavailable\">Unavailable</span>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"dxf-icon-btn dxf-icon-btn--sm\"\n [disabled]=\"!entry.available\"\n aria-label=\"Download\"\n (click)=\"download(entry, $event)\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M8 2v8m0 0L5 7m3 3l3-3M3 13h10\" stroke=\"#475467\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </button>\n </li>\n </ul>\n </aside>\n\n <!-- Viewer -->\n <section class=\"dxf-viewer\">\n <div class=\"dxf-viewer__toolbar\" *ngIf=\"layers.length\">\n <div class=\"dxf-layers\">\n <span class=\"dxf-layers__label\">Layers</span>\n <label class=\"dxf-layers__item dxf-layers__item--all\">\n <input type=\"checkbox\" [checked]=\"allLayersVisible\" (change)=\"toggleAllLayers($event)\" />\n All\n </label>\n <label class=\"dxf-layers__item\" *ngFor=\"let layer of layers\">\n <input type=\"checkbox\" [checked]=\"layer.visible\" (change)=\"toggleLayer(layer)\" />\n {{ layer.displayName }}\n </label>\n </div>\n </div>\n\n <div class=\"dxf-viewer__stage\">\n <div #viewerHost class=\"dxf-viewer__host\"></div>\n\n <div *ngIf=\"isRendering\" class=\"dxf-overlay\">\n <svg class=\"dxf-spinner\" viewBox=\"0 0 50 50\">\n <circle class=\"path\" cx=\"25\" cy=\"25\" r=\"20\" fill=\"none\" stroke-width=\"4\"></circle>\n </svg>\n <span>Rendering drawing…</span>\n </div>\n\n <div *ngIf=\"loadError && !isRendering\" class=\"dxf-overlay dxf-overlay--error\">\n {{ loadError }}\n </div>\n\n <div *ngIf=\"!selected && !isResolving && !loadError\" class=\"dxf-overlay dxf-muted\">\n Select a file to view.\n </div>\n </div>\n\n <p class=\"dxf-viewer__hint\">Scroll to zoom · drag to pan</p>\n </section>\n </div>\n</div>\n"],"names":["i2.StoreBuilderService","i3","i4"],"mappings":"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"}
|
|
@@ -7624,7 +7624,7 @@ class CadSourceService {
|
|
|
7624
7624
|
async open(storeName, cadFiles) {
|
|
7625
7625
|
if (!this.hasSource(cadFiles))
|
|
7626
7626
|
return;
|
|
7627
|
-
const { DxfSourceViewerComponent } = await import('./tango-app-ui-store-builder-dxf-source-viewer.component-
|
|
7627
|
+
const { DxfSourceViewerComponent } = await import('./tango-app-ui-store-builder-dxf-source-viewer.component-BplzhK87.mjs');
|
|
7628
7628
|
const ref = this.ngbModal.open(DxfSourceViewerComponent, {
|
|
7629
7629
|
size: "xl",
|
|
7630
7630
|
centered: true,
|
|
@@ -62369,31 +62369,88 @@ class OnboardStorePlanoComponent {
|
|
|
62369
62369
|
height: maxY - minY,
|
|
62370
62370
|
};
|
|
62371
62371
|
}
|
|
62372
|
-
//
|
|
62373
|
-
|
|
62374
|
-
|
|
62375
|
-
|
|
62376
|
-
|
|
62377
|
-
|
|
62378
|
-
|
|
62372
|
+
// Shared median helper for the robust (outlier-tolerant) bounds below.
|
|
62373
|
+
median(values) {
|
|
62374
|
+
if (!values.length)
|
|
62375
|
+
return 0;
|
|
62376
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
62377
|
+
const mid = Math.floor(sorted.length / 2);
|
|
62378
|
+
return sorted.length % 2
|
|
62379
|
+
? sorted[mid]
|
|
62380
|
+
: (sorted[mid - 1] + sorted[mid]) / 2;
|
|
62381
|
+
}
|
|
62382
|
+
// Union bounds of the canvas objects with gross positional outliers removed.
|
|
62383
|
+
// A single element carrying a bad far coordinate — a CAD orphan wall stranded
|
|
62384
|
+
// at the origin, an unplaced fixture — would otherwise inflate the frame and
|
|
62385
|
+
// collapse the fit zoom to an invisible speck. We drop objects whose center
|
|
62386
|
+
// lies far outside the cluster (median center + a generous multiple of the
|
|
62387
|
+
// spread) and fit to what remains, so no single stray of ANY type (wall,
|
|
62388
|
+
// entrance, fixture, other) can hide the whole layout. Falls back to the full
|
|
62389
|
+
// union when there are too few objects to reason about statistically.
|
|
62390
|
+
getRobustLayoutBounds() {
|
|
62391
|
+
const entries = this.canvas
|
|
62379
62392
|
.getObjects()
|
|
62380
|
-
.
|
|
62381
|
-
|
|
62393
|
+
.map((o) => ({ o, r: o.getBoundingRect() }))
|
|
62394
|
+
.filter(({ r }) => Number.isFinite(r.left) &&
|
|
62395
|
+
Number.isFinite(r.top) &&
|
|
62396
|
+
Number.isFinite(r.width) &&
|
|
62397
|
+
Number.isFinite(r.height));
|
|
62398
|
+
if (!entries.length)
|
|
62382
62399
|
return { left: 0, top: 0, width: 0, height: 0 };
|
|
62383
|
-
|
|
62384
|
-
|
|
62385
|
-
const
|
|
62386
|
-
|
|
62387
|
-
|
|
62388
|
-
|
|
62389
|
-
|
|
62390
|
-
|
|
62391
|
-
|
|
62392
|
-
left: minX,
|
|
62393
|
-
top: minY,
|
|
62394
|
-
width: maxX - minX,
|
|
62395
|
-
height: maxY - minY,
|
|
62400
|
+
const union = (list) => {
|
|
62401
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
62402
|
+
for (const { r } of list) {
|
|
62403
|
+
minX = Math.min(minX, r.left);
|
|
62404
|
+
minY = Math.min(minY, r.top);
|
|
62405
|
+
maxX = Math.max(maxX, r.left + r.width);
|
|
62406
|
+
maxY = Math.max(maxY, r.top + r.height);
|
|
62407
|
+
}
|
|
62408
|
+
return { left: minX, top: minY, width: maxX - minX, height: maxY - minY };
|
|
62396
62409
|
};
|
|
62410
|
+
if (entries.length < 4)
|
|
62411
|
+
return union(entries);
|
|
62412
|
+
const cxs = entries.map(({ r }) => r.left + r.width / 2);
|
|
62413
|
+
const cys = entries.map(({ r }) => r.top + r.height / 2);
|
|
62414
|
+
const medX = this.median(cxs);
|
|
62415
|
+
const medY = this.median(cys);
|
|
62416
|
+
const dists = entries.map((_, i) => Math.hypot(cxs[i] - medX, cys[i] - medY));
|
|
62417
|
+
const medDist = this.median(dists);
|
|
62418
|
+
const mad = this.median(dists.map((d) => Math.abs(d - medDist)));
|
|
62419
|
+
const spread = mad > 1 ? mad : medDist > 1 ? medDist : 1;
|
|
62420
|
+
const threshold = medDist + 6 * spread;
|
|
62421
|
+
const kept = entries.filter((_, i) => dists[i] <= threshold);
|
|
62422
|
+
const dropped = entries.filter((_, i) => dists[i] > threshold);
|
|
62423
|
+
if (dropped.length) {
|
|
62424
|
+
console.warn(`[fit] ignoring ${dropped.length} out-of-cluster object(s) while fitting`, dropped.map(({ o, r }) => ({
|
|
62425
|
+
type: o?.objType,
|
|
62426
|
+
id: o?.fixtureId ?? o?.data?._id ?? o?.data?.elementNumber,
|
|
62427
|
+
bounds: r,
|
|
62428
|
+
})));
|
|
62429
|
+
}
|
|
62430
|
+
return kept.length ? union(kept) : union(entries);
|
|
62431
|
+
}
|
|
62432
|
+
// Wall start-points (px) with gross outliers removed. A CAD orphan wall far
|
|
62433
|
+
// from the store must not define the perimeter that reclaim places elements
|
|
62434
|
+
// into, so strip it the same way the fit does before building the polygon.
|
|
62435
|
+
getRobustWallPolygon() {
|
|
62436
|
+
const pts = (this.floorData?.layoutPolygon ?? [])
|
|
62437
|
+
.filter((w) => w?.elementType === "wall")
|
|
62438
|
+
.map((w) => ({
|
|
62439
|
+
x: this.toPixels(w.relativePosition?.x ?? 0, w.relativePosition?.unit || "ft"),
|
|
62440
|
+
y: this.toPixels(w.relativePosition?.y ?? 0, w.relativePosition?.unit || "ft"),
|
|
62441
|
+
}))
|
|
62442
|
+
.filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
|
|
62443
|
+
if (pts.length < 4)
|
|
62444
|
+
return pts;
|
|
62445
|
+
const medX = this.median(pts.map((p) => p.x));
|
|
62446
|
+
const medY = this.median(pts.map((p) => p.y));
|
|
62447
|
+
const dists = pts.map((p) => Math.hypot(p.x - medX, p.y - medY));
|
|
62448
|
+
const medDist = this.median(dists);
|
|
62449
|
+
const mad = this.median(dists.map((d) => Math.abs(d - medDist)));
|
|
62450
|
+
const spread = mad > 1 ? mad : medDist > 1 ? medDist : 1;
|
|
62451
|
+
const threshold = medDist + 6 * spread;
|
|
62452
|
+
const kept = pts.filter((_, i) => dists[i] <= threshold);
|
|
62453
|
+
return kept.length >= 3 ? kept : pts;
|
|
62397
62454
|
}
|
|
62398
62455
|
// Reclaims elements whose stored position renders them outside the wall
|
|
62399
62456
|
// perimeter — the bad-coordinate artifacts a CAD import can leave behind.
|
|
@@ -62406,7 +62463,10 @@ class OnboardStorePlanoComponent {
|
|
|
62406
62463
|
const floor = this.floorData;
|
|
62407
62464
|
if (!floor?.layoutPolygon?.length)
|
|
62408
62465
|
return 0;
|
|
62409
|
-
|
|
62466
|
+
// Robust wall polygon so an orphan wall (bad coordinate) can't define the
|
|
62467
|
+
// perimeter — otherwise fixtures/other elements would all look "inside" the
|
|
62468
|
+
// inflated frame and never get reclaimed.
|
|
62469
|
+
const wallPolygon = this.getRobustWallPolygon();
|
|
62410
62470
|
if (wallPolygon.length < 3)
|
|
62411
62471
|
return 0;
|
|
62412
62472
|
const bounds = this.polygonBounds(wallPolygon);
|
|
@@ -62627,15 +62687,12 @@ class OnboardStorePlanoComponent {
|
|
|
62627
62687
|
return;
|
|
62628
62688
|
if (this.layoutForm.enabled)
|
|
62629
62689
|
return;
|
|
62630
|
-
// Fit to the
|
|
62631
|
-
// union of
|
|
62632
|
-
//
|
|
62633
|
-
//
|
|
62634
|
-
//
|
|
62635
|
-
const
|
|
62636
|
-
const bounds = perimeter.width && perimeter.height
|
|
62637
|
-
? perimeter
|
|
62638
|
-
: this.getFullLayoutBounds();
|
|
62690
|
+
// Fit to the cluster of objects with gross outliers removed, rather than the
|
|
62691
|
+
// union of everything. A single element with a bad far coordinate — e.g. a
|
|
62692
|
+
// CAD orphan wall stranded at the origin while the real store sits thousands
|
|
62693
|
+
// of feet away — would otherwise blow up the frame and collapse the zoom to
|
|
62694
|
+
// an invisible speck. getRobustLayoutBounds() drops such strays of any type.
|
|
62695
|
+
const bounds = this.getRobustLayoutBounds();
|
|
62639
62696
|
if (!bounds.width || !bounds.height)
|
|
62640
62697
|
return;
|
|
62641
62698
|
const canvasW = this.canvas.getWidth();
|
|
@@ -80273,4 +80330,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
80273
80330
|
*/
|
|
80274
80331
|
|
|
80275
80332
|
export { StoreBuilderService as S, TangoStoreBuilderModule as T, TangoStorePlanoModule as a };
|
|
80276
|
-
//# sourceMappingURL=tango-app-ui-store-builder-tango-app-ui-store-builder-
|
|
80333
|
+
//# sourceMappingURL=tango-app-ui-store-builder-tango-app-ui-store-builder-CXw6OsFW.mjs.map
|