@things-factory/board-ui 10.0.0-beta.8 → 10.0.0-beta.80
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/dist-client/apptools/favorite-tool.js +5 -5
- package/dist-client/apptools/favorite-tool.js.map +1 -1
- package/dist-client/board-list/board-tile-list.d.ts +6 -3
- package/dist-client/board-list/board-tile-list.js +316 -62
- package/dist-client/board-list/board-tile-list.js.map +1 -1
- package/dist-client/board-list/group-bar.js +3 -3
- package/dist-client/board-list/group-bar.js.map +1 -1
- package/dist-client/board-list/play-group-bar.d.ts +0 -1
- package/dist-client/board-list/play-group-bar.js +3 -6
- package/dist-client/board-list/play-group-bar.js.map +1 -1
- package/dist-client/board-provider.js +20 -8
- package/dist-client/board-provider.js.map +1 -1
- package/dist-client/data-grist/board-editor.js +4 -4
- package/dist-client/data-grist/board-editor.js.map +1 -1
- package/dist-client/data-grist/board-renderer.js +4 -4
- package/dist-client/data-grist/board-renderer.js.map +1 -1
- package/dist-client/graphql/attachment.d.ts +33 -0
- package/dist-client/graphql/attachment.js +87 -0
- package/dist-client/graphql/attachment.js.map +1 -0
- package/dist-client/graphql/board-import.d.ts +45 -0
- package/dist-client/graphql/board-import.js +104 -0
- package/dist-client/graphql/board-import.js.map +1 -0
- package/dist-client/graphql/board-template.js +1 -1
- package/dist-client/graphql/board-template.js.map +1 -1
- package/dist-client/graphql/board.d.ts +1 -0
- package/dist-client/graphql/board.js +28 -2
- package/dist-client/graphql/board.js.map +1 -1
- package/dist-client/graphql/group.js +1 -1
- package/dist-client/graphql/group.js.map +1 -1
- package/dist-client/graphql/play-group.js +3 -3
- package/dist-client/graphql/play-group.js.map +1 -1
- package/dist-client/pages/attachment-list-page.d.ts +16 -0
- package/dist-client/pages/attachment-list-page.js +63 -2
- package/dist-client/pages/attachment-list-page.js.map +1 -1
- package/dist-client/pages/board-action-dispatch.d.ts +31 -0
- package/dist-client/pages/board-action-dispatch.js +80 -0
- package/dist-client/pages/board-action-dispatch.js.map +1 -0
- package/dist-client/pages/board-action-dispatch.test.d.ts +1 -0
- package/dist-client/pages/board-action-dispatch.test.js +235 -0
- package/dist-client/pages/board-action-dispatch.test.js.map +1 -0
- package/dist-client/pages/board-create-wizard-page.d.ts +157 -0
- package/dist-client/pages/board-create-wizard-page.js +2176 -0
- package/dist-client/pages/board-create-wizard-page.js.map +1 -0
- package/dist-client/pages/board-edit-dispatch.d.ts +74 -0
- package/dist-client/pages/board-edit-dispatch.js +299 -0
- package/dist-client/pages/board-edit-dispatch.js.map +1 -0
- package/dist-client/pages/board-edit-dispatch.test.d.ts +1 -0
- package/dist-client/pages/board-edit-dispatch.test.js +858 -0
- package/dist-client/pages/board-edit-dispatch.test.js.map +1 -0
- package/dist-client/pages/board-list-page.d.ts +23 -3
- package/dist-client/pages/board-list-page.js +165 -77
- package/dist-client/pages/board-list-page.js.map +1 -1
- package/dist-client/pages/board-modeller-page.d.ts +134 -0
- package/dist-client/pages/board-modeller-page.js +725 -54
- package/dist-client/pages/board-modeller-page.js.map +1 -1
- package/dist-client/pages/board-player-by-name-page.js.map +1 -1
- package/dist-client/pages/board-player-page.js +14 -26
- package/dist-client/pages/board-player-page.js.map +1 -1
- package/dist-client/pages/board-viewer-by-name-page.d.ts +8 -1
- package/dist-client/pages/board-viewer-by-name-page.js +9 -1
- package/dist-client/pages/board-viewer-by-name-page.js.map +1 -1
- package/dist-client/pages/board-viewer-page.d.ts +2 -1
- package/dist-client/pages/board-viewer-page.js +52 -48
- package/dist-client/pages/board-viewer-page.js.map +1 -1
- package/dist-client/pages/play-list-page.d.ts +0 -1
- package/dist-client/pages/play-list-page.js +26 -33
- package/dist-client/pages/play-list-page.js.map +1 -1
- package/dist-client/pages/printable-board-viewer-page.js +2 -2
- package/dist-client/pages/printable-board-viewer-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +3 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/setting-let/board-view-setting-let.js +1 -1
- package/dist-client/setting-let/board-view-setting-let.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/notify-helper.d.ts +7 -0
- package/dist-client/utils/notify-helper.js +28 -0
- package/dist-client/utils/notify-helper.js.map +1 -0
- package/dist-client/utils/query-utils.d.ts +1 -0
- package/dist-client/utils/query-utils.js +20 -0
- package/dist-client/utils/query-utils.js.map +1 -0
- package/dist-client/viewparts/board-basic-info.js +9 -13
- package/dist-client/viewparts/board-basic-info.js.map +1 -1
- package/dist-client/viewparts/board-template-info.d.ts +0 -1
- package/dist-client/viewparts/board-template-info.js +5 -13
- package/dist-client/viewparts/board-template-info.js.map +1 -1
- package/dist-client/viewparts/board-versions.js +1 -1
- package/dist-client/viewparts/board-versions.js.map +1 -1
- package/dist-client/viewparts/group-info-basic.js +2 -2
- package/dist-client/viewparts/group-info-basic.js.map +1 -1
- package/dist-client/viewparts/group-info-import.js +2 -2
- package/dist-client/viewparts/group-info-import.js.map +1 -1
- package/dist-client/viewparts/link-builder.js +1 -1
- package/dist-client/viewparts/link-builder.js.map +1 -1
- package/dist-client/viewparts/play-group-info-basic.js +2 -2
- package/dist-client/viewparts/play-group-info-basic.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/things-factory.config.js +1 -0
- package/translations/en.json +71 -30
- package/translations/ja.json +3 -29
- package/translations/ko.json +71 -30
- package/translations/ms.json +3 -29
- package/translations/zh.json +3 -29
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-create-wizard-page.js","sourceRoot":"","sources":["../../client/pages/board-create-wizard-page.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * <board-create-wizard-page> — 보드 생성 wizard.\n *\n * 세 가지 모드:\n * 처음부터 (blank / template) — 빈 캔버스 또는 기존 템플릿으로 새 보드 생성.\n * 도면 업로드 (new) — DXF / 이미지 업로드 → board-import 파이프라인으로 자동 변환.\n * 기존 파일 (existing) — 이미 업로드된 Attachment 에서 보드 생성.\n */\nimport '@material/web/icon/icon.js'\n\nimport { css, html, type PropertyValues } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { navigate, PageView } from '@operato/shell'\nimport { notify } from '@operato/layout'\nimport '@operato/board/ox-board-preview.js'\n\nimport { createBoard } from '../graphql/board'\nimport { fetchGroupList } from '../graphql/group'\nimport { fetchBoardTemplateList, fetchBoardTemplate } from '../graphql/board-template'\n\nimport {\n createAttachmentForImport,\n fetchAttachmentsForImport,\n type AttachmentResult\n} from '../graphql/attachment'\nimport {\n importBoardAsync,\n fetchImportSession,\n materializeImportSession,\n suggestBoardMeta\n} from '../graphql/board-import'\n\ntype WizardStep = 'upload' | 'progress' | 'review' | 'done'\ntype UploadMode = 'blank' | 'template' | 'new' | 'existing'\n\n/** 허용 도면 형식 — board-import 의 어댑터 set 에 맞춤. */\nconst ACCEPT = '.dxf,.png,.jpg,.jpeg,.webp,.gif,.bmp'\n\n@customElement('board-create-wizard-page')\nexport class BoardCreateWizardPage extends localize(i18next)(PageView) {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n padding: var(--spacing-large, 16px);\n gap: var(--spacing-medium, 12px);\n height: 100%;\n background: var(--md-sys-color-surface, #fafafa);\n color: var(--md-sys-color-on-surface, #1a1a1a);\n overflow-y: auto;\n box-sizing: border-box;\n }\n :host::after {\n content: '';\n display: block;\n flex-shrink: 0;\n height: var(--spacing-large, 16px);\n }\n\n .header {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .header h1 {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n }\n .header p {\n margin: 0;\n font-size: 13px;\n color: var(--md-sys-color-on-secondary-container, #555);\n }\n\n .stepper {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--md-sys-color-surface-container, #fff);\n border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 8px;\n }\n .step {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border-radius: 16px;\n font-size: 12px;\n color: var(--md-sys-color-on-secondary-container, #777);\n background: transparent;\n border: 1px solid transparent;\n }\n .step.active {\n background: var(--md-sys-color-primary-container, #e7f0ff);\n color: var(--md-sys-color-on-primary-container, #0b3a8a);\n border-color: var(--md-sys-color-primary, #2a64d8);\n font-weight: 600;\n }\n .step.done {\n color: var(--md-sys-color-tertiary, #2e7d32);\n }\n .step .index {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--md-sys-color-outline-variant, #cfd8dc);\n color: #fff;\n font-size: 11px;\n line-height: 18px;\n text-align: center;\n }\n .step.active .index {\n background: var(--md-sys-color-primary, #2a64d8);\n }\n .step.done .index {\n background: var(--md-sys-color-tertiary, #2e7d32);\n }\n .stepper > .arrow {\n opacity: 0.4;\n font-size: 14px;\n }\n\n .stage {\n /* flex: 1 0 auto — 빈 공간 있으면 늘어나서 시각 채움, content 가 더 크면 content 크기\n * 그대로 (단순 flex:1 은 :host height 에 고정 → content 가 잘리는 회귀). */\n flex: 1 0 auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n background: var(--md-sys-color-surface-container, #fff);\n border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 8px;\n }\n\n .placeholder {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n height: 100%;\n min-height: 240px;\n color: var(--md-sys-color-on-secondary-container, #777);\n text-align: center;\n }\n .placeholder .big {\n font-size: 16px;\n font-weight: 500;\n }\n .placeholder .small {\n font-size: 12px;\n max-width: 320px;\n }\n\n .controls {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n }\n\n /* upload step UI */\n .drop-zone {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n min-height: 240px;\n padding: 24px;\n border: 2px dashed var(--md-sys-color-outline-variant, #b8c2cc);\n border-radius: 12px;\n background: var(--md-sys-color-surface-container-lowest, #fafbfc);\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n }\n .drop-zone:hover,\n .drop-zone.dragover {\n border-color: var(--md-sys-color-primary, #2a64d8);\n background: var(--md-sys-color-primary-container, #eaf2ff);\n }\n .drop-zone .icon {\n font-size: 36px;\n --md-icon-size: 36px;\n opacity: 0.5;\n }\n .drop-zone .title {\n font-size: 15px;\n font-weight: 500;\n }\n .drop-zone .hint {\n font-size: 12px;\n color: var(--md-sys-color-on-secondary-container, #777);\n }\n input[type='file'] {\n display: none;\n }\n\n .uploaded {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n background: var(--md-sys-color-tertiary-container, #e8f5e9);\n border: 1px solid var(--md-sys-color-tertiary, #2e7d32);\n border-radius: 8px;\n }\n .uploaded .name {\n font-weight: 500;\n }\n .uploaded .meta {\n font-size: 12px;\n opacity: 0.7;\n margin-left: auto;\n }\n\n /* upload mode tabs — slim underline-only */\n .mode-tabs {\n display: flex;\n gap: 24px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);\n margin-bottom: 8px;\n padding: 0 4px;\n }\n .mode-tab {\n padding: 8px 0 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 400;\n color: var(--md-sys-color-on-secondary-container, #777);\n border: none;\n border-bottom: 2px solid transparent;\n background: transparent;\n margin-bottom: -1px;\n transition: color 0.12s, border-color 0.12s;\n text-transform: capitalize;\n }\n .mode-tab:hover {\n color: var(--md-sys-color-on-surface, #1a1a1a);\n }\n .mode-tab.active {\n color: var(--md-sys-color-primary, #2a64d8);\n border-bottom-color: var(--md-sys-color-primary, #2a64d8);\n font-weight: 500;\n }\n .mode-tabs.locked .mode-tab:not(.active) {\n opacity: 0.35;\n cursor: not-allowed;\n }\n\n /* \"처음부터\" 탭 — popup 스타일 two-column */\n .scratch-layout {\n display: flex;\n flex-direction: row;\n gap: 16px;\n flex: 1 0 auto;\n min-height: 400px;\n }\n .scratch-layout .quick-form {\n display: flex;\n flex-direction: column;\n gap: 12px;\n width: 260px;\n flex-shrink: 0;\n }\n .template-panel {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 6px;\n overflow: hidden;\n }\n .template-panel-label {\n font-size: 12px;\n font-weight: 500;\n display: flex;\n align-items: baseline;\n gap: 6px;\n }\n .template-panel-hint {\n font-size: 11px;\n font-weight: 400;\n color: var(--md-sys-color-on-secondary-container, #888);\n }\n /* \"템플릿\" 탭 — 1컬럼 레이아웃 */\n .template-layout {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n .template-layout .quick-form {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n .template-selected-name {\n font-size: 11px;\n font-weight: 600;\n color: var(--md-sys-color-primary, #2a64d8);\n margin-left: 2px;\n }\n .browse-item.selected {\n border-color: var(--md-sys-color-primary, #2a64d8);\n background: var(--md-sys-color-primary-container, #eaf2ff);\n outline: 2px solid var(--md-sys-color-primary, #2a64d8);\n outline-offset: -1px;\n }\n\n /* browse existing list */\n .browse-toolbar {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n .browse-search {\n flex: 1;\n padding: 6px 10px;\n font-size: 13px;\n border: 1px solid var(--md-sys-color-outline-variant, #d0d7de);\n border-radius: 6px;\n }\n .browse-list {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));\n gap: 10px;\n max-height: 320px;\n overflow-y: auto;\n padding: 4px;\n }\n .browse-item {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 8px;\n border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 8px;\n background: var(--md-sys-color-surface-container-lowest, #fafbfc);\n cursor: pointer;\n transition: border-color 0.12s, background 0.12s;\n }\n .browse-item:hover {\n border-color: var(--md-sys-color-primary, #2a64d8);\n background: var(--md-sys-color-primary-container, #eaf2ff);\n }\n .browse-item .thumb {\n width: 100%;\n height: 90px;\n object-fit: cover;\n background: #e9ecef;\n border-radius: 4px;\n }\n .browse-item .thumb-fallback {\n width: 100%;\n height: 90px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #e9ecef;\n border-radius: 4px;\n color: #888;\n font-size: 11px;\n }\n .browse-item .name {\n font-size: 12px;\n font-weight: 500;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .browse-item .meta {\n font-size: 11px;\n color: var(--md-sys-color-on-secondary-container, #777);\n }\n .browse-empty {\n padding: 32px;\n text-align: center;\n color: var(--md-sys-color-on-secondary-container, #777);\n font-size: 13px;\n }\n\n /* file preview pane */\n .preview-pane {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--md-sys-color-surface-container-lowest, #fafbfc);\n border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 8px;\n }\n .preview-pane .preview-image {\n max-width: 100%;\n max-height: 360px;\n object-fit: contain;\n background: #f0f0f0;\n border-radius: 4px;\n align-self: center;\n }\n .preview-pane .preview-fallback {\n padding: 32px;\n text-align: center;\n color: var(--md-sys-color-on-secondary-container, #777);\n font-size: 13px;\n background: #f0f0f0;\n border-radius: 4px;\n }\n .preview-pane .info-row {\n display: flex;\n font-size: 12px;\n gap: 8px;\n }\n .preview-pane .info-row .label {\n color: var(--md-sys-color-on-secondary-container, #777);\n min-width: 80px;\n text-transform: capitalize;\n }\n\n /* user prompt textarea */\n .user-prompt {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n .user-prompt label {\n display: flex;\n flex-direction: column;\n gap: 2px;\n font-size: 13px;\n font-weight: 500;\n text-transform: capitalize;\n }\n .user-prompt label .hint {\n font-size: 11px;\n font-weight: 400;\n color: var(--md-sys-color-on-secondary-container, #777);\n }\n .user-prompt textarea {\n font-family: inherit;\n font-size: 13px;\n padding: 8px 10px;\n border: 1px solid var(--md-sys-color-outline-variant, #d0d7de);\n border-radius: 6px;\n resize: vertical;\n min-height: 60px;\n }\n .user-prompt textarea:focus {\n outline: none;\n border-color: var(--md-sys-color-primary, #2a64d8);\n }\n\n .error-banner {\n padding: 8px 12px;\n background: var(--md-sys-color-error-container, #fde7e7);\n color: var(--md-sys-color-on-error-container, #b91c1c);\n border-radius: 6px;\n font-size: 13px;\n }\n\n /* progress step UI */\n .progress-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 24px;\n background: var(--md-sys-color-surface-container-lowest, #fafbfc);\n border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 12px;\n }\n .progress-status {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n .progress-status .label {\n font-size: 14px;\n font-weight: 500;\n }\n .progress-status .pct {\n margin-left: auto;\n font-size: 13px;\n color: var(--md-sys-color-on-secondary-container, #777);\n }\n .progress-bar {\n position: relative;\n height: 6px;\n background: var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 3px;\n overflow: hidden;\n }\n .progress-bar > .fill {\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n background: var(--md-sys-color-primary, #2a64d8);\n transition: width 0.3s;\n }\n .progress-message {\n font-size: 12px;\n color: var(--md-sys-color-on-secondary-container, #777);\n }\n\n /* review step */\n .review-stats {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n }\n .stat {\n flex: 1;\n min-width: 120px;\n padding: 12px;\n background: var(--md-sys-color-surface-container-lowest, #fafbfc);\n border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 8px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .stat .label {\n font-size: 11px;\n color: var(--md-sys-color-on-secondary-container, #777);\n text-transform: uppercase;\n }\n .stat .value {\n font-size: 22px;\n font-weight: 600;\n }\n .warnings {\n max-height: 160px;\n overflow-y: auto;\n padding: 8px 12px;\n background: var(--md-sys-color-tertiary-container, #fff8e1);\n border: 1px solid var(--md-sys-color-tertiary, #b08e00);\n border-radius: 6px;\n font-size: 12px;\n }\n .warnings ul {\n margin: 4px 0 0;\n padding-left: 16px;\n }\n\n /* AI 분석 요약 카드 */\n .ai-summary {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 14px;\n background: var(--md-sys-color-primary-container, #eaf2ff);\n border: 1px solid var(--md-sys-color-primary, #2a64d8);\n border-radius: 8px;\n }\n .ai-summary .title {\n font-weight: 600;\n font-size: 13px;\n }\n .ai-summary .row {\n display: flex;\n gap: 8px;\n font-size: 12px;\n }\n .ai-summary .row .label {\n color: var(--md-sys-color-on-secondary-container, #555);\n min-width: 100px;\n flex-shrink: 0;\n text-transform: capitalize;\n }\n .ai-summary .row .value {\n flex: 1;\n word-break: break-word;\n }\n .ai-summary .badge {\n display: inline-block;\n padding: 1px 6px;\n margin-right: 4px;\n font-size: 11px;\n background: var(--md-sys-color-surface, #fff);\n border: 1px solid var(--md-sys-color-outline-variant, #d0d7de);\n border-radius: 10px;\n }\n .ai-summary .strategy {\n margin-top: 4px;\n padding: 8px 10px;\n background: var(--md-sys-color-surface, #fff);\n border-left: 3px solid var(--md-sys-color-primary, #2a64d8);\n border-radius: 4px;\n }\n .ai-summary .strategy-label {\n font-size: 12px;\n font-weight: 600;\n color: var(--md-sys-color-primary, #2a64d8);\n margin-bottom: 4px;\n }\n .ai-summary .strategy-text {\n font-size: 12px;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface, #1a1a1a);\n white-space: pre-wrap;\n }\n\n /* variant picker */\n .variant-picker {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n .variant-picker-header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n }\n .variant-picker-header strong {\n font-size: 13px;\n }\n .variant-picker-hint {\n flex: 1;\n font-size: 11px;\n color: var(--md-sys-color-on-secondary-container, #777);\n }\n .regenerate-btn {\n font-size: 12px;\n padding: 4px 10px;\n background: var(--md-sys-color-surface, #fff);\n border: 1px solid var(--md-sys-color-outline-variant, #d0d7de);\n border-radius: 4px;\n cursor: pointer;\n }\n .regenerate-btn:hover {\n background: var(--md-sys-color-surface-container, #f5f5f5);\n }\n .variant-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));\n gap: 10px;\n }\n .variant-card {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 4px;\n border: 2px solid var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 8px;\n background: var(--md-sys-color-surface, #fff);\n cursor: pointer;\n transition: border-color 0.12s, box-shadow 0.12s;\n }\n .variant-card:hover {\n border-color: var(--md-sys-color-primary, #2a64d8);\n }\n .variant-card.selected {\n border-color: var(--md-sys-color-primary, #2a64d8);\n box-shadow: 0 0 0 2px var(--md-sys-color-primary-container, #eaf2ff);\n }\n .variant-thumb {\n width: 100%;\n height: 140px;\n background: var(--md-sys-color-surface-container-lowest, #fafbfc);\n border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 4px;\n overflow: hidden;\n }\n .variant-thumb ox-board-preview {\n width: 100%;\n height: 100%;\n }\n .variant-meta {\n padding: 4px 6px 6px;\n }\n .variant-label {\n font-size: 13px;\n font-weight: 600;\n }\n .variant-desc {\n font-size: 11px;\n color: var(--md-sys-color-on-secondary-container, #777);\n margin-top: 2px;\n }\n .variant-stats {\n font-size: 11px;\n color: var(--md-sys-color-on-secondary-container, #555);\n margin-top: 4px;\n }\n\n /* big-preview — selected variant 큰 미리보기 */\n .big-preview {\n width: 100%;\n height: 360px;\n background: var(--md-sys-color-surface-container-lowest, #fafbfc);\n border: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);\n border-radius: 8px;\n overflow: hidden;\n }\n .big-preview ox-board-preview {\n width: 100%;\n height: 100%;\n }\n\n /* blank / template mode UI */\n .mode-info {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 32px 16px;\n text-align: center;\n }\n .mode-info .icon {\n font-size: 36px;\n opacity: 0.6;\n }\n .mode-info .title {\n font-size: 15px;\n font-weight: 600;\n }\n .mode-info .desc {\n font-size: 12px;\n color: var(--md-sys-color-on-secondary-container, #777);\n max-width: 480px;\n }\n .mode-info .primary {\n margin-top: 8px;\n }\n .mode-hint {\n padding: 10px 12px;\n font-size: 12px;\n color: var(--md-sys-color-on-secondary-container, #555);\n background: var(--md-sys-color-surface-container-lowest, #fafbfc);\n border-left: 3px solid var(--md-sys-color-primary, #2a64d8);\n border-radius: 4px;\n }\n\n /* meta input — board type radios + group select */\n .meta-input {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n }\n .meta-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 180px;\n }\n .meta-field label {\n font-size: 12px;\n font-weight: 500;\n text-transform: capitalize;\n }\n .meta-field select {\n padding: 6px 8px;\n font-size: 13px;\n border: 1px solid var(--md-sys-color-outline-variant, #d0d7de);\n border-radius: 6px;\n }\n .board-type-radios {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n .radio-label {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 13px;\n }\n\n .quick-form .controls {\n margin-top: auto;\n }\n .name-input {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .name-input label {\n font-size: 12px;\n font-weight: 500;\n text-transform: capitalize;\n }\n .name-input input {\n padding: 8px 10px;\n font-size: 14px;\n border: 1px solid var(--md-sys-color-outline-variant, #d0d7de);\n border-radius: 6px;\n outline: none;\n }\n .name-input input:focus {\n border-color: var(--md-sys-color-primary, #2a64d8);\n }\n\n .description-input {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .description-input label {\n display: flex;\n flex-direction: column;\n gap: 2px;\n font-size: 12px;\n font-weight: 500;\n text-transform: capitalize;\n }\n .description-input label .hint {\n font-size: 11px;\n font-weight: 400;\n color: var(--md-sys-color-on-secondary-container, #777);\n }\n .description-input textarea {\n font-family: inherit;\n font-size: 13px;\n line-height: 1.5;\n padding: 8px 10px;\n border: 1px solid var(--md-sys-color-outline-variant, #d0d7de);\n border-radius: 6px;\n outline: none;\n resize: vertical;\n min-height: 90px;\n }\n .description-input textarea:focus {\n border-color: var(--md-sys-color-primary, #2a64d8);\n }\n\n /* done step */\n .done-card {\n text-align: center;\n padding: 32px 24px;\n background: var(--md-sys-color-tertiary-container, #e8f5e9);\n border-radius: 12px;\n }\n .done-card .icon {\n font-size: 48px;\n --md-icon-size: 48px;\n color: var(--md-sys-color-tertiary, #2e7d32);\n }\n .done-card .title {\n font-size: 18px;\n font-weight: 600;\n margin-top: 8px;\n }\n .done-card .meta {\n font-size: 13px;\n color: var(--md-sys-color-on-secondary-container, #555);\n margin-top: 4px;\n }\n\n button.primary {\n padding: 8px 16px;\n background: var(--md-sys-color-primary, #2a64d8);\n color: white;\n border: none;\n border-radius: 6px;\n font-weight: 500;\n cursor: pointer;\n text-transform: capitalize;\n }\n button.primary:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n button.secondary {\n padding: 8px 16px;\n background: transparent;\n color: var(--md-sys-color-on-surface, #333);\n border: 1px solid var(--md-sys-color-outline-variant, #d0d7de);\n border-radius: 6px;\n cursor: pointer;\n text-transform: capitalize;\n }\n `\n ]\n\n /** 현재 wizard 단계. URL/params 와는 독립 — 페이지 안 state 만으로 진행. */\n @state() private _step: WizardStep = 'upload'\n\n /** 업로드 후 받은 Attachment 정보. */\n @state() private _attachment?: AttachmentResult\n\n /** importBoardAsync 결과 ImportSession. polling 시 갱신. */\n @state() private _session?: any\n\n /** 현재 업로드 진행 중 — UI lock + spinner. */\n @state() private _uploading = false\n\n /** 업로드 에러 메시지 — UI 표시 + 사용자가 다시 시도 가능. */\n @state() private _uploadError?: string\n\n /** upload 스텝 안의 모드 — 새 파일 업로드 vs 기존 attachment 선택. */\n @state() private _uploadMode: UploadMode = 'blank'\n\n /** 기존 파일 목록 (모드 전환 시 lazy fetch). */\n @state() private _browseItems: AttachmentResult[] = []\n\n /** 기존 파일 목록 로딩 / 검색 상태. */\n @state() private _browseLoading = false\n @state() private _browseSearch = ''\n\n /** 사용자가 도면에 대해 추가하는 자유서술 — VLM 에 hint 로 전달. */\n @state() private _userPrompt = ''\n\n /** 템플릿 목록 (template 탭 진입 시 lazy fetch). */\n @state() private _templates: Array<{ id: string; name: string; description?: string; thumbnail?: string }> = []\n @state() private _templatesLoading = false\n @state() private _templatesLoaded = false\n @state() private _templateSearch = ''\n\n /** 템플릿 모드 — 선택된 템플릿 (id, model, thumbnail). */\n @state() private _selectedTemplate: any = undefined\n\n /** 보드 그룹 목록 (lazy fetch). */\n @state() private _groups: Array<{ id: string; name: string }> = []\n @state() private _groupsLoaded = false\n\n /** 새 보드의 group / type — 모든 모드 공통. */\n @state() private _selectedGroupId = ''\n @state() private _selectedBoardType: 'main' | 'sub' | 'popup' = 'main'\n\n /** import 진행 / polling 에러. */\n @state() private _importError?: string\n\n /** polling timer id — disconnect 시 취소. */\n private _pollingTimer?: number\n\n /** review step — 새 Board 이름 (짧고 심플). */\n @state() private _newBoardName = ''\n\n /** review step — 새 Board 설명 (자세한 narrative). */\n @state() private _newBoardDescription = ''\n\n /** review step — 사용자가 선택한 시안 id. 미선택 시 default (첫 variant). */\n @state() private _selectedVariantId: string = ''\n\n /** materialize 진행 중 — 중복 클릭 방지. */\n @state() private _materializing = false\n\n /** materialize 결과 Board id — done step 에서 모델러로 navigate 시 사용. */\n @state() private _newBoardId?: string\n\n /** materialize / done 단계 에러. */\n @state() private _materializeError?: string\n\n /** wizard 제목 / 설명 — page header 에 노출. */\n get context() {\n return {\n title: i18next.t('title.create board') || 'Create Board',\n board_topmenu: false\n }\n }\n\n override render() {\n return html`\n <div class=\"header\">\n <h1>${i18next.t('title.create board') || 'Create Board'}</h1>\n </div>\n\n ${this._renderModeTabs()}\n\n <div class=\"stage\">\n ${this._uploadMode === 'blank'\n ? this._renderFromScratchTab()\n : this._uploadMode === 'template'\n ? this._renderFromTemplateTab()\n : this._renderFileWizard()}\n </div>\n `\n }\n\n private _renderModeTabs() {\n // 파일 위자드가 upload 단계를 지나면 탭 전환 불가 (진행 중 이탈 방지)\n const locked = this._isFileMode() && this._step !== 'upload'\n return html`\n <div class=\"mode-tabs ${locked ? 'locked' : ''}\">\n <button\n class=\"mode-tab ${this._uploadMode === 'blank' ? 'active' : ''}\"\n ?disabled=${locked}\n @click=${() => this._setUploadMode('blank')}\n >\n ${i18next.t('label.from-scratch') || 'From Scratch'}\n </button>\n <button\n class=\"mode-tab ${this._uploadMode === 'template' ? 'active' : ''}\"\n ?disabled=${locked}\n @click=${() => this._setUploadMode('template')}\n >\n ${i18next.t('label.from-template') || 'From Template'}\n </button>\n <button\n class=\"mode-tab ${this._uploadMode === 'new' ? 'active' : ''}\"\n ?disabled=${locked}\n @click=${() => this._setUploadMode('new')}\n >\n ${i18next.t('label.from-new-drawing') || 'From New Drawing'}\n </button>\n <button\n class=\"mode-tab ${this._uploadMode === 'existing' ? 'active' : ''}\"\n ?disabled=${locked}\n @click=${() => this._setUploadMode('existing')}\n >\n ${i18next.t('label.from-existing-drawing') || 'From Existing Drawing'}\n </button>\n </div>\n `\n }\n\n private _renderFileWizard() {\n return html`\n ${this._renderStepper()}\n ${this._step === 'upload' ? this._renderFileUploadUI() : ''}\n ${this._step === 'progress' ? this._renderProgressStep() : ''}\n ${this._step === 'review' ? this._renderReviewStep() : ''}\n ${this._step === 'done' ? this._renderDoneStep() : ''}\n `\n }\n\n private _renderStepper() {\n const firstLabel =\n this._uploadMode === 'existing'\n ? i18next.t('label.select file') || 'Select File'\n : i18next.t('label.upload') || 'Upload'\n const steps: Array<{ key: WizardStep; label: string }> = [\n { key: 'upload', label: firstLabel },\n { key: 'progress', label: i18next.t('label.import progress') || 'Conversion Progress' },\n { key: 'review', label: i18next.t('label.review') || 'Review' },\n { key: 'done', label: i18next.t('label.done') || 'Done' }\n ]\n const activeIndex = steps.findIndex(s => s.key === this._step)\n return html`\n <div class=\"stepper\">\n ${steps.map((s, i) => {\n const cls =\n i < activeIndex ? 'step done' : i === activeIndex ? 'step active' : 'step'\n return html`\n ${i > 0 ? html`<span class=\"arrow\">›</span>` : ''}\n <span class=${cls}>\n <span class=\"index\">${i + 1}</span>\n ${s.label}\n </span>\n `\n })}\n </div>\n `\n }\n\n // ── upload step (파일 모드) ──────────────────────────────────────\n\n private _renderFileUploadUI() {\n return html`\n ${this._uploadError ? html`<div class=\"error-banner\">${this._uploadError}</div>` : ''}\n ${this._uploadMode === 'new' ? this._renderNewUpload() : this._renderBrowseExisting()}\n ${this._attachment ? this._renderSelectedPreview() : ''}\n `\n }\n\n /** \"처음부터\" 탭 — 빈 보드 생성 전용 단순 폼. 템플릿 없음. */\n private _renderFromScratchTab() {\n return html`\n <div class=\"quick-form\">\n ${this._renderBoardForm('blank')}\n </div>\n `\n }\n\n /** \"템플릿\" 탭 — existing files와 동일한 browse-item 카드 스타일. */\n private _renderFromTemplateTab() {\n const filtered = this._templateSearch\n ? this._templates.filter(\n t =>\n t.name.toLowerCase().includes(this._templateSearch.toLowerCase()) ||\n (t.description || '').toLowerCase().includes(this._templateSearch.toLowerCase())\n )\n : this._templates\n\n return html`\n <div class=\"template-layout\">\n <div>\n <div class=\"browse-toolbar\">\n <input\n class=\"browse-search\"\n type=\"search\"\n placeholder=${i18next.t('placeholder.search template') || 'Search templates'}\n .value=${this._templateSearch}\n @input=${(e: Event) => (this._templateSearch = (e.target as HTMLInputElement).value)}\n />\n <button\n class=\"secondary\"\n @click=${() => this._loadTemplates(true)}\n ?disabled=${this._templatesLoading}\n >\n ${i18next.t('button.refresh') || 'Refresh'}\n </button>\n </div>\n\n ${this._templatesLoading\n ? html`<div class=\"browse-empty\">${i18next.t('text.loading') || 'Loading...'}</div>`\n : filtered.length === 0\n ? html`<div class=\"browse-empty\">\n ${i18next.t('text.no templates') || 'No templates available.'}\n </div>`\n : html`\n <div class=\"browse-list\">\n ${filtered.map(\n t => html`\n <div\n class=\"browse-item ${this._selectedTemplate?.id === t.id ? 'selected' : ''}\"\n title=${t.name}\n @click=${() => this._selectTemplate(t.id)}\n >\n ${t.thumbnail\n ? html`<img class=\"thumb\" src=${t.thumbnail} />`\n : html`<div class=\"thumb-fallback\">BOARD</div>`}\n <div class=\"name\">${t.name}</div>\n ${t.description\n ? html`<div class=\"meta\">${t.description}</div>`\n : ''}\n </div>\n `\n )}\n </div>\n `}\n </div>\n\n <div class=\"quick-form\">\n ${this._renderBoardForm('template')}\n </div>\n </div>\n `\n }\n\n /** 공통 보드 생성 폼 (blank / template 모드 공유). */\n private _renderBoardForm(mode: 'blank' | 'template') {\n const canSubmit =\n !!this._effectiveName() && (mode === 'blank' || !!this._selectedTemplate?.model)\n return html`\n <div class=\"name-input\">\n <label for=\"board-name\">\n ${i18next.t('label.new board name') || 'New Board Name'} *\n </label>\n <input\n id=\"board-name\"\n type=\"text\"\n placeholder=${this._selectedTemplate?.name ?? (i18next.t('placeholder.board name') || 'New Board')}\n .value=${this._newBoardName}\n @input=${(e: Event) =>\n (this._newBoardName = (e.target as HTMLInputElement).value)}\n ?disabled=${this._materializing}\n />\n </div>\n\n <div class=\"description-input\">\n <label for=\"board-description\">\n ${i18next.t('label.board description') || 'Board Description'}\n </label>\n <textarea\n id=\"board-description\"\n .value=${this._newBoardDescription}\n @input=${(e: Event) =>\n (this._newBoardDescription = (e.target as HTMLTextAreaElement).value)}\n ?disabled=${this._materializing}\n placeholder=${i18next.t('placeholder.board description') || 'Detailed description (optional)'}\n ></textarea>\n </div>\n\n <div class=\"meta-input\">\n <div class=\"meta-field\">\n <label>${i18next.t('label.board-type') || 'Board Type'}</label>\n <div class=\"board-type-radios\">\n ${(['main', 'sub', 'popup'] as const).map(\n t => html`\n <label class=\"radio-label\">\n <input\n type=\"radio\"\n name=\"quick-board-type\"\n value=${t}\n .checked=${this._selectedBoardType === t}\n @change=${() => (this._selectedBoardType = t)}\n />\n ${t}\n </label>\n `\n )}\n </div>\n </div>\n <div class=\"meta-field\">\n <label for=\"quick-group\">${i18next.t('label.group') || 'Group'}</label>\n <select\n id=\"quick-group\"\n .value=${this._selectedGroupId}\n @change=${(e: Event) =>\n (this._selectedGroupId = (e.target as HTMLSelectElement).value)}\n ?disabled=${this._materializing}\n >\n <option value=\"\">${i18next.t('label.no group') || '(No Group)'}</option>\n ${this._groups.map(g => html`<option value=${g.id}>${g.name}</option>`)}\n </select>\n </div>\n </div>\n\n ${this._materializeError\n ? html`<div class=\"error-banner\">${this._materializeError}</div>`\n : ''}\n\n <div class=\"controls\">\n <button\n class=\"primary\"\n @click=${this._materialize}\n ?disabled=${this._materializing || !canSubmit}\n >\n ${this._materializing\n ? i18next.t('text.saving') || 'Saving...'\n : i18next.t('button.create board') || 'Create Board'}\n </button>\n </div>\n `\n }\n\n private _renderNewUpload() {\n return html`\n <label\n class=\"drop-zone\"\n @dragover=${(e: DragEvent) => {\n e.preventDefault()\n ;(e.currentTarget as HTMLElement).classList.add('dragover')\n }}\n @dragleave=${(e: DragEvent) => {\n ;(e.currentTarget as HTMLElement).classList.remove('dragover')\n }}\n @drop=${this._onDrop}\n >\n <md-icon class=\"icon\">upload</md-icon>\n <div class=\"title\">${this._uploading ? i18next.t('text.uploading') || 'Uploading...' : i18next.t('text.drop or click to upload') || 'Drop a drawing here or click to browse'}</div>\n <div class=\"hint\">${ACCEPT}</div>\n <input\n type=\"file\"\n accept=${ACCEPT}\n ?disabled=${this._uploading}\n @change=${this._onFileChange}\n />\n </label>\n `\n }\n\n private _renderBrowseExisting() {\n return html`\n <div class=\"browse-toolbar\">\n <input\n class=\"browse-search\"\n type=\"search\"\n placeholder=${i18next.t('placeholder.search file') || 'Search by filename'}\n .value=${this._browseSearch}\n @input=${(e: Event) => {\n this._browseSearch = (e.target as HTMLInputElement).value\n this._refreshBrowse()\n }}\n />\n <button\n class=\"secondary\"\n @click=${() => this._refreshBrowse()}\n ?disabled=${this._browseLoading}\n >\n ${i18next.t('button.refresh') || 'Refresh'}\n </button>\n </div>\n\n ${this._browseLoading\n ? html`<div class=\"browse-empty\">${i18next.t('text.loading') || 'Loading...'}</div>`\n : this._browseItems.length === 0\n ? html`<div class=\"browse-empty\">\n ${i18next.t('text.no existing files') ||\n 'No drawing or image files found. Use the \\'From New Drawing\\' tab to upload.'}\n </div>`\n : html`\n <div class=\"browse-list\">\n ${this._browseItems.map(\n item => html`\n <div\n class=\"browse-item\"\n title=${item.name}\n @click=${() => this._selectExisting(item)}\n >\n ${this._isImageMime(item.mimetype)\n ? html`<img class=\"thumb\" src=\"/attachment/${item.path}\" />`\n : html`<div class=\"thumb-fallback\">\n ${(item.mimetype?.split('/')[1] || 'file').toUpperCase()}\n </div>`}\n <div class=\"name\">${item.name}</div>\n <div class=\"meta\">\n ${item.size ? this._formatSize(item.size) : ''}\n ${item.mimetype ? ' · ' + item.mimetype : ''}\n </div>\n </div>\n `\n )}\n </div>\n `}\n `\n }\n\n /**\n * 선택된 attachment 의 미리보기 + 변환 시작 버튼. 두 모드 (new/existing) 공통 렌더링.\n * 이미지 mimetype 이면 실제 이미지를 띄우고, 아니면 메타정보 카드만.\n * 추가로 사용자 prompt textarea — VLM 의 view type 분류 + entity 추출에 hint 로 합류.\n */\n private _renderSelectedPreview() {\n if (!this._attachment) return ''\n const a = this._attachment\n const isImage = this._isImageMime(a.mimetype)\n return html`\n <div class=\"preview-pane\">\n ${isImage\n ? html`<img class=\"preview-image\" src=\"/attachment/${a.path}\" />`\n : html`<div class=\"preview-fallback\">\n ${(a.mimetype || 'file').toUpperCase()}\n </div>`}\n <div class=\"info-row\">\n <span class=\"label\">${i18next.t('label.name') || 'Name'}</span>\n <span>${a.name}</span>\n </div>\n <div class=\"info-row\">\n <span class=\"label\">${i18next.t('label.mimetype') || 'MIME'}</span>\n <span>${a.mimetype || 'unknown'}</span>\n </div>\n <div class=\"info-row\">\n <span class=\"label\">${i18next.t('label.size') || 'Size'}</span>\n <span>${a.size ? this._formatSize(a.size) : '-'}</span>\n </div>\n </div>\n\n <div class=\"user-prompt\">\n <label>\n ${i18next.t('label.user prompt') || 'Drawing notes / key features'}\n <span class=\"hint\">\n ${i18next.t('hint.user prompt') ||\n 'Optional — e.g. \"FAB lithography zone, large rectangles are stockers, thin lines are OHT rails\"'}\n </span>\n </label>\n <textarea\n rows=\"3\"\n .value=${this._userPrompt}\n @input=${(e: Event) => (this._userPrompt = (e.target as HTMLTextAreaElement).value)}\n ?disabled=${this._uploading}\n placeholder=${i18next.t('placeholder.user prompt') ||\n 'Add notes about this drawing (optional)'}\n ></textarea>\n </div>\n\n <div class=\"controls\">\n <button class=\"secondary\" @click=${this._resetUpload} ?disabled=${this._uploading}>\n ${i18next.t('button.choose another') || 'Choose Another File'}\n </button>\n <button class=\"primary\" @click=${this._proceedToImport} ?disabled=${this._uploading}>\n ${i18next.t('button.start import') || 'Start Conversion'}\n </button>\n </div>\n `\n }\n\n private _isImageMime(mime?: string): boolean {\n return !!mime && mime.toLowerCase().startsWith('image/')\n }\n\n private async _setUploadMode(mode: UploadMode) {\n this._uploadMode = mode\n if (mode === 'existing' && this._browseItems.length === 0 && !this._browseLoading) {\n await this._refreshBrowse()\n }\n if (mode === 'template' && !this._templatesLoaded && !this._templatesLoading) {\n await this._loadTemplates()\n }\n // blank/template 진입 시 group 목록 lazy fetch (review step 의 group selector 용)\n if ((mode === 'blank' || mode === 'template') && !this._groupsLoaded) {\n await this._loadGroups()\n }\n }\n\n private async _loadGroups() {\n try {\n const res = await fetchGroupList()\n const items = res?.groups?.items\n this._groups = Array.isArray(items) ? items : []\n } catch (err) {\n console.warn('[board-create-wizard] fetchGroupList failed', err)\n this._groups = []\n }\n this._groupsLoaded = true\n }\n\n private async _loadTemplates(force = false) {\n if (this._templatesLoaded && !force) return\n this._templatesLoading = true\n try {\n const data = await fetchBoardTemplateList({ pagination: { limit: 100, page: 1 } })\n this._templates = data?.boardTemplates?.items || []\n this._templatesLoaded = true\n } catch (err) {\n console.warn('[board-create-wizard] fetchBoardTemplateList failed', err)\n this._templates = []\n }\n this._templatesLoading = false\n }\n\n private async _selectTemplate(id: string) {\n const data = await fetchBoardTemplate(id)\n let template = data?.boardTemplate\n if (template?.model) template = { ...template, model: JSON.parse(template.model) }\n this._selectedTemplate = template || undefined\n if (template?.name && !this._newBoardName) {\n this._newBoardName = template.name\n }\n }\n\n /**\n * 기존 attachment 목록 fetch — 이미지 + DXF 만 (mimetype prefix 'image' 1차 + 검색은 파일명).\n * mimetype 단일 prefix 만 지원하는 백엔드 한계로 image / application 분리 query 가 필요하지만,\n * MVP 는 두 번 fetch 해서 합치는 단순 방식.\n */\n private async _refreshBrowse() {\n this._browseLoading = true\n try {\n const [imgs, others] = await Promise.all([\n fetchAttachmentsForImport({\n search: this._browseSearch || undefined,\n mimetypePrefix: 'image',\n limit: 30\n }),\n fetchAttachmentsForImport({\n search: (this._browseSearch || '') + '%.dxf',\n limit: 20\n })\n ])\n // 단순 합치기 — image first, then DXF, dedupe by id\n const seen = new Set<string>()\n const merged: AttachmentResult[] = []\n for (const it of [...imgs.items, ...others.items]) {\n if (!seen.has(it.id)) {\n seen.add(it.id)\n merged.push(it)\n }\n }\n this._browseItems = merged\n } catch (err: any) {\n notify({ level: 'error', message: err?.message || String(err) })\n this._browseItems = []\n } finally {\n this._browseLoading = false\n }\n }\n\n private _selectExisting(item: AttachmentResult) {\n this._attachment = item\n this._uploadError = undefined\n }\n\n private async _onFileChange(e: Event) {\n const input = e.target as HTMLInputElement\n const file = input.files?.[0]\n if (file) await this._uploadFile(file)\n // value 초기화 — 같은 파일 재선택 가능\n input.value = ''\n }\n\n private async _onDrop(e: DragEvent) {\n e.preventDefault()\n ;(e.currentTarget as HTMLElement).classList.remove('dragover')\n const file = e.dataTransfer?.files?.[0]\n if (file) await this._uploadFile(file)\n }\n\n private async _uploadFile(file: File) {\n this._uploading = true\n this._uploadError = undefined\n try {\n const result = await createAttachmentForImport(file)\n this._attachment = result\n } catch (err: any) {\n const message = err?.message || String(err)\n this._uploadError = message\n notify({ level: 'error', message })\n } finally {\n this._uploading = false\n }\n }\n\n private _resetUpload() {\n this._attachment = undefined\n this._uploadError = undefined\n }\n\n private async _proceedToImport() {\n if (!this._attachment) return\n this._step = 'progress'\n this._importError = undefined\n this._session = undefined\n try {\n const session = await importBoardAsync({\n attachmentId: this._attachment.id,\n userPrompt: this._userPrompt.trim() || undefined\n })\n this._session = session\n this._startPolling()\n } catch (err: any) {\n const message = err?.message || String(err)\n this._importError = message\n notify({ level: 'error', message })\n }\n }\n\n // ── progress step ───────────────────────────────────────────────\n\n /**\n * polling — 1초 간격으로 fetchImportSession.\n * - status='completed' → review step\n * - status='failed' → error 표시 + 재시도 가능\n * - 그 외 → progress UI 갱신 후 다시 polling\n *\n * 취소 시점: pollingTimer 명시 clear (다음 단계 진입 / 페이지 떠남).\n */\n private _startPolling() {\n this._stopPolling()\n const tick = async () => {\n if (!this._session?.id) return\n try {\n const fresh = await fetchImportSession(this._session.id)\n this._session = fresh\n if (fresh?.status === 'completed') {\n this._stopPolling()\n this._step = 'review'\n // 보드 이름 자동 추천 — 충돌 회피된 이름을 서버에서 받아서 input default 로.\n // 이미 사용자가 수정한 값이 있으면 덮어쓰지 않음.\n if (!this._newBoardName && !this._newBoardDescription) {\n try {\n const meta = await suggestBoardMeta({ sessionId: fresh.id })\n // 사용자가 이미 수정한 값 보존 — name/description 각각 독립 체크\n if (meta?.name && !this._newBoardName) this._newBoardName = meta.name\n if (meta?.description && !this._newBoardDescription) {\n this._newBoardDescription = meta.description\n }\n } catch (err) {\n console.warn('[board-create-wizard] suggestBoardMeta failed', err)\n }\n }\n return\n }\n if (fresh?.status === 'failed') {\n this._stopPolling()\n this._importError = fresh.message || 'Import failed'\n return\n }\n } catch (err: any) {\n // polling 일시적 에러는 무시 — 다음 tick 에 재시도. 단 너무 자주면 사용자에 노출.\n console.warn('[board-create-wizard] polling error', err)\n }\n this._pollingTimer = window.setTimeout(tick, 1000) as unknown as number\n }\n // 즉시 1회 + 이후 setTimeout chain\n this._pollingTimer = window.setTimeout(tick, 0) as unknown as number\n }\n\n private _stopPolling() {\n if (this._pollingTimer) {\n clearTimeout(this._pollingTimer)\n this._pollingTimer = undefined\n }\n }\n\n private _renderProgressStep() {\n if (this._importError) {\n return html`\n <div class=\"error-banner\">${this._importError}</div>\n <div class=\"controls\">\n <button class=\"secondary\" @click=${this._retryFromUpload}>\n ${i18next.t('button.retry') || 'Retry'}\n </button>\n </div>\n `\n }\n const session = this._session\n const progress = session?.progress ?? 0\n const status = session?.status ?? 'queued'\n const message = session?.message ?? ''\n return html`\n <div class=\"progress-card\">\n <div class=\"progress-status\">\n <span class=\"label\">${this._statusLabel(status)}</span>\n <span class=\"pct\">${Math.round(progress)}%</span>\n </div>\n <div class=\"progress-bar\">\n <div class=\"fill\" style=\"width: ${Math.min(100, Math.max(0, progress))}%\"></div>\n </div>\n ${message ? html`<div class=\"progress-message\">${message}</div>` : ''}\n </div>\n `\n }\n\n private _statusLabel(status: string): string {\n switch (status) {\n case 'queued': return i18next.t('label.queued') || 'Queued'\n case 'parsing': return i18next.t('label.parsing') || 'Parsing'\n case 'mapping': return i18next.t('label.mapping') || 'Mapping'\n case 'assembling': return i18next.t('label.assembling') || 'Assembling Board'\n case 'binding': return i18next.t('label.binding') || 'Data Binding'\n case 'completed': return i18next.t('label.completed') || 'Completed'\n case 'failed': return i18next.t('label.failed') || 'Failed'\n default: return status\n }\n }\n\n private _retryFromUpload() {\n this._stopPolling()\n this._session = undefined\n this._importError = undefined\n this._step = 'upload'\n }\n\n override disconnectedCallback() {\n this._stopPolling()\n super.disconnectedCallback()\n }\n\n /**\n * step 이나 session 변경 시 polling 상태 동기화. step='progress' + session 존재 + 종료 안된\n * 상태에서만 polling 활성화. 그 외 상태로 진입하면 자동 정리.\n */\n override updated(changes: PropertyValues) {\n super.updated(changes)\n if (changes.has('_step' as any) || changes.has('_session' as any)) {\n const session = this._session\n const inProgress = this._step === 'progress'\n const sessionAlive =\n session && session.status !== 'completed' && session.status !== 'failed'\n if (inProgress && sessionAlive && !this._pollingTimer) {\n this._startPolling()\n } else if (!inProgress || !sessionAlive) {\n this._stopPolling()\n }\n }\n }\n\n // ── review step ─────────────────────────────────────────────────\n\n private _renderReviewStep() {\n return html`\n ${this._materializeError\n ? html`<div class=\"error-banner\">${this._materializeError}</div>`\n : ''}\n\n ${this._renderFileReviewBody()}\n\n <div class=\"name-input\">\n <label for=\"board-name\">${i18next.t('label.new board name') || 'New Board Name'}</label>\n <input\n id=\"board-name\"\n type=\"text\"\n placeholder=${this._attachment?.name?.replace(/\\.[^.]+$/, '') ??\n this._selectedTemplate?.name ?? (i18next.t('placeholder.board name') || 'New Board')}\n .value=${this._newBoardName}\n @input=${(e: Event) => (this._newBoardName = (e.target as HTMLInputElement).value)}\n ?disabled=${this._materializing}\n />\n </div>\n\n <div class=\"description-input\">\n <label for=\"board-description\">\n ${i18next.t('label.board description') || 'Board Description'}\n </label>\n <textarea\n id=\"board-description\"\n rows=\"3\"\n .value=${this._newBoardDescription}\n @input=${(e: Event) =>\n (this._newBoardDescription = (e.target as HTMLTextAreaElement).value)}\n ?disabled=${this._materializing}\n placeholder=${i18next.t('placeholder.board description') ||\n 'Detailed description (optional)'}\n ></textarea>\n </div>\n\n <div class=\"meta-input\">\n <div class=\"meta-field\">\n <label>${i18next.t('label.board-type') || 'Board Type'}</label>\n <div class=\"board-type-radios\">\n ${(['main', 'sub', 'popup'] as const).map(\n t => html`\n <label class=\"radio-label\">\n <input\n type=\"radio\"\n name=\"board-type\"\n value=${t}\n .checked=${this._selectedBoardType === t}\n @change=${() => (this._selectedBoardType = t)}\n />\n ${t}\n </label>\n `\n )}\n </div>\n </div>\n <div class=\"meta-field\">\n <label for=\"board-group\">${i18next.t('label.group') || 'Group'}</label>\n <select\n id=\"board-group\"\n .value=${this._selectedGroupId}\n @change=${(e: Event) =>\n (this._selectedGroupId = (e.target as HTMLSelectElement).value)}\n ?disabled=${this._materializing}\n >\n <option value=\"\">\n ${i18next.t('label.no group') || '(No Group)'}\n </option>\n ${this._groups.map(\n g => html`<option value=${g.id}>${g.name}</option>`\n )}\n </select>\n </div>\n </div>\n\n <div class=\"controls\">\n <button class=\"secondary\" @click=${this._retryFromUpload} ?disabled=${this._materializing}>\n ${i18next.t('button.start over') || 'Start Over'}\n </button>\n <button\n class=\"primary\"\n @click=${this._materialize}\n ?disabled=${this._materializing || !this._effectiveName()}\n >\n ${this._materializing\n ? i18next.t('text.saving') || 'Saving...'\n : i18next.t('button.save as new board') || 'Save as New Board'}\n </button>\n </div>\n `\n }\n\n /** 파일 import 모드 여부 — true 면 variants/AI 분석 표시, false 면 단순 정보 입력. */\n private _isFileMode(): boolean {\n return this._uploadMode === 'new' || this._uploadMode === 'existing'\n }\n\n /** review 의 파일 모드 본문 — variants picker + AI 분석 + 큰 preview + stats + warnings. */\n private _renderFileReviewBody() {\n const result = this._session?.result\n const variants: any[] = Array.isArray(result?.variants) ? result.variants : []\n const selected = this._getSelectedVariant(variants, result)\n const stats = selected?.stats ?? result?.stats ?? {}\n const warnings: string[] = (selected?.warnings ?? result?.warnings ?? []).map((w: any) =>\n typeof w === 'string' ? w : w.message ?? JSON.stringify(w)\n )\n return html`\n ${variants.length > 1 ? this._renderVariantPicker(variants, selected) : ''}\n ${this._renderAISummary(result, selected)}\n\n ${selected?.boardModel\n ? html`\n <div class=\"big-preview\">\n <ox-board-preview\n .boardModel=${selected.boardModel}\n interactive\n ></ox-board-preview>\n </div>\n `\n : ''}\n\n <div class=\"review-stats\">\n <div class=\"stat\">\n <span class=\"label\">${i18next.t('label.entities') || 'Entities'}</span>\n <span class=\"value\">${stats.total ?? '?'}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">${i18next.t('label.matched') || 'Matched'}</span>\n <span class=\"value\">${stats.matched ?? 0}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">${i18next.t('label.generic') || 'Generic'}</span>\n <span class=\"value\">${stats.generic ?? 0}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">${i18next.t('label.warnings') || 'Warnings'}</span>\n <span class=\"value\">${warnings.length}</span>\n </div>\n </div>\n\n ${warnings.length > 0\n ? html`\n <div class=\"warnings\">\n <strong>${i18next.t('label.warnings') || 'Warnings'}:</strong>\n <ul>\n ${warnings.slice(0, 20).map(w => html`<li>${w}</li>`)}\n ${warnings.length > 20\n ? html`<li>… +${warnings.length - 20} more</li>`\n : ''}\n </ul>\n </div>\n `\n : ''}\n `\n }\n\n /**\n * 사용자가 선택한 시안 — _selectedVariantId 우선, 없으면 result.defaultVariantId, 없으면 첫 시안.\n */\n private _getSelectedVariant(variants: any[], result: any): any | undefined {\n if (variants.length === 0) return undefined\n if (this._selectedVariantId) {\n const found = variants.find(v => v.id === this._selectedVariantId)\n if (found) return found\n }\n if (result?.defaultVariantId) {\n const found = variants.find(v => v.id === result.defaultVariantId)\n if (found) return found\n }\n return variants[0]\n }\n\n /**\n * 3 시안 picker — 썸네일 + 라벨 + 클릭 시 선택. 시안이 1개 이하면 안 그림.\n * 사용자가 다른 시안 선택 시 _selectedVariantId 갱신 → re-render → 큰 preview / stats / 추천 갱신.\n */\n private _renderVariantPicker(variants: any[], selected: any) {\n return html`\n <div class=\"variant-picker\">\n <div class=\"variant-picker-header\">\n <strong>${i18next.t('label.choose variant') || 'Choose AI Variant'}</strong>\n <span class=\"variant-picker-hint\">\n ${i18next.t('hint.choose variant') ||\n 'Different AI interpretations of the same drawing. Choose the most accurate one.'}\n </span>\n <button\n class=\"regenerate-btn\"\n @click=${this._onRegenerateVariants}\n ?disabled=${this._materializing}\n title=${i18next.t('hint.regenerate variants') ||\n 'Regenerate if none of the variants look right'}\n >\n ↻ ${i18next.t('button.regenerate') || 'Regenerate'}\n </button>\n </div>\n <div class=\"variant-grid\">\n ${variants.map(\n v => html`\n <div\n class=\"variant-card ${selected?.id === v.id ? 'selected' : ''}\"\n @click=${() => this._onSelectVariant(v.id)}\n >\n <div class=\"variant-thumb\">\n <ox-board-preview .boardModel=${v.boardModel}></ox-board-preview>\n </div>\n <div class=\"variant-meta\">\n <div class=\"variant-label\">${v.label || v.id}</div>\n ${v.description\n ? html`<div class=\"variant-desc\">${v.description}</div>`\n : ''}\n <div class=\"variant-stats\">\n ${v.stats?.total ?? 0}개 객체\n ${v.stats?.matched ? html` · ${v.stats.matched} 매핑` : ''}\n </div>\n </div>\n </div>\n `\n )}\n </div>\n </div>\n `\n }\n\n private _onSelectVariant(variantId: string) {\n if (this._selectedVariantId === variantId) return\n this._selectedVariantId = variantId\n // 선택된 variant 의 subjectName / subjectDescription 으로 자동 채움 — 사용자가 이미\n // 수정한 값은 보존하지 않음 (시안 바뀌면 새 정체성이 더 적절).\n const variant = (this._session?.result?.variants ?? []).find((v: any) => v.id === variantId)\n if (variant?.subjectName) {\n // shortenName 같은 정규화는 서버 suggestBoardMeta 가 이미 처리. 여기는 그대로.\n this._newBoardName = variant.subjectName.slice(0, 30).trim() || this._newBoardName\n }\n if (variant?.subjectDescription) {\n this._newBoardDescription = variant.subjectDescription\n }\n }\n\n private async _onRegenerateVariants() {\n if (!this._attachment) return\n // 기존 review 결과 폐기, progress 단계로 되돌리고 import 다시 시작.\n this._step = 'progress'\n this._session = undefined\n this._selectedVariantId = ''\n this._materializeError = undefined\n try {\n const session = await importBoardAsync({\n attachmentId: this._attachment.id,\n userPrompt: this._userPrompt.trim() || undefined\n })\n this._session = session\n this._startPolling()\n } catch (err: any) {\n const message = err?.message || String(err)\n this._importError = message\n notify({ level: 'error', message })\n }\n }\n\n /**\n * AI 분석 요약 카드 — VLM 이 도면을 어떻게 이해했고 어떤 관점으로 import 를 진행했는지\n * 사용자 review 에 노출. metadata 가 비어있으면 (DXF 임포트 등 VLM 미사용 경로) 카드 자체 생략.\n *\n * selected variant 의 importStrategy / subjectName / subjectDescription 은 시안별 전용.\n */\n private _renderAISummary(result: any, selectedVariant?: any) {\n const meta = result?.metadata\n if (!meta) return ''\n const viewType = meta.viewType as string | undefined\n const confidence = typeof meta.viewTypeConfidence === 'number' ? meta.viewTypeConfidence : undefined\n const reasoning = meta.viewTypeReasoning as string | undefined\n // importStrategy 는 selected variant 우선, 없으면 metadata 의 (default) 사용\n const importStrategy = (selectedVariant?.importStrategy ?? meta.importStrategy) as\n | string\n | undefined\n const userPrompt = meta.userPrompt as string | undefined\n const dist = result?.categoryDistribution as Record<string, number> | undefined\n const aiClient = meta.aiClientId as string | undefined\n const imageDims =\n meta.imageWidth && meta.imageHeight ? `${meta.imageWidth}×${meta.imageHeight}px` : undefined\n\n const viewTypeLabel = (() => {\n switch (viewType) {\n case 'top-down':\n return i18next.t('label.viewtype top-down') || 'Floor Plan (top-down)'\n case 'perspective-3d':\n return i18next.t('label.viewtype perspective-3d') || '3D Render / Bird\\'s Eye'\n case 'photo':\n return i18next.t('label.viewtype photo') || 'Photo'\n default:\n return viewType || '-'\n }\n })()\n\n return html`\n <div class=\"ai-summary\">\n <div class=\"title\">🔍 ${i18next.t('label.ai analysis') || 'AI Analysis Summary'}</div>\n\n <div class=\"row\">\n <span class=\"label\">${i18next.t('label.view type') || 'Drawing Type'}</span>\n <span class=\"value\">\n <span class=\"badge\">${viewTypeLabel}</span>\n ${typeof confidence === 'number'\n ? html`<span class=\"badge\">${i18next.t('label.confidence') || 'Confidence'} ${Math.round(confidence * 100)}%</span>`\n : ''}\n </span>\n </div>\n\n ${reasoning\n ? html`\n <div class=\"row\">\n <span class=\"label\">${i18next.t('label.reasoning') || 'Reasoning'}</span>\n <span class=\"value\">${reasoning}</span>\n </div>\n `\n : ''}\n ${importStrategy\n ? html`\n <div class=\"strategy\">\n <div class=\"strategy-label\">\n 💡 ${i18next.t('label.ai intent') || 'AI Intent / Approach'}\n </div>\n <div class=\"strategy-text\">${importStrategy}</div>\n </div>\n `\n : ''}\n ${userPrompt\n ? html`\n <div class=\"row\">\n <span class=\"label\">${i18next.t('label.user hint') || 'User Hint'}</span>\n <span class=\"value\">\"${userPrompt}\"</span>\n </div>\n `\n : ''}\n ${dist && Object.keys(dist).length > 0\n ? html`\n <div class=\"row\">\n <span class=\"label\">${i18next.t('label.categories') || 'Categories'}</span>\n <span class=\"value\">\n ${Object.entries(dist).map(\n ([cat, n]) => html`<span class=\"badge\">${cat} ${n}</span>`\n )}\n </span>\n </div>\n `\n : ''}\n ${imageDims\n ? html`\n <div class=\"row\">\n <span class=\"label\">${i18next.t('label.image size') || 'Image Size'}</span>\n <span class=\"value\">${imageDims}</span>\n </div>\n `\n : ''}\n ${aiClient\n ? html`\n <div class=\"row\">\n <span class=\"label\">${i18next.t('label.ai client') || 'AI Model'}</span>\n <span class=\"value\">${aiClient}</span>\n </div>\n `\n : ''}\n ${viewType === 'perspective-3d' || viewType === 'photo'\n ? html`\n <div class=\"row\">\n <span class=\"label\" style=\"color:#a0892a;\">⚠️</span>\n <span class=\"value\" style=\"color:#7a5e00;font-style:italic;\">\n ${i18next.t('text.perspective notice') ||\n 'Perspective drawings are difficult to infer floor plan positions — positional accuracy may be low. Please verify and adjust.'}\n </span>\n </div>\n `\n : ''}\n </div>\n `\n }\n\n /** 사용자 입력 또는 첨부파일 이름 (확장자 제거) 을 fallback. 빈 input 일 때만 fallback. */\n private _effectiveName(): string {\n if (this._newBoardName.trim()) return this._newBoardName.trim()\n if (this._attachment?.name) return this._attachment.name.replace(/\\.[^.]+$/, '')\n return ''\n }\n\n private async _materialize() {\n const name = this._effectiveName()\n if (!name) return\n if (this._isFileMode() && !this._session?.id) return\n this._materializing = true\n this._materializeError = undefined\n try {\n const description = this._newBoardDescription.trim()\n const groupId = this._selectedGroupId || undefined\n const type = this._selectedBoardType\n let boardId: string | undefined\n\n if (this._isFileMode()) {\n // import 결과 → materializeImportSession (variant 선택)\n const variantId = this._selectedVariantId || undefined\n const board = await materializeImportSession({\n sessionId: this._session!.id,\n name,\n ...(description ? { description } : {}),\n ...(groupId ? { groupId } : {}),\n type,\n ...(variantId ? { variantId } : {})\n })\n boardId = board?.id\n } else {\n // blank / template — 직접 createBoard.\n const model =\n this._uploadMode === 'template' && this._selectedTemplate?.model\n ? this._selectedTemplate.model\n : { width: 800, height: 600 }\n const thumbnail =\n this._uploadMode === 'template' ? this._selectedTemplate?.thumbnail : undefined\n const created = await createBoard({\n name,\n description,\n type,\n groupId: groupId || '',\n model,\n ...(thumbnail ? { thumbnail } : {})\n })\n boardId = created?.createBoard?.id ?? created?.id\n }\n\n if (!boardId) throw new Error('Board id missing in response.')\n this._newBoardId = boardId\n if (this._isFileMode()) {\n this._step = 'done'\n } else {\n // blank / template — 생성 즉시 모델러로 이동\n navigate(`board-modeller/${boardId}`)\n }\n } catch (err: any) {\n const message = err?.message || String(err)\n this._materializeError = message\n notify({ level: 'error', message })\n } finally {\n this._materializing = false\n }\n }\n\n // ── done step ───────────────────────────────────────────────────\n\n private _renderDoneStep() {\n return html`\n <div class=\"done-card\">\n <md-icon class=\"icon\">check_circle</md-icon>\n <div class=\"title\">${i18next.t('text.import success') || 'Board created successfully'}</div>\n <div class=\"meta\">\n ${this._effectiveName()} · ${i18next.t('label.draft') || 'Draft'}\n </div>\n </div>\n <div class=\"controls\">\n <button class=\"secondary\" @click=${this._retryFromUpload}>\n ${i18next.t('button.import another') || 'Import Another Drawing'}\n </button>\n <button class=\"primary\" @click=${this._goToModeller} ?disabled=${!this._newBoardId}>\n ${i18next.t('button.open modeller') || 'Open Modeller'}\n </button>\n </div>\n `\n }\n\n private _goToModeller() {\n if (!this._newBoardId) return\n navigate(`board-modeller/${this._newBoardId}`)\n }\n\n private _formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`\n }\n\n // 페이지 라이프사이클\n override pageUpdated(_changes: PropertyValues, _lifecycle: any) {\n if ((this as any).active) {\n // wizard 가 'done' 상태로 끝난 뒤 다시 진입하면 초기 상태로 리셋.\n if (this._step === 'done') {\n this._resetAll()\n }\n // 처음부터 탭이 디폴트 — 첫 진입 시 그룹 목록 로딩.\n if (!this._groupsLoaded) {\n this._loadGroups()\n }\n }\n }\n\n /** wizard 모든 state 를 초기값으로 — 'done' 후 재진입 / 처음부터 시작 시 사용. */\n private _resetAll() {\n this._step = 'upload'\n this._attachment = undefined\n this._session = undefined\n this._uploading = false\n this._uploadError = undefined\n this._uploadMode = 'blank'\n this._browseItems = []\n this._browseLoading = false\n this._browseSearch = ''\n this._userPrompt = ''\n this._importError = undefined\n this._newBoardName = ''\n this._newBoardDescription = ''\n this._selectedVariantId = ''\n this._selectedTemplate = undefined\n this._templates = []\n this._templatesLoading = false\n this._templatesLoaded = false\n this._templateSearch = ''\n this._selectedGroupId = ''\n this._selectedBoardType = 'main'\n this._materializing = false\n this._newBoardId = undefined\n this._materializeError = undefined\n }\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host 측 BoardEditOp dispatcher — AI 의 model 변경 op 를 things-scene 에 in-place
|
|
3
|
+
* 적용 + revert 용 inverse op 캡처.
|
|
4
|
+
*
|
|
5
|
+
* 핵심:
|
|
6
|
+
* - scene 의 in-place mutation API (scene.add / target.set / scene.remove 등)
|
|
7
|
+
* 로 적용 → commander 가 자동 snapshot push → CMD+Z / dirty / selection 보존.
|
|
8
|
+
* - 'replace' 는 부득이 wholesale 경로 (호스트가 별도 처리). dispatcher 는 받지 않음.
|
|
9
|
+
* - 각 op 의 inverse 를 적용 직전/직후 scene 상태로 계산해 함께 반환 — 호스트가
|
|
10
|
+
* 누적해 두면 나중에 같은 dispatcher 로 역순 적용해 revert 구현.
|
|
11
|
+
*
|
|
12
|
+
* Pure function — Lit element 외부에서 mock scene 으로 단위 테스트 가능.
|
|
13
|
+
*/
|
|
14
|
+
import type { BoardEditOp, ArrangeLayout } from '@things-factory/board-ai';
|
|
15
|
+
export interface DispatchContext {
|
|
16
|
+
/**
|
|
17
|
+
* `add` op 의 component 를 things-scene 의 default 와 deep-merge.
|
|
18
|
+
* 호스트가 BoardModeller 의 template registry 를 보고 채운다 — dispatcher 는 호출만.
|
|
19
|
+
* 미지정 시 component 를 그대로 scene.add 에 전달.
|
|
20
|
+
*/
|
|
21
|
+
normalize?: (c: any) => any;
|
|
22
|
+
}
|
|
23
|
+
export interface DispatchResult {
|
|
24
|
+
/** op 가 실제로 scene 에 반영됐는지. silent no-op (없는 refid 등) → false */
|
|
25
|
+
applied: boolean;
|
|
26
|
+
/** 적용 직전/직후 상태로 계산한 revert 용 inverse — 단일 op 가 다중 inverse 를
|
|
27
|
+
* 생성할 수 있음 (예: align 1개 → modify N개, group 1개 → ungroup 1개). */
|
|
28
|
+
inverseOps: BoardEditOp[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 단일 BoardEditOp 를 things-scene 에 in-place 적용.
|
|
32
|
+
*
|
|
33
|
+
* `replace` 는 받지 않음 (호스트의 wholesale 경로). 미지원 op / 누락 컴포넌트 시
|
|
34
|
+
* `applied: false` 반환. 호스트가 missed 로 분류해 사용자 경고.
|
|
35
|
+
*/
|
|
36
|
+
export declare function dispatchBoardEditOp(scene: any, op: BoardEditOp, ctx?: DispatchContext): DispatchResult;
|
|
37
|
+
/**
|
|
38
|
+
* Sugar layout 위치 계산 — grid / row / column.
|
|
39
|
+
*
|
|
40
|
+
* 정책:
|
|
41
|
+
* - left/top 만 변경. width/height 는 보존 (사용자 의도).
|
|
42
|
+
* - grid: cell size = max(width)/max(height) — 컴포넌트 사이즈 다를 때 겹침 방지.
|
|
43
|
+
* Row-major 채움.
|
|
44
|
+
* - row/column: 각 컴포넌트의 실 size + gap 으로 누적. align (start/center/end) 으로
|
|
45
|
+
* cross-axis 정렬.
|
|
46
|
+
* - anchor 미지정 시 첫 컴포넌트의 현재 (left, top) — 예측 가능.
|
|
47
|
+
*
|
|
48
|
+
* 입력: layout 정의 + 각 target 의 현재 (left, top) (anchor default 용) + sizes.
|
|
49
|
+
* 출력: 각 target 의 새 (left, top). targets 와 동일 순서.
|
|
50
|
+
*
|
|
51
|
+
* Pure function — host dispatcher 외부에서도 단위 테스트 가능.
|
|
52
|
+
*/
|
|
53
|
+
export declare function computeArrangePositions(layout: ArrangeLayout, current: Array<{
|
|
54
|
+
left: number;
|
|
55
|
+
top: number;
|
|
56
|
+
}>, sizes: Array<{
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
}>): Array<{
|
|
60
|
+
left: number;
|
|
61
|
+
top: number;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* scene 의 모든 컴포넌트 refid 수집 — add/group 의 inverse 계산용.
|
|
65
|
+
*
|
|
66
|
+
* scene.add / scene.group 직전·직후 호출해 차집합으로 새 발급 refid 식별.
|
|
67
|
+
*/
|
|
68
|
+
export declare function collectAllRefids(scene: any): number[];
|
|
69
|
+
/**
|
|
70
|
+
* patch 가 변경하려는 키들에 대해 model 의 현재 값을 deep-clone 으로 캡처.
|
|
71
|
+
* inverse modify / modifyBoard op 의 patch 로 사용. 원본에 없던 키는 null 로 보관
|
|
72
|
+
* (revert 시 명시적 null reset).
|
|
73
|
+
*/
|
|
74
|
+
export declare function captureOldKeys(model: any, patch: any): any;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { mergeComponent } from '@things-factory/board-ai';
|
|
2
|
+
import { findSceneComponent } from './board-action-dispatch';
|
|
3
|
+
const NOOP_RESULT = { applied: false, inverseOps: [] };
|
|
4
|
+
/**
|
|
5
|
+
* 단일 BoardEditOp 를 things-scene 에 in-place 적용.
|
|
6
|
+
*
|
|
7
|
+
* `replace` 는 받지 않음 (호스트의 wholesale 경로). 미지원 op / 누락 컴포넌트 시
|
|
8
|
+
* `applied: false` 반환. 호스트가 missed 로 분류해 사용자 경고.
|
|
9
|
+
*/
|
|
10
|
+
export function dispatchBoardEditOp(scene, op, ctx = {}) {
|
|
11
|
+
if (!scene || !op)
|
|
12
|
+
return NOOP_RESULT;
|
|
13
|
+
const normalize = ctx.normalize ?? ((c) => c);
|
|
14
|
+
switch (op.op) {
|
|
15
|
+
case 'add': {
|
|
16
|
+
const normalized = normalize(op.component);
|
|
17
|
+
// add 의 inverse 는 새로 발급될 refid 가 필요 → scene.add 직전/직후 차집합으로 캡처.
|
|
18
|
+
const prevRefids = new Set(collectAllRefids(scene));
|
|
19
|
+
scene.add(normalized, {});
|
|
20
|
+
const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r));
|
|
21
|
+
const inverseOps = newRefids.map(refid => ({ op: 'remove', refid }));
|
|
22
|
+
return { applied: true, inverseOps };
|
|
23
|
+
}
|
|
24
|
+
case 'remove': {
|
|
25
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
26
|
+
if (!target || !target.parent)
|
|
27
|
+
return NOOP_RESULT;
|
|
28
|
+
const savedModel = JSON.parse(JSON.stringify(target.model));
|
|
29
|
+
const prevSelected = scene.selected ?? [];
|
|
30
|
+
scene.selected = [target];
|
|
31
|
+
scene.remove();
|
|
32
|
+
scene.selected = prevSelected.filter((c) => c !== target);
|
|
33
|
+
return { applied: true, inverseOps: [{ op: 'add', component: savedModel }] };
|
|
34
|
+
}
|
|
35
|
+
case 'modify': {
|
|
36
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
37
|
+
if (!target)
|
|
38
|
+
return NOOP_RESULT;
|
|
39
|
+
const oldValues = captureOldKeys(target.model, op.patch);
|
|
40
|
+
const merged = mergeComponent(target.model, op.patch);
|
|
41
|
+
target.set(merged);
|
|
42
|
+
// change 는 자동 snapshot 안 함 → commander.execute 로 명시 push
|
|
43
|
+
scene.commander?.execute(null, false);
|
|
44
|
+
return {
|
|
45
|
+
applied: true,
|
|
46
|
+
inverseOps: [{ op: 'modify', refid: op.refid, patch: oldValues }]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
case 'modifyBoard': {
|
|
50
|
+
const root = scene.root;
|
|
51
|
+
if (!root || typeof root.set !== 'function')
|
|
52
|
+
return NOOP_RESULT;
|
|
53
|
+
const cleanPatch = { ...(op.patch || {}) };
|
|
54
|
+
delete cleanPatch.components; // 자식 변경은 별도 op
|
|
55
|
+
const oldValues = captureOldKeys(root.model, cleanPatch);
|
|
56
|
+
const merged = mergeComponent(root.model, cleanPatch);
|
|
57
|
+
root.set(merged);
|
|
58
|
+
scene.commander?.execute(null, false);
|
|
59
|
+
return { applied: true, inverseOps: [{ op: 'modifyBoard', patch: oldValues }] };
|
|
60
|
+
}
|
|
61
|
+
case 'align': {
|
|
62
|
+
const targets = op.refids
|
|
63
|
+
.map(r => findSceneComponent(scene, { refid: r }))
|
|
64
|
+
.filter((c) => c);
|
|
65
|
+
if (targets.length < 2)
|
|
66
|
+
return NOOP_RESULT;
|
|
67
|
+
const beforeBounds = targets.map((c) => ({
|
|
68
|
+
refid: c.get('refid'),
|
|
69
|
+
left: c.get('left'),
|
|
70
|
+
top: c.get('top'),
|
|
71
|
+
width: c.get('width'),
|
|
72
|
+
height: c.get('height')
|
|
73
|
+
}));
|
|
74
|
+
const prevSelected = scene.selected ?? [];
|
|
75
|
+
scene.selected = targets;
|
|
76
|
+
scene.align(op.direction);
|
|
77
|
+
scene.selected = prevSelected;
|
|
78
|
+
// 정렬 직전 좌표를 modify inverse 시퀀스로 — 정확한 좌표 복원
|
|
79
|
+
const inverseOps = beforeBounds.map(b => ({
|
|
80
|
+
op: 'modify',
|
|
81
|
+
refid: b.refid,
|
|
82
|
+
patch: { left: b.left, top: b.top, width: b.width, height: b.height }
|
|
83
|
+
}));
|
|
84
|
+
return { applied: true, inverseOps };
|
|
85
|
+
}
|
|
86
|
+
case 'distribute': {
|
|
87
|
+
const targets = op.refids
|
|
88
|
+
.map(r => findSceneComponent(scene, { refid: r }))
|
|
89
|
+
.filter((c) => c);
|
|
90
|
+
if (targets.length < 2)
|
|
91
|
+
return NOOP_RESULT;
|
|
92
|
+
const beforeBounds = targets.map((c) => ({
|
|
93
|
+
refid: c.get('refid'),
|
|
94
|
+
left: c.get('left'),
|
|
95
|
+
top: c.get('top')
|
|
96
|
+
}));
|
|
97
|
+
const prevSelected = scene.selected ?? [];
|
|
98
|
+
scene.selected = targets;
|
|
99
|
+
// things-scene API 는 'VERTICAL' / 'HORIZONTAL' 대문자
|
|
100
|
+
scene.distribute(op.axis === 'horizontal' ? 'HORIZONTAL' : 'VERTICAL');
|
|
101
|
+
scene.selected = prevSelected;
|
|
102
|
+
const inverseOps = beforeBounds.map(b => ({
|
|
103
|
+
op: 'modify',
|
|
104
|
+
refid: b.refid,
|
|
105
|
+
patch: { left: b.left, top: b.top }
|
|
106
|
+
}));
|
|
107
|
+
return { applied: true, inverseOps };
|
|
108
|
+
}
|
|
109
|
+
case 'group': {
|
|
110
|
+
const targets = op.refids
|
|
111
|
+
.map(r => findSceneComponent(scene, { refid: r }))
|
|
112
|
+
.filter((c) => c);
|
|
113
|
+
if (targets.length < 2)
|
|
114
|
+
return NOOP_RESULT;
|
|
115
|
+
const prevRefids = new Set(collectAllRefids(scene));
|
|
116
|
+
const prevSelected = scene.selected ?? [];
|
|
117
|
+
scene.selected = targets;
|
|
118
|
+
scene.group();
|
|
119
|
+
scene.selected = prevSelected;
|
|
120
|
+
const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r));
|
|
121
|
+
const inverseOps = newRefids.map(refid => ({ op: 'ungroup', refid }));
|
|
122
|
+
return { applied: true, inverseOps };
|
|
123
|
+
}
|
|
124
|
+
case 'ungroup': {
|
|
125
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
126
|
+
if (!target)
|
|
127
|
+
return NOOP_RESULT;
|
|
128
|
+
const childRefids = [];
|
|
129
|
+
const children = target.components ?? [];
|
|
130
|
+
for (const child of children) {
|
|
131
|
+
const r = child.get?.('refid');
|
|
132
|
+
if (typeof r === 'number')
|
|
133
|
+
childRefids.push(r);
|
|
134
|
+
}
|
|
135
|
+
const prevSelected = scene.selected ?? [];
|
|
136
|
+
scene.selected = [target];
|
|
137
|
+
scene.ungroup();
|
|
138
|
+
scene.selected = prevSelected.filter((c) => c !== target);
|
|
139
|
+
const inverseOps = childRefids.length >= 2 ? [{ op: 'group', refids: childRefids }] : [];
|
|
140
|
+
return { applied: true, inverseOps };
|
|
141
|
+
}
|
|
142
|
+
case 'zorder': {
|
|
143
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
144
|
+
if (!target)
|
|
145
|
+
return NOOP_RESULT;
|
|
146
|
+
const prevSelected = scene.selected ?? [];
|
|
147
|
+
scene.selected = [target];
|
|
148
|
+
scene.zorder(op.direction);
|
|
149
|
+
scene.selected = prevSelected;
|
|
150
|
+
// Best-effort: forward↔backward, front↔back. 정확하지 않을 수 있지만
|
|
151
|
+
// 단일 step 단순 반전이라 대부분 OK.
|
|
152
|
+
const opp = {
|
|
153
|
+
forward: 'backward',
|
|
154
|
+
backward: 'forward',
|
|
155
|
+
front: 'back',
|
|
156
|
+
back: 'front'
|
|
157
|
+
};
|
|
158
|
+
const dir = opp[op.direction];
|
|
159
|
+
const inverseOps = dir
|
|
160
|
+
? [{ op: 'zorder', refid: op.refid, direction: dir }]
|
|
161
|
+
: [];
|
|
162
|
+
return { applied: true, inverseOps };
|
|
163
|
+
}
|
|
164
|
+
case 'arrange': {
|
|
165
|
+
// Sugar layout — host 측에서 grid/row/column 위치 계산.
|
|
166
|
+
// align/distribute 와 달리 things-scene 의 native API 가 없으므로 직접 좌표 계산
|
|
167
|
+
// 해서 target.set() 으로 적용. left/top 만 변경, width/height 유지.
|
|
168
|
+
const targets = op.refids
|
|
169
|
+
.map(r => findSceneComponent(scene, { refid: r }))
|
|
170
|
+
.filter((c) => c);
|
|
171
|
+
if (targets.length < 2)
|
|
172
|
+
return NOOP_RESULT;
|
|
173
|
+
const beforePositions = targets.map((c) => ({
|
|
174
|
+
refid: c.get('refid'),
|
|
175
|
+
left: c.get('left'),
|
|
176
|
+
top: c.get('top')
|
|
177
|
+
}));
|
|
178
|
+
const sizes = targets.map((c) => ({
|
|
179
|
+
width: typeof c.get('width') === 'number' ? c.get('width') : 0,
|
|
180
|
+
height: typeof c.get('height') === 'number' ? c.get('height') : 0
|
|
181
|
+
}));
|
|
182
|
+
const positions = computeArrangePositions(op.layout, beforePositions, sizes);
|
|
183
|
+
for (let i = 0; i < targets.length; i++) {
|
|
184
|
+
const t = targets[i];
|
|
185
|
+
const pos = positions[i];
|
|
186
|
+
const merged = mergeComponent(t.model, { left: pos.left, top: pos.top });
|
|
187
|
+
t.set(merged);
|
|
188
|
+
}
|
|
189
|
+
// 단일 snapshot — N 개 modify 가 한 undo step
|
|
190
|
+
scene.commander?.execute(null, false);
|
|
191
|
+
const inverseOps = beforePositions.map(b => ({
|
|
192
|
+
op: 'modify',
|
|
193
|
+
refid: b.refid,
|
|
194
|
+
patch: { left: b.left, top: b.top }
|
|
195
|
+
}));
|
|
196
|
+
return { applied: true, inverseOps };
|
|
197
|
+
}
|
|
198
|
+
case 'replace':
|
|
199
|
+
// 'replace' 는 호스트의 wholesale 경로 — dispatcher 는 받지 않음.
|
|
200
|
+
return NOOP_RESULT;
|
|
201
|
+
default:
|
|
202
|
+
return NOOP_RESULT;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Sugar layout 위치 계산 — grid / row / column.
|
|
207
|
+
*
|
|
208
|
+
* 정책:
|
|
209
|
+
* - left/top 만 변경. width/height 는 보존 (사용자 의도).
|
|
210
|
+
* - grid: cell size = max(width)/max(height) — 컴포넌트 사이즈 다를 때 겹침 방지.
|
|
211
|
+
* Row-major 채움.
|
|
212
|
+
* - row/column: 각 컴포넌트의 실 size + gap 으로 누적. align (start/center/end) 으로
|
|
213
|
+
* cross-axis 정렬.
|
|
214
|
+
* - anchor 미지정 시 첫 컴포넌트의 현재 (left, top) — 예측 가능.
|
|
215
|
+
*
|
|
216
|
+
* 입력: layout 정의 + 각 target 의 현재 (left, top) (anchor default 용) + sizes.
|
|
217
|
+
* 출력: 각 target 의 새 (left, top). targets 와 동일 순서.
|
|
218
|
+
*
|
|
219
|
+
* Pure function — host dispatcher 외부에서도 단위 테스트 가능.
|
|
220
|
+
*/
|
|
221
|
+
export function computeArrangePositions(layout, current, sizes) {
|
|
222
|
+
if (current.length === 0)
|
|
223
|
+
return [];
|
|
224
|
+
const anchor = layout.anchor ?? { left: current[0].left, top: current[0].top };
|
|
225
|
+
const gap = typeof layout.gap === 'number' ? layout.gap : 10;
|
|
226
|
+
if (layout.type === 'grid') {
|
|
227
|
+
const cols = Math.max(1, Math.floor(layout.cols));
|
|
228
|
+
const cellW = sizes.reduce((m, s) => Math.max(m, s.width), 0);
|
|
229
|
+
const cellH = sizes.reduce((m, s) => Math.max(m, s.height), 0);
|
|
230
|
+
return current.map((_, i) => {
|
|
231
|
+
const row = Math.floor(i / cols);
|
|
232
|
+
const col = i % cols;
|
|
233
|
+
return {
|
|
234
|
+
left: anchor.left + col * (cellW + gap),
|
|
235
|
+
top: anchor.top + row * (cellH + gap)
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (layout.type === 'row') {
|
|
240
|
+
const align = layout.align ?? 'start';
|
|
241
|
+
const maxH = sizes.reduce((m, s) => Math.max(m, s.height), 0);
|
|
242
|
+
const out = [];
|
|
243
|
+
let cursor = anchor.left;
|
|
244
|
+
for (let i = 0; i < sizes.length; i++) {
|
|
245
|
+
const s = sizes[i];
|
|
246
|
+
let top = anchor.top;
|
|
247
|
+
if (align === 'center')
|
|
248
|
+
top = anchor.top + (maxH - s.height) / 2;
|
|
249
|
+
else if (align === 'end')
|
|
250
|
+
top = anchor.top + (maxH - s.height);
|
|
251
|
+
out.push({ left: cursor, top });
|
|
252
|
+
cursor += s.width + gap;
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
// column
|
|
257
|
+
const align = layout.align ?? 'start';
|
|
258
|
+
const maxW = sizes.reduce((m, s) => Math.max(m, s.width), 0);
|
|
259
|
+
const out = [];
|
|
260
|
+
let cursor = anchor.top;
|
|
261
|
+
for (let i = 0; i < sizes.length; i++) {
|
|
262
|
+
const s = sizes[i];
|
|
263
|
+
let left = anchor.left;
|
|
264
|
+
if (align === 'center')
|
|
265
|
+
left = anchor.left + (maxW - s.width) / 2;
|
|
266
|
+
else if (align === 'end')
|
|
267
|
+
left = anchor.left + (maxW - s.width);
|
|
268
|
+
out.push({ left, top: cursor });
|
|
269
|
+
cursor += s.height + gap;
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* scene 의 모든 컴포넌트 refid 수집 — add/group 의 inverse 계산용.
|
|
275
|
+
*
|
|
276
|
+
* scene.add / scene.group 직전·직후 호출해 차집합으로 새 발급 refid 식별.
|
|
277
|
+
*/
|
|
278
|
+
export function collectAllRefids(scene) {
|
|
279
|
+
const refids = [];
|
|
280
|
+
const map = scene?.rootContainer?.refidIndexMap;
|
|
281
|
+
if (map && typeof map.forEach === 'function') {
|
|
282
|
+
map.forEach((_, refid) => refids.push(refid));
|
|
283
|
+
}
|
|
284
|
+
return refids;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* patch 가 변경하려는 키들에 대해 model 의 현재 값을 deep-clone 으로 캡처.
|
|
288
|
+
* inverse modify / modifyBoard op 의 patch 로 사용. 원본에 없던 키는 null 로 보관
|
|
289
|
+
* (revert 시 명시적 null reset).
|
|
290
|
+
*/
|
|
291
|
+
export function captureOldKeys(model, patch) {
|
|
292
|
+
const out = {};
|
|
293
|
+
for (const k of Object.keys(patch || {})) {
|
|
294
|
+
const v = model?.[k];
|
|
295
|
+
out[k] = v === undefined ? null : JSON.parse(JSON.stringify(v));
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=board-edit-dispatch.js.map
|