@fmsim/board 2.0.0-beta.101 → 2.0.0-beta.104

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.
Files changed (33) hide show
  1. package/custom-elements.json +2020 -1759
  2. package/dist/src/modeller/bulk-create-dialog.d.ts +12 -1
  3. package/dist/src/modeller/bulk-create-dialog.js +67 -3
  4. package/dist/src/modeller/bulk-create-dialog.js.map +1 -1
  5. package/dist/src/modeller/bulk-create.d.ts +6 -0
  6. package/dist/src/modeller/bulk-create.js +3 -1
  7. package/dist/src/modeller/bulk-create.js.map +1 -1
  8. package/dist/src/modeller/component-toolbar/component-toolbar.d.ts +6 -0
  9. package/dist/src/modeller/component-toolbar/component-toolbar.js +18 -2
  10. package/dist/src/modeller/component-toolbar/component-toolbar.js.map +1 -1
  11. package/dist/src/modeller/edit-toolbar.js +10 -1
  12. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  13. package/dist/src/modeller/property-sidebar/effects/effects.js +3 -2
  14. package/dist/src/modeller/property-sidebar/effects/effects.js.map +1 -1
  15. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js +12 -3
  16. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js.map +1 -1
  17. package/dist/src/modeller/property-sidebar/shapes/shapes.js +40 -24
  18. package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
  19. package/dist/src/modeller/property-sidebar/specifics/parent-object.d.ts +29 -0
  20. package/dist/src/modeller/property-sidebar/specifics/parent-object.js +74 -0
  21. package/dist/src/modeller/property-sidebar/specifics/parent-object.js.map +1 -0
  22. package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +4 -2
  23. package/dist/src/modeller/property-sidebar/specifics/specifics.js +13 -31
  24. package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
  25. package/dist/src/ox-board-modeller.js +9 -2
  26. package/dist/src/ox-board-modeller.js.map +1 -1
  27. package/dist/src/ox-board-viewer.js +8 -2
  28. package/dist/src/ox-board-viewer.js.map +1 -1
  29. package/dist/src/scene-monitor-renderer.d.ts +10 -0
  30. package/dist/src/scene-monitor-renderer.js +226 -0
  31. package/dist/src/scene-monitor-renderer.js.map +1 -0
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { LitElement } from 'lit';
2
+ import { OxInputEntityId } from './property-sidebar/shapes/ox-input-entity-id';
2
3
  import { generateBulkComponents, type Area } from './bulk-create.js';
3
4
  export type ConflictPolicy = 'skip' | 'duplicate';
4
5
  export interface BulkCreateDialogProps {
@@ -13,14 +14,23 @@ export interface BulkCreateDialogProps {
13
14
  /** 취소 / 닫기 콜백. */
14
15
  onCancel?: () => void;
15
16
  }
16
- export declare class BulkCreateDialog extends LitElement {
17
+ declare const BulkCreateDialog_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types.js").ScopedElementsHost>;
18
+ export declare class BulkCreateDialog extends BulkCreateDialog_base {
19
+ static get scopedElements(): {
20
+ 'ox-input-entity-id': typeof OxInputEntityId;
21
+ };
17
22
  static styles: import("lit").CSSResult;
18
23
  template: Record<string, any>;
19
24
  initialArea: Area;
20
25
  existingIds: Set<string>;
21
26
  onConfirm: BulkCreateDialogProps['onConfirm'];
22
27
  onCancel?: BulkCreateDialogProps['onCancel'];
28
+ /** parentObject 필드 노출 여부 (template 이 ParentObjectMixin 컴포넌트일 때). */
29
+ hasParentObject: boolean;
30
+ /** parentObject 후보 id 리스트 (template 타입 기준, toolbar 에서 계산해 주입). */
31
+ parentObjectItems: string[];
23
32
  private pattern;
33
+ private parentObjectId;
24
34
  private count;
25
35
  private rows;
26
36
  private cols;
@@ -49,3 +59,4 @@ declare global {
49
59
  'bulk-create-dialog': BulkCreateDialog;
50
60
  }
51
61
  }
62
+ export {};
@@ -6,12 +6,19 @@
6
6
  import { __decorate } from "tslib";
7
7
  import { LitElement, css, html } from 'lit';
8
8
  import { customElement, property, query, state } from 'lit/decorators.js';
9
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements';
10
+ import { OxInputEntityId } from './property-sidebar/shapes/ox-input-entity-id';
9
11
  import { generateBulkComponents, findIdConflicts, inferIdPattern, nextAvailableStart, deriveGridShape } from './bulk-create.js';
10
- let BulkCreateDialog = class BulkCreateDialog extends LitElement {
12
+ let BulkCreateDialog = class BulkCreateDialog extends ScopedElementsMixin(LitElement) {
11
13
  constructor() {
12
14
  super(...arguments);
13
15
  this.existingIds = new Set();
16
+ /** parentObject 필드 노출 여부 (template 이 ParentObjectMixin 컴포넌트일 때). */
17
+ this.hasParentObject = false;
18
+ /** parentObject 후보 id 리스트 (template 타입 기준, toolbar 에서 계산해 주입). */
19
+ this.parentObjectItems = [];
14
20
  this.pattern = 'grid';
21
+ this.parentObjectId = '';
15
22
  this.count = 12;
16
23
  this.rows = undefined;
17
24
  this.cols = undefined;
@@ -30,8 +37,13 @@ let BulkCreateDialog = class BulkCreateDialog extends LitElement {
30
37
  (_a = this.onCancel) === null || _a === void 0 ? void 0 : _a.call(this);
31
38
  };
32
39
  }
40
+ static get scopedElements() {
41
+ return {
42
+ 'ox-input-entity-id': OxInputEntityId
43
+ };
44
+ }
33
45
  connectedCallback() {
34
- var _a;
46
+ var _a, _b, _c;
35
47
  super.connectedCallback();
36
48
  // OxPopup wrapper (ox-popup) 의 흰색 배경 + box-shadow 가 dialog 라운드 코너
37
49
  // 밖으로 비쳐서 4 꼭지점에 흰색 사각형이 보임. 부모를 투명/그림자 없이 만들어
@@ -62,6 +74,8 @@ let BulkCreateDialog = class BulkCreateDialog extends LitElement {
62
74
  // 반복 bulk-paste 시 이전 batch 의 끝 다음으로 자동 advance.
63
75
  // 매칭 ID 가 없으면 inferIdPattern 의 fallback (= template num+1) 사용.
64
76
  this.idStart = nextAvailableStart(inferred.pattern, this.existingIds, inferred.start);
77
+ // 4) 복사 대상(template)이 이미 parentObjectId 를 가지면 그것을 초기값으로.
78
+ this.parentObjectId = String((_c = (_b = this.template) === null || _b === void 0 ? void 0 : _b.parentObjectId) !== null && _c !== void 0 ? _c : '');
65
79
  }
66
80
  /** rows/cols 가 미입력 (undefined) 이면 영역 종횡비 + count 로 자동 계산해 표시. */
67
81
  get displayRows() {
@@ -95,7 +109,9 @@ let BulkCreateDialog = class BulkCreateDialog extends LitElement {
95
109
  cols: this.cols,
96
110
  idPattern: this.idPattern.replace(/\{type\}/g, String((_b = (_a = this.template) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'C')),
97
111
  idStart: this.idStart,
98
- idPadding: this.idPadding
112
+ idPadding: this.idPadding,
113
+ // 선택 시 각 생성 컴포넌트에 parentObjectId 를 병합 (도메인 특수값은 여기서만).
114
+ extraState: this.parentObjectId ? { parentObjectId: this.parentObjectId } : undefined
99
115
  });
100
116
  }
101
117
  get conflicts() {
@@ -237,6 +253,20 @@ let BulkCreateDialog = class BulkCreateDialog extends LitElement {
237
253
  />
238
254
  </div>
239
255
 
256
+ ${this.hasParentObject
257
+ ? html `
258
+ <div class="row">
259
+ <label>상위 객체</label>
260
+ <ox-input-entity-id
261
+ .localItems=${this.parentObjectItems}
262
+ .value=${this.parentObjectId}
263
+ placeholder="상위 객체 id (검색)"
264
+ @change=${(e) => (this.parentObjectId = e.target.value)}
265
+ ></ox-input-entity-id>
266
+ </div>
267
+ `
268
+ : ''}
269
+
240
270
  <div class="preview">
241
271
  ${models.length > 0
242
272
  ? html `총 ${models.length}개 — 예: <code>${models[0].id}</code> ... <code>${models[models.length - 1].id}</code>`
@@ -341,6 +371,31 @@ BulkCreateDialog.styles = css `
341
371
  border-color: #4a9eff;
342
372
  background: rgba(74, 158, 255, 0.08);
343
373
  }
374
+ /* parentObject 입력(ox-input-entity-id)의 내부 input 을 dialog 의 다른 input 과 통일. */
375
+ ox-input-entity-id::part(input) {
376
+ padding: 4px 6px;
377
+ font: inherit;
378
+ font-size: 12px;
379
+ background: rgba(255, 255, 255, 0.06);
380
+ border: 1px solid rgba(255, 255, 255, 0.12);
381
+ border-radius: 3px;
382
+ color: rgba(255, 255, 255, 0.9);
383
+ outline: none;
384
+ }
385
+ ox-input-entity-id::part(input):focus {
386
+ border-color: #4a9eff;
387
+ background: rgba(74, 158, 255, 0.08);
388
+ }
389
+ /* 검색 드롭다운(shadow 내부)은 md-sys-color 변수 + 상속 color 로 다크 테마 통일.
390
+ (변수는 shadow 경계를 관통하고, .item 은 color 를 host 로부터 상속받는다.) */
391
+ ox-input-entity-id {
392
+ color: rgba(255, 255, 255, 0.9);
393
+ --md-sys-color-surface: rgb(30, 30, 30);
394
+ --md-sys-color-surface-variant: rgb(45, 45, 45);
395
+ --md-sys-color-outline-variant: rgba(255, 255, 255, 0.15);
396
+ --md-sys-color-secondary-container: rgba(74, 158, 255, 0.25);
397
+ --md-sys-color-on-surface-variant: rgba(255, 255, 255, 0.55);
398
+ }
344
399
  select option {
345
400
  background: rgb(30, 30, 30);
346
401
  color: rgba(255, 255, 255, 0.9);
@@ -494,9 +549,18 @@ __decorate([
494
549
  __decorate([
495
550
  property({ attribute: false })
496
551
  ], BulkCreateDialog.prototype, "onCancel", void 0);
552
+ __decorate([
553
+ property({ attribute: false })
554
+ ], BulkCreateDialog.prototype, "hasParentObject", void 0);
555
+ __decorate([
556
+ property({ attribute: false })
557
+ ], BulkCreateDialog.prototype, "parentObjectItems", void 0);
497
558
  __decorate([
498
559
  state()
499
560
  ], BulkCreateDialog.prototype, "pattern", void 0);
561
+ __decorate([
562
+ state()
563
+ ], BulkCreateDialog.prototype, "parentObjectId", void 0);
500
564
  __decorate([
501
565
  state()
502
566
  ], BulkCreateDialog.prototype, "count", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"bulk-create-dialog.js","sourceRoot":"","sources":["../../../src/modeller/bulk-create-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * 멀티 컴포넌트 생성 dialog — 템플릿 + 영역 + 옵션 입력 → OK 시 콜백 호출.\n */\n\nimport { LitElement, css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport {\n generateBulkComponents,\n findIdConflicts,\n inferIdPattern,\n nextAvailableStart,\n deriveGridShape,\n type BulkCreateOptions,\n type Pattern,\n type Area\n} from './bulk-create.js'\n\nexport type ConflictPolicy = 'skip' | 'duplicate'\n\nexport interface BulkCreateDialogProps {\n /** 템플릿 컴포넌트 state (read-only 표시용). */\n template: Record<string, any>\n /** 초기 영역 (보드 좌표). 사용자가 dialog 내에서 조정 가능. */\n initialArea: Area\n /** 기존 scene 의 모든 ID — 충돌 검사. */\n existingIds?: Set<string>\n /** OK 클릭 시 콜백 — 생성할 model 배열 전달. */\n onConfirm: (models: ReturnType<typeof generateBulkComponents>) => void\n /** 취소 / 닫기 콜백. */\n onCancel?: () => void\n}\n\n@customElement('bulk-create-dialog')\nexport class BulkCreateDialog extends LitElement {\n static styles = css`\n :host {\n display: block;\n padding: 16px 20px;\n min-width: 340px;\n max-width: 480px;\n background: rgba(30, 30, 30, 0.94);\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 10px;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n color: rgba(255, 255, 255, 0.85);\n font: 12px/1.6 -apple-system, BlinkMacSystemFont, sans-serif;\n }\n h3 {\n margin: 0 0 12px;\n font-size: 13px;\n font-weight: 600;\n color: #4a9eff;\n }\n .row {\n display: grid;\n grid-template-columns: 100px 1fr;\n align-items: center;\n gap: 8px;\n margin: 6px 0;\n }\n label {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.55);\n }\n input,\n select {\n padding: 4px 6px;\n font: inherit;\n font-size: 12px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 3px;\n width: 100%;\n box-sizing: border-box;\n color: rgba(255, 255, 255, 0.9);\n outline: none;\n }\n input:focus,\n select:focus {\n border-color: #4a9eff;\n background: rgba(74, 158, 255, 0.08);\n }\n select option {\n background: rgb(30, 30, 30);\n color: rgba(255, 255, 255, 0.9);\n }\n .grid-shape {\n display: grid;\n grid-template-columns: 1fr 20px 1fr;\n gap: 4px;\n align-items: center;\n }\n .grid-shape > span {\n text-align: center;\n color: rgba(255, 255, 255, 0.35);\n }\n .pattern-picker {\n display: flex;\n gap: 6px;\n }\n .pattern-btn {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n padding: 6px 4px;\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 4px;\n cursor: pointer;\n color: rgba(255, 255, 255, 0.6);\n transition: background 0.1s, border-color 0.1s;\n }\n .pattern-btn:hover {\n background: rgba(255, 255, 255, 0.08);\n }\n .pattern-btn.active {\n border-color: #4a9eff;\n background: rgba(74, 158, 255, 0.15);\n color: rgba(255, 255, 255, 0.95);\n }\n .pattern-btn svg {\n width: 28px;\n height: 28px;\n }\n .pattern-btn svg circle {\n fill: currentColor;\n }\n .pattern-btn span {\n font-size: 10.5px;\n }\n .policy-radios {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n .policy-radios label {\n display: flex;\n align-items: center;\n gap: 4px;\n cursor: pointer;\n color: rgba(255, 255, 255, 0.75);\n }\n .policy-radios input[type='radio'] {\n width: auto;\n accent-color: #4a9eff;\n margin: 0;\n }\n code {\n font-family: ui-monospace, SFMono-Regular, monospace;\n background: rgba(255, 255, 255, 0.08);\n padding: 1px 4px;\n border-radius: 3px;\n color: #b6d4fe;\n font-size: 11px;\n }\n .hint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.4);\n margin-top: 2px;\n grid-column: 2;\n }\n .preview {\n font-size: 11.5px;\n color: rgba(255, 255, 255, 0.65);\n margin-top: 10px;\n padding: 6px 8px;\n background: rgba(255, 255, 255, 0.04);\n border-radius: 4px;\n }\n .conflict {\n margin-top: 8px;\n padding: 8px 10px;\n background: rgba(255, 100, 100, 0.12);\n border: 1px solid rgba(255, 100, 100, 0.3);\n border-radius: 4px;\n color: #ffb3b3;\n font-size: 11.5px;\n }\n .conflict strong {\n color: #ff8080;\n }\n .conflict-hint {\n margin-top: 4px;\n color: rgba(255, 255, 255, 0.45);\n font-size: 11px;\n }\n .actions {\n margin-top: 16px;\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n }\n button {\n padding: 5px 14px;\n font: inherit;\n font-size: 12px;\n border: 1px solid rgba(255, 255, 255, 0.15);\n background: rgba(255, 255, 255, 0.06);\n color: rgba(255, 255, 255, 0.85);\n cursor: pointer;\n border-radius: 4px;\n }\n button:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n }\n button:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n button.primary {\n background: #4a9eff;\n color: white;\n border-color: #4a9eff;\n }\n button.primary:hover:not(:disabled) {\n background: #6db1ff;\n }\n `\n\n @property({ type: Object }) template!: Record<string, any>\n @property({ type: Object }) initialArea!: Area\n @property({ attribute: false }) existingIds: Set<string> = new Set()\n @property({ attribute: false }) onConfirm!: BulkCreateDialogProps['onConfirm']\n @property({ attribute: false }) onCancel?: BulkCreateDialogProps['onCancel']\n\n @state() private pattern: Pattern = 'grid'\n @state() private count = 12\n @state() private rows: number | undefined = undefined\n @state() private cols: number | undefined = undefined\n @state() private idPattern = '{i}'\n @state() private idStart = 1\n @state() private idPadding = 0\n @state() private area!: Area\n @state() private conflictPolicy: ConflictPolicy = 'skip'\n\n @query('#count-input') countInput!: HTMLInputElement\n\n connectedCallback() {\n super.connectedCallback()\n // OxPopup wrapper (ox-popup) 의 흰색 배경 + box-shadow 가 dialog 라운드 코너\n // 밖으로 비쳐서 4 꼭지점에 흰색 사각형이 보임. 부모를 투명/그림자 없이 만들어\n // dialog 자체의 라운드 + 그림자만 보이게.\n const parent = this.parentElement as HTMLElement | null\n if (parent) {\n parent.style.background = 'transparent'\n parent.style.boxShadow = 'none'\n parent.style.border = 'none'\n }\n this.area = { ...this.initialArea }\n\n // 1) 영역 종횡비로 레이아웃 기본 선택:\n // 너비 ≫ 높이 → line-h (가로), 높이 ≫ 너비 → line-v (세로), 비슷하면 grid.\n const w = Math.max(this.area.width, 1)\n const h = Math.max(this.area.height, 1)\n const aspect = w / h\n if (aspect >= 2.5) this.pattern = 'line-h'\n else if (aspect <= 0.4) this.pattern = 'line-v'\n else this.pattern = 'grid'\n\n // 2) 템플릿 id 로부터 ID 패턴 / padding 자동 추론.\n const inferred = inferIdPattern(this.template?.id)\n this.idPattern = inferred.pattern\n this.idPadding = inferred.padding\n\n // 3) 시작 번호 = existingIds 에 패턴 매칭되는 *최대 번호 + 1*.\n // 반복 bulk-paste 시 이전 batch 의 끝 다음으로 자동 advance.\n // 매칭 ID 가 없으면 inferIdPattern 의 fallback (= template num+1) 사용.\n this.idStart = nextAvailableStart(inferred.pattern, this.existingIds, inferred.start)\n }\n\n /** rows/cols 가 미입력 (undefined) 이면 영역 종횡비 + count 로 자동 계산해 표시. */\n private get displayRows(): number {\n if (this.rows != null) return this.rows\n return deriveGridShape(this.count, this.area, undefined, this.cols).rows\n }\n\n private get displayCols(): number {\n if (this.cols != null) return this.cols\n return deriveGridShape(this.count, this.area, this.rows, undefined).cols\n }\n\n firstUpdated() {\n this.countInput?.focus()\n this.countInput?.select()\n }\n\n private updateArea(field: keyof Area, value: number) {\n this.area = { ...this.area, [field]: value }\n }\n\n private get previewModels() {\n if (!this.template) return []\n return generateBulkComponents({\n template: this.template,\n area: this.area,\n pattern: this.pattern,\n count: this.count,\n rows: this.rows,\n cols: this.cols,\n idPattern: this.idPattern.replace(/\\{type\\}/g, String(this.template?.type ?? 'C')),\n idStart: this.idStart,\n idPadding: this.idPadding\n })\n }\n\n private get conflicts(): string[] {\n return findIdConflicts(this.previewModels, this.existingIds)\n }\n\n /** 충돌 정책 적용 후 실제 생성될 model 배열. */\n private get effectiveModels() {\n const all = this.previewModels\n if (this.conflictPolicy === 'skip' && this.existingIds.size > 0) {\n return all.filter(m => !this.existingIds.has(m.id))\n }\n return all\n }\n\n private onOk = () => {\n const models = this.effectiveModels\n if (models.length === 0) return\n this.onConfirm(models)\n }\n\n private onCancelClick = () => {\n this.onCancel?.()\n }\n\n render() {\n const tplType = this.template?.type ?? '?'\n const tplId = this.template?.id ?? '?'\n const models = this.previewModels\n\n return html`\n <h3>Bulk Paste — ${tplType}</h3>\n\n <div class=\"row\">\n <label>템플릿</label>\n <span style=\"color:#b6d4fe;\">${tplId} <span style=\"color:rgba(255,255,255,0.4);\">(${tplType})</span></span>\n </div>\n\n <div class=\"row\">\n <label>레이아웃</label>\n <div class=\"pattern-picker\">\n <button\n type=\"button\"\n class=\"pattern-btn ${this.pattern === 'grid' ? 'active' : ''}\"\n @click=${() => (this.pattern = 'grid')}\n title=\"격자\"\n >\n <svg viewBox=\"0 0 24 24\">\n <circle cx=\"6\" cy=\"6\" r=\"1.6\" /><circle cx=\"12\" cy=\"6\" r=\"1.6\" /><circle cx=\"18\" cy=\"6\" r=\"1.6\" />\n <circle cx=\"6\" cy=\"12\" r=\"1.6\" /><circle cx=\"12\" cy=\"12\" r=\"1.6\" /><circle cx=\"18\" cy=\"12\" r=\"1.6\" />\n <circle cx=\"6\" cy=\"18\" r=\"1.6\" /><circle cx=\"12\" cy=\"18\" r=\"1.6\" /><circle cx=\"18\" cy=\"18\" r=\"1.6\" />\n </svg>\n <span>격자</span>\n </button>\n <button\n type=\"button\"\n class=\"pattern-btn ${this.pattern === 'line-h' ? 'active' : ''}\"\n @click=${() => (this.pattern = 'line-h')}\n title=\"가로 1줄\"\n >\n <svg viewBox=\"0 0 24 24\">\n <circle cx=\"3\" cy=\"12\" r=\"1.6\" /><circle cx=\"8\" cy=\"12\" r=\"1.6\" /><circle cx=\"13\" cy=\"12\" r=\"1.6\" />\n <circle cx=\"18\" cy=\"12\" r=\"1.6\" /><circle cx=\"22\" cy=\"12\" r=\"1.4\" />\n </svg>\n <span>가로</span>\n </button>\n <button\n type=\"button\"\n class=\"pattern-btn ${this.pattern === 'line-v' ? 'active' : ''}\"\n @click=${() => (this.pattern = 'line-v')}\n title=\"세로 1줄\"\n >\n <svg viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"3\" r=\"1.6\" /><circle cx=\"12\" cy=\"8\" r=\"1.6\" /><circle cx=\"12\" cy=\"13\" r=\"1.6\" />\n <circle cx=\"12\" cy=\"18\" r=\"1.6\" /><circle cx=\"12\" cy=\"22\" r=\"1.4\" />\n </svg>\n <span>세로</span>\n </button>\n </div>\n </div>\n\n <div class=\"row\">\n <label>개수</label>\n <input\n id=\"count-input\"\n type=\"number\"\n min=\"1\"\n .value=${String(this.count)}\n @input=${(e: Event) => (this.count = Math.max(1, +(e.target as HTMLInputElement).value))}\n />\n </div>\n\n ${this.pattern === 'grid'\n ? html`\n <div class=\"row\">\n <label>행 × 열</label>\n <div class=\"grid-shape\">\n <input\n type=\"number\"\n min=\"1\"\n .value=${String(this.displayRows)}\n @input=${(e: Event) => {\n const v = (e.target as HTMLInputElement).value\n this.rows = v ? Math.max(1, +v) : undefined\n }}\n />\n <span>×</span>\n <input\n type=\"number\"\n min=\"1\"\n .value=${String(this.displayCols)}\n @input=${(e: Event) => {\n const v = (e.target as HTMLInputElement).value\n this.cols = v ? Math.max(1, +v) : undefined\n }}\n />\n </div>\n </div>\n `\n : ''}\n\n <div class=\"row\">\n <label>ID 패턴</label>\n <input\n type=\"text\"\n .value=${this.idPattern}\n @input=${(e: Event) => (this.idPattern = (e.target as HTMLInputElement).value)}\n />\n </div>\n <div class=\"row\">\n <span></span>\n <div class=\"hint\">placeholder: <code>{i}</code> 일련번호, <code>{r}</code> 행, <code>{c}</code> 열 (grid)</div>\n </div>\n\n <div class=\"row\">\n <label>시작 번호</label>\n <input\n type=\"number\"\n .value=${String(this.idStart)}\n @input=${(e: Event) => (this.idStart = +(e.target as HTMLInputElement).value)}\n />\n </div>\n\n <div class=\"row\">\n <label>Zero-pad 자릿수</label>\n <input\n type=\"number\"\n min=\"0\"\n .value=${String(this.idPadding)}\n @input=${(e: Event) => (this.idPadding = Math.max(0, +(e.target as HTMLInputElement).value))}\n />\n </div>\n\n <div class=\"preview\">\n ${models.length > 0\n ? html`총 ${models.length}개 — 예: <code>${models[0].id}</code> ... <code>${models[models.length - 1].id}</code>`\n : html`<span style=\"color:#ff8080;\">생성될 컴포넌트 없음</span>`}\n </div>\n\n ${this.conflicts.length > 0\n ? html`\n <div class=\"conflict\">\n <strong>ID 충돌 ${this.conflicts.length}개</strong>\n <code>${this.conflicts.slice(0, 5).join(', ')}${this.conflicts.length > 5 ? ', ...' : ''}</code>\n <div class=\"conflict-hint\" style=\"margin-top:6px;\">\n <div class=\"policy-radios\">\n <label>\n <input\n type=\"radio\"\n name=\"conflict-policy\"\n value=\"skip\"\n .checked=${this.conflictPolicy === 'skip'}\n @change=${() => (this.conflictPolicy = 'skip')}\n />\n 충돌 ID 스킵 (기본)\n </label>\n <label>\n <input\n type=\"radio\"\n name=\"conflict-policy\"\n value=\"duplicate\"\n .checked=${this.conflictPolicy === 'duplicate'}\n @change=${() => (this.conflictPolicy = 'duplicate')}\n />\n 중복 그대로 생성\n </label>\n </div>\n </div>\n </div>\n `\n : ''}\n\n <div class=\"actions\">\n <button @click=${this.onCancelClick}>취소</button>\n <button\n class=\"primary\"\n @click=${this.onOk}\n ?disabled=${this.effectiveModels.length === 0}\n >\n 생성 (${this.effectiveModels.length}${\n this.conflicts.length > 0 && this.conflictPolicy === 'skip'\n ? html`<span style=\"opacity:0.6;\"> / ${models.length}</span>`\n : ''\n })\n </button>\n </div>\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'bulk-create-dialog': BulkCreateDialog\n }\n}\n"]}
1
+ {"version":3,"file":"bulk-create-dialog.js","sourceRoot":"","sources":["../../../src/modeller/bulk-create-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * 멀티 컴포넌트 생성 dialog — 템플릿 + 영역 + 옵션 입력 → OK 시 콜백 호출.\n */\n\nimport { LitElement, css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\n\nimport { OxInputEntityId } from './property-sidebar/shapes/ox-input-entity-id'\n\nimport {\n generateBulkComponents,\n findIdConflicts,\n inferIdPattern,\n nextAvailableStart,\n deriveGridShape,\n type BulkCreateOptions,\n type Pattern,\n type Area\n} from './bulk-create.js'\n\nexport type ConflictPolicy = 'skip' | 'duplicate'\n\nexport interface BulkCreateDialogProps {\n /** 템플릿 컴포넌트 state (read-only 표시용). */\n template: Record<string, any>\n /** 초기 영역 (보드 좌표). 사용자가 dialog 내에서 조정 가능. */\n initialArea: Area\n /** 기존 scene 의 모든 ID — 충돌 검사. */\n existingIds?: Set<string>\n /** OK 클릭 시 콜백 — 생성할 model 배열 전달. */\n onConfirm: (models: ReturnType<typeof generateBulkComponents>) => void\n /** 취소 / 닫기 콜백. */\n onCancel?: () => void\n}\n\n@customElement('bulk-create-dialog')\nexport class BulkCreateDialog extends ScopedElementsMixin(LitElement) {\n static get scopedElements() {\n return {\n 'ox-input-entity-id': OxInputEntityId\n }\n }\n\n static styles = css`\n :host {\n display: block;\n padding: 16px 20px;\n min-width: 340px;\n max-width: 480px;\n background: rgba(30, 30, 30, 0.94);\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 10px;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n color: rgba(255, 255, 255, 0.85);\n font: 12px/1.6 -apple-system, BlinkMacSystemFont, sans-serif;\n }\n h3 {\n margin: 0 0 12px;\n font-size: 13px;\n font-weight: 600;\n color: #4a9eff;\n }\n .row {\n display: grid;\n grid-template-columns: 100px 1fr;\n align-items: center;\n gap: 8px;\n margin: 6px 0;\n }\n label {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.55);\n }\n input,\n select {\n padding: 4px 6px;\n font: inherit;\n font-size: 12px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 3px;\n width: 100%;\n box-sizing: border-box;\n color: rgba(255, 255, 255, 0.9);\n outline: none;\n }\n input:focus,\n select:focus {\n border-color: #4a9eff;\n background: rgba(74, 158, 255, 0.08);\n }\n /* parentObject 입력(ox-input-entity-id)의 내부 input 을 dialog 의 다른 input 과 통일. */\n ox-input-entity-id::part(input) {\n padding: 4px 6px;\n font: inherit;\n font-size: 12px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 3px;\n color: rgba(255, 255, 255, 0.9);\n outline: none;\n }\n ox-input-entity-id::part(input):focus {\n border-color: #4a9eff;\n background: rgba(74, 158, 255, 0.08);\n }\n /* 검색 드롭다운(shadow 내부)은 md-sys-color 변수 + 상속 color 로 다크 테마 통일.\n (변수는 shadow 경계를 관통하고, .item 은 color 를 host 로부터 상속받는다.) */\n ox-input-entity-id {\n color: rgba(255, 255, 255, 0.9);\n --md-sys-color-surface: rgb(30, 30, 30);\n --md-sys-color-surface-variant: rgb(45, 45, 45);\n --md-sys-color-outline-variant: rgba(255, 255, 255, 0.15);\n --md-sys-color-secondary-container: rgba(74, 158, 255, 0.25);\n --md-sys-color-on-surface-variant: rgba(255, 255, 255, 0.55);\n }\n select option {\n background: rgb(30, 30, 30);\n color: rgba(255, 255, 255, 0.9);\n }\n .grid-shape {\n display: grid;\n grid-template-columns: 1fr 20px 1fr;\n gap: 4px;\n align-items: center;\n }\n .grid-shape > span {\n text-align: center;\n color: rgba(255, 255, 255, 0.35);\n }\n .pattern-picker {\n display: flex;\n gap: 6px;\n }\n .pattern-btn {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n padding: 6px 4px;\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 4px;\n cursor: pointer;\n color: rgba(255, 255, 255, 0.6);\n transition: background 0.1s, border-color 0.1s;\n }\n .pattern-btn:hover {\n background: rgba(255, 255, 255, 0.08);\n }\n .pattern-btn.active {\n border-color: #4a9eff;\n background: rgba(74, 158, 255, 0.15);\n color: rgba(255, 255, 255, 0.95);\n }\n .pattern-btn svg {\n width: 28px;\n height: 28px;\n }\n .pattern-btn svg circle {\n fill: currentColor;\n }\n .pattern-btn span {\n font-size: 10.5px;\n }\n .policy-radios {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n .policy-radios label {\n display: flex;\n align-items: center;\n gap: 4px;\n cursor: pointer;\n color: rgba(255, 255, 255, 0.75);\n }\n .policy-radios input[type='radio'] {\n width: auto;\n accent-color: #4a9eff;\n margin: 0;\n }\n code {\n font-family: ui-monospace, SFMono-Regular, monospace;\n background: rgba(255, 255, 255, 0.08);\n padding: 1px 4px;\n border-radius: 3px;\n color: #b6d4fe;\n font-size: 11px;\n }\n .hint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.4);\n margin-top: 2px;\n grid-column: 2;\n }\n .preview {\n font-size: 11.5px;\n color: rgba(255, 255, 255, 0.65);\n margin-top: 10px;\n padding: 6px 8px;\n background: rgba(255, 255, 255, 0.04);\n border-radius: 4px;\n }\n .conflict {\n margin-top: 8px;\n padding: 8px 10px;\n background: rgba(255, 100, 100, 0.12);\n border: 1px solid rgba(255, 100, 100, 0.3);\n border-radius: 4px;\n color: #ffb3b3;\n font-size: 11.5px;\n }\n .conflict strong {\n color: #ff8080;\n }\n .conflict-hint {\n margin-top: 4px;\n color: rgba(255, 255, 255, 0.45);\n font-size: 11px;\n }\n .actions {\n margin-top: 16px;\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n }\n button {\n padding: 5px 14px;\n font: inherit;\n font-size: 12px;\n border: 1px solid rgba(255, 255, 255, 0.15);\n background: rgba(255, 255, 255, 0.06);\n color: rgba(255, 255, 255, 0.85);\n cursor: pointer;\n border-radius: 4px;\n }\n button:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n }\n button:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n button.primary {\n background: #4a9eff;\n color: white;\n border-color: #4a9eff;\n }\n button.primary:hover:not(:disabled) {\n background: #6db1ff;\n }\n `\n\n @property({ type: Object }) template!: Record<string, any>\n @property({ type: Object }) initialArea!: Area\n @property({ attribute: false }) existingIds: Set<string> = new Set()\n @property({ attribute: false }) onConfirm!: BulkCreateDialogProps['onConfirm']\n @property({ attribute: false }) onCancel?: BulkCreateDialogProps['onCancel']\n /** parentObject 필드 노출 여부 (template 이 ParentObjectMixin 컴포넌트일 때). */\n @property({ attribute: false }) hasParentObject = false\n /** parentObject 후보 id 리스트 (template 타입 기준, toolbar 에서 계산해 주입). */\n @property({ attribute: false }) parentObjectItems: string[] = []\n\n @state() private pattern: Pattern = 'grid'\n @state() private parentObjectId = ''\n @state() private count = 12\n @state() private rows: number | undefined = undefined\n @state() private cols: number | undefined = undefined\n @state() private idPattern = '{i}'\n @state() private idStart = 1\n @state() private idPadding = 0\n @state() private area!: Area\n @state() private conflictPolicy: ConflictPolicy = 'skip'\n\n @query('#count-input') countInput!: HTMLInputElement\n\n connectedCallback() {\n super.connectedCallback()\n // OxPopup wrapper (ox-popup) 의 흰색 배경 + box-shadow 가 dialog 라운드 코너\n // 밖으로 비쳐서 4 꼭지점에 흰색 사각형이 보임. 부모를 투명/그림자 없이 만들어\n // dialog 자체의 라운드 + 그림자만 보이게.\n const parent = this.parentElement as HTMLElement | null\n if (parent) {\n parent.style.background = 'transparent'\n parent.style.boxShadow = 'none'\n parent.style.border = 'none'\n }\n this.area = { ...this.initialArea }\n\n // 1) 영역 종횡비로 레이아웃 기본 선택:\n // 너비 ≫ 높이 → line-h (가로), 높이 ≫ 너비 → line-v (세로), 비슷하면 grid.\n const w = Math.max(this.area.width, 1)\n const h = Math.max(this.area.height, 1)\n const aspect = w / h\n if (aspect >= 2.5) this.pattern = 'line-h'\n else if (aspect <= 0.4) this.pattern = 'line-v'\n else this.pattern = 'grid'\n\n // 2) 템플릿 id 로부터 ID 패턴 / padding 자동 추론.\n const inferred = inferIdPattern(this.template?.id)\n this.idPattern = inferred.pattern\n this.idPadding = inferred.padding\n\n // 3) 시작 번호 = existingIds 에 패턴 매칭되는 *최대 번호 + 1*.\n // 반복 bulk-paste 시 이전 batch 의 끝 다음으로 자동 advance.\n // 매칭 ID 가 없으면 inferIdPattern 의 fallback (= template num+1) 사용.\n this.idStart = nextAvailableStart(inferred.pattern, this.existingIds, inferred.start)\n\n // 4) 복사 대상(template)이 이미 parentObjectId 를 가지면 그것을 초기값으로.\n this.parentObjectId = String(this.template?.parentObjectId ?? '')\n }\n\n /** rows/cols 가 미입력 (undefined) 이면 영역 종횡비 + count 로 자동 계산해 표시. */\n private get displayRows(): number {\n if (this.rows != null) return this.rows\n return deriveGridShape(this.count, this.area, undefined, this.cols).rows\n }\n\n private get displayCols(): number {\n if (this.cols != null) return this.cols\n return deriveGridShape(this.count, this.area, this.rows, undefined).cols\n }\n\n firstUpdated() {\n this.countInput?.focus()\n this.countInput?.select()\n }\n\n private updateArea(field: keyof Area, value: number) {\n this.area = { ...this.area, [field]: value }\n }\n\n private get previewModels() {\n if (!this.template) return []\n return generateBulkComponents({\n template: this.template,\n area: this.area,\n pattern: this.pattern,\n count: this.count,\n rows: this.rows,\n cols: this.cols,\n idPattern: this.idPattern.replace(/\\{type\\}/g, String(this.template?.type ?? 'C')),\n idStart: this.idStart,\n idPadding: this.idPadding,\n // 선택 시 각 생성 컴포넌트에 parentObjectId 를 병합 (도메인 특수값은 여기서만).\n extraState: this.parentObjectId ? { parentObjectId: this.parentObjectId } : undefined\n })\n }\n\n private get conflicts(): string[] {\n return findIdConflicts(this.previewModels, this.existingIds)\n }\n\n /** 충돌 정책 적용 후 실제 생성될 model 배열. */\n private get effectiveModels() {\n const all = this.previewModels\n if (this.conflictPolicy === 'skip' && this.existingIds.size > 0) {\n return all.filter(m => !this.existingIds.has(m.id))\n }\n return all\n }\n\n private onOk = () => {\n const models = this.effectiveModels\n if (models.length === 0) return\n this.onConfirm(models)\n }\n\n private onCancelClick = () => {\n this.onCancel?.()\n }\n\n render() {\n const tplType = this.template?.type ?? '?'\n const tplId = this.template?.id ?? '?'\n const models = this.previewModels\n\n return html`\n <h3>Bulk Paste — ${tplType}</h3>\n\n <div class=\"row\">\n <label>템플릿</label>\n <span style=\"color:#b6d4fe;\">${tplId} <span style=\"color:rgba(255,255,255,0.4);\">(${tplType})</span></span>\n </div>\n\n <div class=\"row\">\n <label>레이아웃</label>\n <div class=\"pattern-picker\">\n <button\n type=\"button\"\n class=\"pattern-btn ${this.pattern === 'grid' ? 'active' : ''}\"\n @click=${() => (this.pattern = 'grid')}\n title=\"격자\"\n >\n <svg viewBox=\"0 0 24 24\">\n <circle cx=\"6\" cy=\"6\" r=\"1.6\" /><circle cx=\"12\" cy=\"6\" r=\"1.6\" /><circle cx=\"18\" cy=\"6\" r=\"1.6\" />\n <circle cx=\"6\" cy=\"12\" r=\"1.6\" /><circle cx=\"12\" cy=\"12\" r=\"1.6\" /><circle cx=\"18\" cy=\"12\" r=\"1.6\" />\n <circle cx=\"6\" cy=\"18\" r=\"1.6\" /><circle cx=\"12\" cy=\"18\" r=\"1.6\" /><circle cx=\"18\" cy=\"18\" r=\"1.6\" />\n </svg>\n <span>격자</span>\n </button>\n <button\n type=\"button\"\n class=\"pattern-btn ${this.pattern === 'line-h' ? 'active' : ''}\"\n @click=${() => (this.pattern = 'line-h')}\n title=\"가로 1줄\"\n >\n <svg viewBox=\"0 0 24 24\">\n <circle cx=\"3\" cy=\"12\" r=\"1.6\" /><circle cx=\"8\" cy=\"12\" r=\"1.6\" /><circle cx=\"13\" cy=\"12\" r=\"1.6\" />\n <circle cx=\"18\" cy=\"12\" r=\"1.6\" /><circle cx=\"22\" cy=\"12\" r=\"1.4\" />\n </svg>\n <span>가로</span>\n </button>\n <button\n type=\"button\"\n class=\"pattern-btn ${this.pattern === 'line-v' ? 'active' : ''}\"\n @click=${() => (this.pattern = 'line-v')}\n title=\"세로 1줄\"\n >\n <svg viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"3\" r=\"1.6\" /><circle cx=\"12\" cy=\"8\" r=\"1.6\" /><circle cx=\"12\" cy=\"13\" r=\"1.6\" />\n <circle cx=\"12\" cy=\"18\" r=\"1.6\" /><circle cx=\"12\" cy=\"22\" r=\"1.4\" />\n </svg>\n <span>세로</span>\n </button>\n </div>\n </div>\n\n <div class=\"row\">\n <label>개수</label>\n <input\n id=\"count-input\"\n type=\"number\"\n min=\"1\"\n .value=${String(this.count)}\n @input=${(e: Event) => (this.count = Math.max(1, +(e.target as HTMLInputElement).value))}\n />\n </div>\n\n ${this.pattern === 'grid'\n ? html`\n <div class=\"row\">\n <label>행 × 열</label>\n <div class=\"grid-shape\">\n <input\n type=\"number\"\n min=\"1\"\n .value=${String(this.displayRows)}\n @input=${(e: Event) => {\n const v = (e.target as HTMLInputElement).value\n this.rows = v ? Math.max(1, +v) : undefined\n }}\n />\n <span>×</span>\n <input\n type=\"number\"\n min=\"1\"\n .value=${String(this.displayCols)}\n @input=${(e: Event) => {\n const v = (e.target as HTMLInputElement).value\n this.cols = v ? Math.max(1, +v) : undefined\n }}\n />\n </div>\n </div>\n `\n : ''}\n\n <div class=\"row\">\n <label>ID 패턴</label>\n <input\n type=\"text\"\n .value=${this.idPattern}\n @input=${(e: Event) => (this.idPattern = (e.target as HTMLInputElement).value)}\n />\n </div>\n <div class=\"row\">\n <span></span>\n <div class=\"hint\">placeholder: <code>{i}</code> 일련번호, <code>{r}</code> 행, <code>{c}</code> 열 (grid)</div>\n </div>\n\n <div class=\"row\">\n <label>시작 번호</label>\n <input\n type=\"number\"\n .value=${String(this.idStart)}\n @input=${(e: Event) => (this.idStart = +(e.target as HTMLInputElement).value)}\n />\n </div>\n\n <div class=\"row\">\n <label>Zero-pad 자릿수</label>\n <input\n type=\"number\"\n min=\"0\"\n .value=${String(this.idPadding)}\n @input=${(e: Event) => (this.idPadding = Math.max(0, +(e.target as HTMLInputElement).value))}\n />\n </div>\n\n ${this.hasParentObject\n ? html`\n <div class=\"row\">\n <label>상위 객체</label>\n <ox-input-entity-id\n .localItems=${this.parentObjectItems}\n .value=${this.parentObjectId}\n placeholder=\"상위 객체 id (검색)\"\n @change=${(e: Event) => (this.parentObjectId = (e.target as OxInputEntityId).value)}\n ></ox-input-entity-id>\n </div>\n `\n : ''}\n\n <div class=\"preview\">\n ${models.length > 0\n ? html`총 ${models.length}개 — 예: <code>${models[0].id}</code> ... <code>${models[models.length - 1].id}</code>`\n : html`<span style=\"color:#ff8080;\">생성될 컴포넌트 없음</span>`}\n </div>\n\n ${this.conflicts.length > 0\n ? html`\n <div class=\"conflict\">\n <strong>ID 충돌 ${this.conflicts.length}개</strong>\n <code>${this.conflicts.slice(0, 5).join(', ')}${this.conflicts.length > 5 ? ', ...' : ''}</code>\n <div class=\"conflict-hint\" style=\"margin-top:6px;\">\n <div class=\"policy-radios\">\n <label>\n <input\n type=\"radio\"\n name=\"conflict-policy\"\n value=\"skip\"\n .checked=${this.conflictPolicy === 'skip'}\n @change=${() => (this.conflictPolicy = 'skip')}\n />\n 충돌 ID 스킵 (기본)\n </label>\n <label>\n <input\n type=\"radio\"\n name=\"conflict-policy\"\n value=\"duplicate\"\n .checked=${this.conflictPolicy === 'duplicate'}\n @change=${() => (this.conflictPolicy = 'duplicate')}\n />\n 중복 그대로 생성\n </label>\n </div>\n </div>\n </div>\n `\n : ''}\n\n <div class=\"actions\">\n <button @click=${this.onCancelClick}>취소</button>\n <button\n class=\"primary\"\n @click=${this.onOk}\n ?disabled=${this.effectiveModels.length === 0}\n >\n 생성 (${this.effectiveModels.length}${\n this.conflicts.length > 0 && this.conflictPolicy === 'skip'\n ? html`<span style=\"opacity:0.6;\"> / ${models.length}</span>`\n : ''\n })\n </button>\n </div>\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'bulk-create-dialog': BulkCreateDialog\n }\n}\n"]}
@@ -38,6 +38,12 @@ export interface BulkCreateOptions {
38
38
  * 맞춰 자동 조정할지. default 'template' (원본 크기 유지).
39
39
  */
40
40
  sizeMode?: 'template' | 'fit-cell';
41
+ /**
42
+ * 생성되는 모든 컴포넌트 model 에 병합할 추가 state (도메인 무지).
43
+ * consumer 가 필요 시 주입 (예: fmsim 의 parentObjectId). 이 generic 생성기는
44
+ * 값의 의미를 모른다 — 그냥 각 model 에 spread 한다.
45
+ */
46
+ extraState?: Record<string, any>;
41
47
  }
42
48
  export interface GeneratedModel {
43
49
  type?: string;
@@ -64,7 +64,9 @@ export function generateBulkComponents(opts) {
64
64
  if (!template || typeof template !== 'object')
65
65
  return [];
66
66
  // template 에서 제외할 필드. id/위치 는 새로 생성. (width/height 는 sizeMode 에 따라 처리)
67
- const { id: _id, left: _l, top: _t, width: _w, height: _h, ...rest } = template;
67
+ const { id: _id, left: _l, top: _t, width: _w, height: _h, ...restRaw } = template;
68
+ // consumer 가 주입한 extraState 를 병합 (예: parentObjectId). 각 model 에 그대로 상속된다.
69
+ const rest = opts.extraState ? { ...restRaw, ...opts.extraState } : restRaw;
68
70
  const tplW = typeof template.width === 'number' ? template.width : 50;
69
71
  const tplH = typeof template.height === 'number' ? template.height : 50;
70
72
  const result = [];
@@ -1 +1 @@
1
- {"version":3,"file":"bulk-create.js","sourceRoot":"","sources":["../../../src/modeller/bulk-create.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Bulk-create — 단일 템플릿 컴포넌트로부터 N개의 컴포넌트 model 을 일괄 생성\n * 하는 순수 헬퍼 모음.\n *\n * 설계 원칙:\n * - 순수 함수 (UI / scene side-effect 없음)\n * - 도메인 중립 (factory, board-ai, fmsim, operato-* 등 모든 consumer 가 동일하게 사용)\n * - 의존성 0 (Three.js / Lit 불필요)\n *\n * 사용 패턴:\n * 1. consumer 가 template (= 컴포넌트 model state) + area + 옵션 수집\n * 2. generateBulkComponents() → 새 컴포넌트 model 배열 반환\n * 3. scene.undoableChange(() => scene.add(models)) — 단일 undo 단위로 추가\n *\n * 결합 사용:\n * - deriveGridShape: 격자 패턴의 (rows, cols) 자동 결정\n * - inferIdPattern: 템플릿의 id 로부터 ID 패턴/시작/padding 자동 추출\n * - nextAvailableStart: 반복 호출 시 시작번호 자동 advance (existingIds 스캔)\n * - findIdConflicts: 생성될 ID 가 scene 의 기존 ID 와 충돌하는지 검출\n */\n\nexport type Pattern = 'grid' | 'line-h' | 'line-v'\n\nexport interface Area {\n left: number\n top: number\n width: number\n height: number\n}\n\nexport interface BulkCreateOptions {\n /** 복제할 원본 컴포넌트 state. id, left, top 제외하고 모두 상속. */\n template: Record<string, any>\n /** 타겟 영역 (보드 좌표). */\n area: Area\n /** 분포 패턴. */\n pattern: Pattern\n /**\n * 총 개수. grid 패턴에선 rows × cols 로 분해.\n * rows/cols 가 명시되면 그것 사용, 미명시 시 area 비율과 count 로 자동 결정.\n */\n count: number\n /** grid pattern 용 — 행 수. 미지정 시 area 비율 + count 로 자동. */\n rows?: number\n /** grid pattern 용 — 열 수. 미지정 시 area 비율 + count 로 자동. */\n cols?: number\n /**\n * ID 패턴. placeholder:\n * `{i}` — 일련번호 (0-based + idStart)\n * `{r}` — 행 index (grid 만, 0-based + idStart)\n * `{c}` — 열 index (grid 만, 0-based + idStart)\n * 예: \"PORT_{i}\", \"P_{r}_{c}\", \"CELL{r}{c}\"\n */\n idPattern: string\n /** ID 시작 번호. default 1. */\n idStart?: number\n /** ID 숫자 자릿수 (zero-pad). 0 = 미적용. default 0. */\n idPadding?: number\n /**\n * 컴포넌트의 크기를 사용할지 (= template.width/height 그대로) 또는 cell 에\n * 맞춰 자동 조정할지. default 'template' (원본 크기 유지).\n */\n sizeMode?: 'template' | 'fit-cell'\n}\n\nexport interface GeneratedModel {\n type?: string\n id: string\n left: number\n top: number\n width: number\n height: number\n [key: string]: any\n}\n\n/**\n * 타겟 영역과 총 count 로 grid 의 (rows, cols) 자동 결정.\n * area 의 종횡비를 가능한 한 보존.\n */\nexport function deriveGridShape(\n count: number,\n area: Area,\n rows?: number,\n cols?: number\n): { rows: number; cols: number } {\n if (rows && cols) return { rows, cols }\n if (rows) return { rows, cols: Math.ceil(count / rows) }\n if (cols) return { rows: Math.ceil(count / cols), cols }\n\n // 자동: area 의 종횡비와 비슷한 grid 도출.\n // cols / rows ≈ width / height → cols ≈ sqrt(count × width / height)\n const aspect = area.width / Math.max(area.height, 1)\n const c = Math.max(1, Math.round(Math.sqrt(count * aspect)))\n const r = Math.max(1, Math.ceil(count / c))\n return { rows: r, cols: c }\n}\n\n/**\n * ID 패턴 치환 — {i}, {r}, {c} placeholder.\n */\nfunction formatId(\n pattern: string,\n i: number,\n r: number,\n c: number,\n start: number,\n padding: number\n): string {\n const pad = (n: number) => {\n const v = n + start\n return padding > 0 ? String(v).padStart(padding, '0') : String(v)\n }\n return pattern\n .replace(/\\{i\\}/g, pad(i))\n .replace(/\\{r\\}/g, pad(r))\n .replace(/\\{c\\}/g, pad(c))\n}\n\n/**\n * 메인 헬퍼 — 옵션을 받아 새 컴포넌트 model 배열 반환.\n *\n * **주의**: 반환된 model 은 `scene.add(models)` 로 추가하면 된다. ID 충돌 사전\n * 검사는 호출 측 책임 — `findIdConflicts` 와 결합 사용.\n */\nexport function generateBulkComponents(opts: BulkCreateOptions): GeneratedModel[] {\n const {\n template,\n area,\n pattern,\n count,\n idPattern,\n idStart = 1,\n idPadding = 0,\n sizeMode = 'template'\n } = opts\n\n if (count <= 0) return []\n if (!template || typeof template !== 'object') return []\n\n // template 에서 제외할 필드. id/위치 는 새로 생성. (width/height 는 sizeMode 에 따라 처리)\n const { id: _id, left: _l, top: _t, width: _w, height: _h, ...rest } = template\n\n const tplW = typeof template.width === 'number' ? template.width : 50\n const tplH = typeof template.height === 'number' ? template.height : 50\n\n const result: GeneratedModel[] = []\n\n if (pattern === 'grid') {\n const { rows, cols } = deriveGridShape(count, area, opts.rows, opts.cols)\n const cellW = area.width / cols\n const cellH = area.height / rows\n const itemW = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplW\n const itemH = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplH\n\n let i = 0\n outer: for (let r = 0; r < rows; r++) {\n for (let c = 0; c < cols; c++) {\n if (i >= count) break outer\n const cx = area.left + cellW * (c + 0.5)\n const cy = area.top + cellH * (r + 0.5)\n result.push({\n ...rest,\n id: formatId(idPattern, i, r, c, idStart, idPadding),\n left: cx - itemW / 2,\n top: cy - itemH / 2,\n width: itemW,\n height: itemH\n })\n i++\n }\n }\n } else if (pattern === 'line-h' || pattern === 'line-v') {\n const isH = pattern === 'line-h'\n const span = isH ? area.width : area.height\n const cellSize = span / count\n const itemW = sizeMode === 'fit-cell' && isH ? cellSize * 0.8 : tplW\n const itemH = sizeMode === 'fit-cell' && !isH ? cellSize * 0.8 : tplH\n\n for (let i = 0; i < count; i++) {\n const along = (i + 0.5) * cellSize\n const cx = isH ? area.left + along : area.left + area.width / 2\n const cy = isH ? area.top + area.height / 2 : area.top + along\n result.push({\n ...rest,\n id: formatId(idPattern, i, 0, i, idStart, idPadding),\n left: cx - itemW / 2,\n top: cy - itemH / 2,\n width: itemW,\n height: itemH\n })\n }\n }\n\n return result\n}\n\n/**\n * 생성될 model 의 id 중 기존 scene 의 id 와 충돌하는 것 검출.\n * @returns 충돌하는 id 배열 (없으면 빈 배열).\n */\nexport function findIdConflicts(\n models: GeneratedModel[],\n existingIds: Iterable<string>\n): string[] {\n const set = existingIds instanceof Set ? existingIds : new Set(existingIds)\n const conflicts: string[] = []\n for (const m of models) {\n if (set.has(m.id)) conflicts.push(m.id)\n }\n return conflicts\n}\n\n/**\n * 템플릿의 id 문자열로부터 다음 컴포넌트들의 ID 패턴을 자동 추론.\n *\n * 규칙:\n * - 끝에 숫자 있음: `\"PORT_05\"` → `{ pattern: \"PORT_{i}\", start: 6, padding: 2 }`\n * - 끝에 숫자 없음: `\"abc\"` → `{ pattern: \"abc_{i}\", start: 1, padding: 0 }`\n * - 빈 id: `{ pattern: \"{i}\", start: 1, padding: 0 }`\n *\n * padding 은 *leading zero* 가 있을 때만 적용:\n * - \"05\" → padding 2 (= \"01\", \"02\"... 형식 유지)\n * - \"5\" → padding 0 (= \"1\", \"2\"... 형식)\n */\nexport function inferIdPattern(id: string | undefined | null): {\n pattern: string\n start: number\n padding: number\n} {\n if (!id || typeof id !== 'string') {\n return { pattern: '{i}', start: 1, padding: 0 }\n }\n const m = id.match(/^(.*?)(\\d+)$/)\n if (m) {\n const prefix = m[1]\n const numStr = m[2]\n const num = parseInt(numStr, 10)\n const padding = numStr.length > 1 && numStr.startsWith('0') ? numStr.length : 0\n return {\n pattern: `${prefix}{i}`,\n start: num + 1,\n padding\n }\n }\n return {\n pattern: `${id}_{i}`,\n start: 1,\n padding: 0\n }\n}\n\n/**\n * 패턴이 매칭하는 기존 ID 들 중 *최대 번호 + 1* 반환 — 반복 bulk-create 시\n * 시작 번호 자동 advance 용.\n *\n * 예:\n * pattern = \"PORT_{i}\", existingIds = [\"PORT_05\", \"PORT_12\", \"Other_1\"]\n * → 13 (= 12 + 1)\n *\n * 매칭 안 되면 fallbackStart 반환 (기본 inferIdPattern 의 start).\n *\n * NOTE: 현재 `{i}` placeholder 만 인식. `{r}` / `{c}` 는 grid 전용이므로 별도\n * 처리 필요 (추후).\n */\nexport function nextAvailableStart(\n idPattern: string,\n existingIds: Iterable<string>,\n fallbackStart: number\n): number {\n if (!idPattern.includes('{i}')) return fallbackStart\n // pattern → regex. {i} → 캡쳐, {r}/{c} → 그냥 숫자, 특수문자 escape.\n const escaped = idPattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const re = new RegExp(\n '^' +\n escaped\n .replace(/\\\\\\{i\\\\\\}/g, '(\\\\d+)')\n .replace(/\\\\\\{[rc]\\\\\\}/g, '\\\\d+') +\n '$'\n )\n let max = -1\n for (const id of existingIds) {\n const m = id.match(re)\n if (m) {\n const n = parseInt(m[1], 10)\n if (!Number.isNaN(n) && n > max) max = n\n }\n }\n return max >= 0 ? max + 1 : fallbackStart\n}\n"]}
1
+ {"version":3,"file":"bulk-create.js","sourceRoot":"","sources":["../../../src/modeller/bulk-create.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Bulk-create — 단일 템플릿 컴포넌트로부터 N개의 컴포넌트 model 을 일괄 생성\n * 하는 순수 헬퍼 모음.\n *\n * 설계 원칙:\n * - 순수 함수 (UI / scene side-effect 없음)\n * - 도메인 중립 (factory, board-ai, fmsim, operato-* 등 모든 consumer 가 동일하게 사용)\n * - 의존성 0 (Three.js / Lit 불필요)\n *\n * 사용 패턴:\n * 1. consumer 가 template (= 컴포넌트 model state) + area + 옵션 수집\n * 2. generateBulkComponents() → 새 컴포넌트 model 배열 반환\n * 3. scene.undoableChange(() => scene.add(models)) — 단일 undo 단위로 추가\n *\n * 결합 사용:\n * - deriveGridShape: 격자 패턴의 (rows, cols) 자동 결정\n * - inferIdPattern: 템플릿의 id 로부터 ID 패턴/시작/padding 자동 추출\n * - nextAvailableStart: 반복 호출 시 시작번호 자동 advance (existingIds 스캔)\n * - findIdConflicts: 생성될 ID 가 scene 의 기존 ID 와 충돌하는지 검출\n */\n\nexport type Pattern = 'grid' | 'line-h' | 'line-v'\n\nexport interface Area {\n left: number\n top: number\n width: number\n height: number\n}\n\nexport interface BulkCreateOptions {\n /** 복제할 원본 컴포넌트 state. id, left, top 제외하고 모두 상속. */\n template: Record<string, any>\n /** 타겟 영역 (보드 좌표). */\n area: Area\n /** 분포 패턴. */\n pattern: Pattern\n /**\n * 총 개수. grid 패턴에선 rows × cols 로 분해.\n * rows/cols 가 명시되면 그것 사용, 미명시 시 area 비율과 count 로 자동 결정.\n */\n count: number\n /** grid pattern 용 — 행 수. 미지정 시 area 비율 + count 로 자동. */\n rows?: number\n /** grid pattern 용 — 열 수. 미지정 시 area 비율 + count 로 자동. */\n cols?: number\n /**\n * ID 패턴. placeholder:\n * `{i}` — 일련번호 (0-based + idStart)\n * `{r}` — 행 index (grid 만, 0-based + idStart)\n * `{c}` — 열 index (grid 만, 0-based + idStart)\n * 예: \"PORT_{i}\", \"P_{r}_{c}\", \"CELL{r}{c}\"\n */\n idPattern: string\n /** ID 시작 번호. default 1. */\n idStart?: number\n /** ID 숫자 자릿수 (zero-pad). 0 = 미적용. default 0. */\n idPadding?: number\n /**\n * 컴포넌트의 크기를 사용할지 (= template.width/height 그대로) 또는 cell 에\n * 맞춰 자동 조정할지. default 'template' (원본 크기 유지).\n */\n sizeMode?: 'template' | 'fit-cell'\n /**\n * 생성되는 모든 컴포넌트 model 에 병합할 추가 state (도메인 무지).\n * consumer 가 필요 시 주입 (예: fmsim 의 parentObjectId). 이 generic 생성기는\n * 값의 의미를 모른다 — 그냥 각 model 에 spread 한다.\n */\n extraState?: Record<string, any>\n}\n\nexport interface GeneratedModel {\n type?: string\n id: string\n left: number\n top: number\n width: number\n height: number\n [key: string]: any\n}\n\n/**\n * 타겟 영역과 총 count 로 grid 의 (rows, cols) 자동 결정.\n * area 의 종횡비를 가능한 한 보존.\n */\nexport function deriveGridShape(\n count: number,\n area: Area,\n rows?: number,\n cols?: number\n): { rows: number; cols: number } {\n if (rows && cols) return { rows, cols }\n if (rows) return { rows, cols: Math.ceil(count / rows) }\n if (cols) return { rows: Math.ceil(count / cols), cols }\n\n // 자동: area 의 종횡비와 비슷한 grid 도출.\n // cols / rows ≈ width / height → cols ≈ sqrt(count × width / height)\n const aspect = area.width / Math.max(area.height, 1)\n const c = Math.max(1, Math.round(Math.sqrt(count * aspect)))\n const r = Math.max(1, Math.ceil(count / c))\n return { rows: r, cols: c }\n}\n\n/**\n * ID 패턴 치환 — {i}, {r}, {c} placeholder.\n */\nfunction formatId(\n pattern: string,\n i: number,\n r: number,\n c: number,\n start: number,\n padding: number\n): string {\n const pad = (n: number) => {\n const v = n + start\n return padding > 0 ? String(v).padStart(padding, '0') : String(v)\n }\n return pattern\n .replace(/\\{i\\}/g, pad(i))\n .replace(/\\{r\\}/g, pad(r))\n .replace(/\\{c\\}/g, pad(c))\n}\n\n/**\n * 메인 헬퍼 — 옵션을 받아 새 컴포넌트 model 배열 반환.\n *\n * **주의**: 반환된 model 은 `scene.add(models)` 로 추가하면 된다. ID 충돌 사전\n * 검사는 호출 측 책임 — `findIdConflicts` 와 결합 사용.\n */\nexport function generateBulkComponents(opts: BulkCreateOptions): GeneratedModel[] {\n const {\n template,\n area,\n pattern,\n count,\n idPattern,\n idStart = 1,\n idPadding = 0,\n sizeMode = 'template'\n } = opts\n\n if (count <= 0) return []\n if (!template || typeof template !== 'object') return []\n\n // template 에서 제외할 필드. id/위치 는 새로 생성. (width/height 는 sizeMode 에 따라 처리)\n const { id: _id, left: _l, top: _t, width: _w, height: _h, ...restRaw } = template\n // consumer 가 주입한 extraState 를 병합 (예: parentObjectId). 각 model 에 그대로 상속된다.\n const rest = opts.extraState ? { ...restRaw, ...opts.extraState } : restRaw\n\n const tplW = typeof template.width === 'number' ? template.width : 50\n const tplH = typeof template.height === 'number' ? template.height : 50\n\n const result: GeneratedModel[] = []\n\n if (pattern === 'grid') {\n const { rows, cols } = deriveGridShape(count, area, opts.rows, opts.cols)\n const cellW = area.width / cols\n const cellH = area.height / rows\n const itemW = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplW\n const itemH = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplH\n\n let i = 0\n outer: for (let r = 0; r < rows; r++) {\n for (let c = 0; c < cols; c++) {\n if (i >= count) break outer\n const cx = area.left + cellW * (c + 0.5)\n const cy = area.top + cellH * (r + 0.5)\n result.push({\n ...rest,\n id: formatId(idPattern, i, r, c, idStart, idPadding),\n left: cx - itemW / 2,\n top: cy - itemH / 2,\n width: itemW,\n height: itemH\n })\n i++\n }\n }\n } else if (pattern === 'line-h' || pattern === 'line-v') {\n const isH = pattern === 'line-h'\n const span = isH ? area.width : area.height\n const cellSize = span / count\n const itemW = sizeMode === 'fit-cell' && isH ? cellSize * 0.8 : tplW\n const itemH = sizeMode === 'fit-cell' && !isH ? cellSize * 0.8 : tplH\n\n for (let i = 0; i < count; i++) {\n const along = (i + 0.5) * cellSize\n const cx = isH ? area.left + along : area.left + area.width / 2\n const cy = isH ? area.top + area.height / 2 : area.top + along\n result.push({\n ...rest,\n id: formatId(idPattern, i, 0, i, idStart, idPadding),\n left: cx - itemW / 2,\n top: cy - itemH / 2,\n width: itemW,\n height: itemH\n })\n }\n }\n\n return result\n}\n\n/**\n * 생성될 model 의 id 중 기존 scene 의 id 와 충돌하는 것 검출.\n * @returns 충돌하는 id 배열 (없으면 빈 배열).\n */\nexport function findIdConflicts(\n models: GeneratedModel[],\n existingIds: Iterable<string>\n): string[] {\n const set = existingIds instanceof Set ? existingIds : new Set(existingIds)\n const conflicts: string[] = []\n for (const m of models) {\n if (set.has(m.id)) conflicts.push(m.id)\n }\n return conflicts\n}\n\n/**\n * 템플릿의 id 문자열로부터 다음 컴포넌트들의 ID 패턴을 자동 추론.\n *\n * 규칙:\n * - 끝에 숫자 있음: `\"PORT_05\"` → `{ pattern: \"PORT_{i}\", start: 6, padding: 2 }`\n * - 끝에 숫자 없음: `\"abc\"` → `{ pattern: \"abc_{i}\", start: 1, padding: 0 }`\n * - 빈 id: `{ pattern: \"{i}\", start: 1, padding: 0 }`\n *\n * padding 은 *leading zero* 가 있을 때만 적용:\n * - \"05\" → padding 2 (= \"01\", \"02\"... 형식 유지)\n * - \"5\" → padding 0 (= \"1\", \"2\"... 형식)\n */\nexport function inferIdPattern(id: string | undefined | null): {\n pattern: string\n start: number\n padding: number\n} {\n if (!id || typeof id !== 'string') {\n return { pattern: '{i}', start: 1, padding: 0 }\n }\n const m = id.match(/^(.*?)(\\d+)$/)\n if (m) {\n const prefix = m[1]\n const numStr = m[2]\n const num = parseInt(numStr, 10)\n const padding = numStr.length > 1 && numStr.startsWith('0') ? numStr.length : 0\n return {\n pattern: `${prefix}{i}`,\n start: num + 1,\n padding\n }\n }\n return {\n pattern: `${id}_{i}`,\n start: 1,\n padding: 0\n }\n}\n\n/**\n * 패턴이 매칭하는 기존 ID 들 중 *최대 번호 + 1* 반환 — 반복 bulk-create 시\n * 시작 번호 자동 advance 용.\n *\n * 예:\n * pattern = \"PORT_{i}\", existingIds = [\"PORT_05\", \"PORT_12\", \"Other_1\"]\n * → 13 (= 12 + 1)\n *\n * 매칭 안 되면 fallbackStart 반환 (기본 inferIdPattern 의 start).\n *\n * NOTE: 현재 `{i}` placeholder 만 인식. `{r}` / `{c}` 는 grid 전용이므로 별도\n * 처리 필요 (추후).\n */\nexport function nextAvailableStart(\n idPattern: string,\n existingIds: Iterable<string>,\n fallbackStart: number\n): number {\n if (!idPattern.includes('{i}')) return fallbackStart\n // pattern → regex. {i} → 캡쳐, {r}/{c} → 그냥 숫자, 특수문자 escape.\n const escaped = idPattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const re = new RegExp(\n '^' +\n escaped\n .replace(/\\\\\\{i\\\\\\}/g, '(\\\\d+)')\n .replace(/\\\\\\{[rc]\\\\\\}/g, '\\\\d+') +\n '$'\n )\n let max = -1\n for (const id of existingIds) {\n const m = id.match(re)\n if (m) {\n const n = parseInt(m[1], 10)\n if (!Number.isNaN(n) && n > max) max = n\n }\n }\n return max >= 0 ? max + 1 : fallbackStart\n}\n"]}
@@ -21,6 +21,12 @@ export declare class ComponentToolbar extends ComponentToolbar_base {
21
21
  static get scopedElements(): {
22
22
  'component-menu': typeof ComponentMenu;
23
23
  };
24
+ /**
25
+ * 3D 비활성(전역 isThreedEnabled()=false)에서는 3D 전용 컴포넌트 타입
26
+ * (requires3d — decal/backdrop/floor-plate 등)을 팔레트에서 제외하고, 비게 된 그룹도 숨긴다.
27
+ * 3D 활성(기본)이면 원본 목록 그대로.
28
+ */
29
+ private get _visibleGroupList();
24
30
  render(): import("lit-html").TemplateResult<1>;
25
31
  onWheelEvent(e: Event): void;
26
32
  _setMode(mode: SceneMode): void;
@@ -4,7 +4,7 @@
4
4
  import { __decorate } from "tslib";
5
5
  import { css, html, LitElement } from 'lit';
6
6
  import { property, query, queryAll } from 'lit/decorators.js';
7
- import { SCENE_MODE } from '@hatiolab/things-scene';
7
+ import { Component, SCENE_MODE, isThreedEnabled } from '@hatiolab/things-scene';
8
8
  import { ScopedElementsMixin } from '@open-wc/scoped-elements';
9
9
  import { ComponentMenu } from './component-menu.js';
10
10
  import { ICON_EDIT_MODE, ICON_SHIFT_MODE } from './mode-icons';
@@ -44,8 +44,24 @@ export class ComponentToolbar extends ScopedElementsMixin(LitElement) {
44
44
  'component-menu': ComponentMenu
45
45
  };
46
46
  }
47
+ /**
48
+ * 3D 비활성(전역 isThreedEnabled()=false)에서는 3D 전용 컴포넌트 타입
49
+ * (requires3d — decal/backdrop/floor-plate 등)을 팔레트에서 제외하고, 비게 된 그룹도 숨긴다.
50
+ * 3D 활성(기본)이면 원본 목록 그대로.
51
+ */
52
+ get _visibleGroupList() {
53
+ const list = this.componentGroupList || [];
54
+ if (isThreedEnabled())
55
+ return list;
56
+ return list
57
+ .map(group => ({
58
+ ...group,
59
+ templates: (group.templates || []).filter(t => { var _a; return !((_a = Component.register(t.type)) === null || _a === void 0 ? void 0 : _a.requires3d); })
60
+ }))
61
+ .filter(group => group.templates.length > 0);
62
+ }
47
63
  render() {
48
- const componentList = this.componentGroupList || [];
64
+ const componentList = this._visibleGroupList;
49
65
  const modeIcon = this.icons[this.mode === SCENE_MODE.SHIFT ? 'mode-shift' : 'mode-edit'];
50
66
  return html `
51
67
  <div components @mousewheel=${(e) => this.onWheelEvent(e)}>
@@ -1 +1 @@
1
- {"version":3,"file":"component-toolbar.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-toolbar.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css, html, LitElement, PropertyValues } from 'lit'\nimport { property, query, queryAll } from 'lit/decorators.js'\n\nimport { Scene, SCENE_MODE, SceneMode } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\n\nimport { ComponentGroup } from '../../types'\nimport { ComponentMenu } from './component-menu.js'\nimport { ICON_EDIT_MODE, ICON_SHIFT_MODE } from './mode-icons'\n\nexport class ComponentToolbar extends ScopedElementsMixin(LitElement) {\n static styles = [\n css`\n :host {\n left: 0;\n display: block;\n position: relative;\n\n width: var(--component-toolbar-icon-size);\n background-color: var(--component-toolbar-background-color);\n }\n\n [components] {\n display: block;\n padding: 0;\n margin: 0;\n\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n [components] > img {\n display: flex;\n flex-direction: row;\n\n width: var(--component-toolbar-icon-size);\n height: var(--component-toolbar-icon-size);\n min-width: 50%;\n\n border-bottom: var(--component-toolbar-border);\n margin: 0;\n padding: 6px;\n box-sizing: border-box;\n }\n\n #shift[active] {\n background-color: #beb9b3;\n }\n\n component-menu {\n position: absolute;\n top: 0;\n left: var(--component-toolbar-icon-size);\n height: 100%;\n outline: none;\n }\n\n component-menu[hidden] {\n display: none;\n }\n `\n ]\n\n @property({ type: Array }) componentGroupList: ComponentGroup[] = []\n @property({ type: String }) group?: string | null\n @property({ type: Object }) scene!: Scene\n @property({ type: Number }) mode: SceneMode = SCENE_MODE.EDIT\n\n @query('#shift') shift!: HTMLImageElement\n @query('component-menu') private menu!: HTMLElement\n @query('div[components]') private componentsContainer!: HTMLElement\n @queryAll('[data-group]') private groups!: NodeListOf<HTMLElement>\n\n private icons: { [name: string]: string } = {}\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('componentGroupList')) {\n const color = getComputedStyle(this, null).getPropertyValue('--primary-color')\n\n this.icons = (this.componentGroupList || [])\n .filter(group => group.templates?.length > 0)\n .reduce((sum, group: ComponentGroup) => {\n sum[group.name] =\n 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(group.icon.replace(/{{strokeColor}}/g, color))\n return sum\n }, {} as { [name: string]: string })\n\n this.icons['mode-edit'] =\n 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(ICON_EDIT_MODE.replace(/{{strokeColor}}/g, color))\n this.icons['mode-shift'] =\n 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(ICON_SHIFT_MODE.replace(/{{strokeColor}}/g, color))\n }\n\n if (changes.has('group')) {\n this.groups.forEach(group => {\n group.getAttribute('data-group') === this.group\n ? group.setAttribute('active', '')\n : group.removeAttribute('active')\n })\n\n this.group ? this.menu.removeAttribute('hidden') : this.menu.setAttribute('hidden', '')\n }\n }\n\n static get scopedElements() {\n return {\n 'component-menu': ComponentMenu\n }\n }\n\n render() {\n const componentList = this.componentGroupList || []\n const modeIcon = this.icons[this.mode === SCENE_MODE.SHIFT ? 'mode-shift' : 'mode-edit']\n\n return html`\n <div components @mousewheel=${(e: MouseEvent) => this.onWheelEvent(e)}>\n <img id=\"shift\" .src=${modeIcon} @click=${(e: MouseEvent) => this._onClickShift(e)} />\n\n ${componentList\n .filter(group => group.templates?.length > 0)\n .map(\n group => html`\n <img\n data-group=${group.name}\n @click=${(e: MouseEvent) => this._onClickGroup(e)}\n .src=${this.icons[group.name]}\n />\n `\n )}\n </div>\n\n <component-menu\n tabindex=\"-1\"\n @focusout=${() => {\n this.group = null\n }}\n .scene=${this.scene}\n .group=${this.group}\n .groups=${componentList}\n hidden\n >\n </component-menu>\n `\n }\n\n onWheelEvent(e: Event) {\n var delta = Math.max(-1, Math.min(1, (e as WheelEvent).deltaY || -(e as WheelEvent).detail))\n this.componentsContainer.scrollTop += delta * 10\n\n e.preventDefault()\n }\n\n _setMode(mode: SceneMode) {\n this.mode = mode\n\n this.dispatchEvent(\n new CustomEvent('mode-changed', {\n bubbles: true,\n composed: true,\n detail: { value: mode }\n })\n )\n }\n\n _onClickShift(e: MouseEvent) {\n const shift = this.shift\n\n if (shift.hasAttribute('active')) {\n shift.removeAttribute('active')\n this._setMode(SCENE_MODE.EDIT)\n } else {\n shift.setAttribute('active', '')\n this._setMode(SCENE_MODE.SHIFT)\n }\n }\n\n async _onClickGroup(e: MouseEvent) {\n var button = e.target as HTMLElement\n\n this.group = button!.closest('[data-group]')?.getAttribute('data-group')\n\n if (!this.group) return\n\n await this.updateComplete\n\n this.menu?.focus()\n }\n}\n"]}
1
+ {"version":3,"file":"component-toolbar.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-toolbar.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css, html, LitElement, PropertyValues } from 'lit'\nimport { property, query, queryAll } from 'lit/decorators.js'\n\nimport { Component, Scene, SCENE_MODE, SceneMode, isThreedEnabled } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\n\nimport { ComponentGroup } from '../../types'\nimport { ComponentMenu } from './component-menu.js'\nimport { ICON_EDIT_MODE, ICON_SHIFT_MODE } from './mode-icons'\n\nexport class ComponentToolbar extends ScopedElementsMixin(LitElement) {\n static styles = [\n css`\n :host {\n left: 0;\n display: block;\n position: relative;\n\n width: var(--component-toolbar-icon-size);\n background-color: var(--component-toolbar-background-color);\n }\n\n [components] {\n display: block;\n padding: 0;\n margin: 0;\n\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n [components] > img {\n display: flex;\n flex-direction: row;\n\n width: var(--component-toolbar-icon-size);\n height: var(--component-toolbar-icon-size);\n min-width: 50%;\n\n border-bottom: var(--component-toolbar-border);\n margin: 0;\n padding: 6px;\n box-sizing: border-box;\n }\n\n #shift[active] {\n background-color: #beb9b3;\n }\n\n component-menu {\n position: absolute;\n top: 0;\n left: var(--component-toolbar-icon-size);\n height: 100%;\n outline: none;\n }\n\n component-menu[hidden] {\n display: none;\n }\n `\n ]\n\n @property({ type: Array }) componentGroupList: ComponentGroup[] = []\n @property({ type: String }) group?: string | null\n @property({ type: Object }) scene!: Scene\n @property({ type: Number }) mode: SceneMode = SCENE_MODE.EDIT\n\n @query('#shift') shift!: HTMLImageElement\n @query('component-menu') private menu!: HTMLElement\n @query('div[components]') private componentsContainer!: HTMLElement\n @queryAll('[data-group]') private groups!: NodeListOf<HTMLElement>\n\n private icons: { [name: string]: string } = {}\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('componentGroupList')) {\n const color = getComputedStyle(this, null).getPropertyValue('--primary-color')\n\n this.icons = (this.componentGroupList || [])\n .filter(group => group.templates?.length > 0)\n .reduce((sum, group: ComponentGroup) => {\n sum[group.name] =\n 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(group.icon.replace(/{{strokeColor}}/g, color))\n return sum\n }, {} as { [name: string]: string })\n\n this.icons['mode-edit'] =\n 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(ICON_EDIT_MODE.replace(/{{strokeColor}}/g, color))\n this.icons['mode-shift'] =\n 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(ICON_SHIFT_MODE.replace(/{{strokeColor}}/g, color))\n }\n\n if (changes.has('group')) {\n this.groups.forEach(group => {\n group.getAttribute('data-group') === this.group\n ? group.setAttribute('active', '')\n : group.removeAttribute('active')\n })\n\n this.group ? this.menu.removeAttribute('hidden') : this.menu.setAttribute('hidden', '')\n }\n }\n\n static get scopedElements() {\n return {\n 'component-menu': ComponentMenu\n }\n }\n\n /**\n * 3D 비활성(전역 isThreedEnabled()=false)에서는 3D 전용 컴포넌트 타입\n * (requires3d — decal/backdrop/floor-plate 등)을 팔레트에서 제외하고, 비게 된 그룹도 숨긴다.\n * 3D 활성(기본)이면 원본 목록 그대로.\n */\n private get _visibleGroupList(): ComponentGroup[] {\n const list = this.componentGroupList || []\n if (isThreedEnabled()) return list\n\n return list\n .map(group => ({\n ...group,\n templates: (group.templates || []).filter(t => !Component.register(t.type)?.requires3d)\n }))\n .filter(group => group.templates.length > 0)\n }\n\n render() {\n const componentList = this._visibleGroupList\n const modeIcon = this.icons[this.mode === SCENE_MODE.SHIFT ? 'mode-shift' : 'mode-edit']\n\n return html`\n <div components @mousewheel=${(e: MouseEvent) => this.onWheelEvent(e)}>\n <img id=\"shift\" .src=${modeIcon} @click=${(e: MouseEvent) => this._onClickShift(e)} />\n\n ${componentList\n .filter(group => group.templates?.length > 0)\n .map(\n group => html`\n <img\n data-group=${group.name}\n @click=${(e: MouseEvent) => this._onClickGroup(e)}\n .src=${this.icons[group.name]}\n />\n `\n )}\n </div>\n\n <component-menu\n tabindex=\"-1\"\n @focusout=${() => {\n this.group = null\n }}\n .scene=${this.scene}\n .group=${this.group}\n .groups=${componentList}\n hidden\n >\n </component-menu>\n `\n }\n\n onWheelEvent(e: Event) {\n var delta = Math.max(-1, Math.min(1, (e as WheelEvent).deltaY || -(e as WheelEvent).detail))\n this.componentsContainer.scrollTop += delta * 10\n\n e.preventDefault()\n }\n\n _setMode(mode: SceneMode) {\n this.mode = mode\n\n this.dispatchEvent(\n new CustomEvent('mode-changed', {\n bubbles: true,\n composed: true,\n detail: { value: mode }\n })\n )\n }\n\n _onClickShift(e: MouseEvent) {\n const shift = this.shift\n\n if (shift.hasAttribute('active')) {\n shift.removeAttribute('active')\n this._setMode(SCENE_MODE.EDIT)\n } else {\n shift.setAttribute('active', '')\n this._setMode(SCENE_MODE.SHIFT)\n }\n }\n\n async _onClickGroup(e: MouseEvent) {\n var button = e.target as HTMLElement\n\n this.group = button!.closest('[data-group]')?.getAttribute('data-group')\n\n if (!this.group) return\n\n await this.updateComplete\n\n this.menu?.focus()\n }\n}\n"]}
@@ -8,6 +8,7 @@ import { copyToClipboard, isMacOS } from '@operato/utils';
8
8
  import { OxPopup } from '@operato/popup';
9
9
  import { style } from './edit-toolbar-style';
10
10
  import './bulk-create-dialog.js';
11
+ import { parentObjectForTemplate } from './property-sidebar/specifics/parent-object.js';
11
12
  const MACOS = isMacOS();
12
13
  /**
13
14
  * 컴포넌트 model JSON 만으로 type-aware bounding box (width, height) 추출.
@@ -587,6 +588,11 @@ export class EditToolbar extends LitElement {
587
588
  if (root)
588
589
  collectIds(root);
589
590
  dialogEl.existingIds = existingIds;
591
+ // parentObject 커스터마이징: template 타입이 ParentObjectMixin 이면 상위객체 선택 필드 노출.
592
+ // 후보 리스트는 타입의 parentObjectTypes 기준으로 씬에서 수집 (속성창과 동일 로직 재사용).
593
+ const { has, items } = parentObjectForTemplate(template, root);
594
+ dialogEl.hasParentObject = has;
595
+ dialogEl.parentObjectItems = items;
590
596
  dialogEl.onConfirm = (models) => {
591
597
  const scene = this.scene;
592
598
  if (!scene || !models.length) {
@@ -616,7 +622,10 @@ export class EditToolbar extends LitElement {
616
622
  };
617
623
  const popup = OxPopup.open({
618
624
  template: dialogEl,
619
- backdrop: true
625
+ backdrop: true,
626
+ // 내부 입력(parentObject 검색 드롭다운 등)이 blur/focusout 될 때 팝업이 자동으로
627
+ // 닫히지 않도록. 닫기는 OK/취소 버튼 또는 backdrop/Escape 로만.
628
+ preventCloseOnBlur: true
620
629
  });
621
630
  }
622
631
  onTapSelectAll() {