@fmsim/board 1.0.88 → 2.0.0-beta.101

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 (144) hide show
  1. package/custom-elements.json +7366 -3448
  2. package/demo/index-modeller.html +1 -1
  3. package/demo/index-player.html +2 -2
  4. package/demo/index-viewer.html +2 -2
  5. package/demo/index.html +2 -2
  6. package/dist/src/component/etc.js +36 -0
  7. package/dist/src/component/etc.js.map +1 -1
  8. package/dist/src/component/index.d.ts +1 -0
  9. package/dist/src/component/index.js +1 -0
  10. package/dist/src/component/index.js.map +1 -1
  11. package/dist/src/component/restful-attachment-creation-card.js +1 -1
  12. package/dist/src/component/restful-attachment-creation-card.js.map +1 -1
  13. package/dist/src/component/restful-attachment-selector.js +8 -9
  14. package/dist/src/component/restful-attachment-selector.js.map +1 -1
  15. package/dist/src/component/restful-input-attachment-selector.d.ts +1 -1
  16. package/dist/src/component/restful-input-attachment-selector.js +3 -3
  17. package/dist/src/component/restful-input-attachment-selector.js.map +1 -1
  18. package/dist/src/component/restful-input-fill-style.d.ts +13 -2
  19. package/dist/src/component/restful-input-fill-style.js.map +1 -1
  20. package/dist/src/component/restful-property-editor-backdrop-faces.d.ts +50 -0
  21. package/dist/src/component/restful-property-editor-backdrop-faces.js +524 -0
  22. package/dist/src/component/restful-property-editor-backdrop-faces.js.map +1 -0
  23. package/dist/src/index.d.ts +1 -0
  24. package/dist/src/index.js +1 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/layers/mcs-mouse-event-handler.js +8 -0
  27. package/dist/src/layers/mcs-mouse-event-handler.js.map +1 -1
  28. package/dist/src/layers/movement-layer.d.ts +7 -6
  29. package/dist/src/layers/movement-layer.js +56 -11
  30. package/dist/src/layers/movement-layer.js.map +1 -1
  31. package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
  32. package/dist/src/modeller/bulk-create-dialog.js +531 -0
  33. package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
  34. package/dist/src/modeller/bulk-create.d.ts +101 -0
  35. package/dist/src/modeller/bulk-create.js +199 -0
  36. package/dist/src/modeller/bulk-create.js.map +1 -0
  37. package/dist/src/modeller/component-toolbar/component-menu.d.ts +1 -1
  38. package/dist/src/modeller/component-toolbar/component-toolbar.d.ts +4 -4
  39. package/dist/src/modeller/component-toolbar/component-toolbar.js.map +1 -1
  40. package/dist/src/modeller/edit-toolbar-style.js +43 -3
  41. package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
  42. package/dist/src/modeller/edit-toolbar.d.ts +21 -14
  43. package/dist/src/modeller/edit-toolbar.js +371 -213
  44. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  45. package/dist/src/modeller/property-sidebar/abstract-property.js +10 -2
  46. package/dist/src/modeller/property-sidebar/abstract-property.js.map +1 -1
  47. package/dist/src/modeller/property-sidebar/data-binding/data-binding-mapper.js +1 -1
  48. package/dist/src/modeller/property-sidebar/data-binding/data-binding-mapper.js.map +1 -1
  49. package/dist/src/modeller/property-sidebar/data-binding/data-binding.d.ts +2 -2
  50. package/dist/src/modeller/property-sidebar/data-binding/data-binding.js +18 -18
  51. package/dist/src/modeller/property-sidebar/data-binding/data-binding.js.map +1 -1
  52. package/dist/src/modeller/property-sidebar/effects/effects.d.ts +10 -1
  53. package/dist/src/modeller/property-sidebar/effects/effects.js +66 -1
  54. package/dist/src/modeller/property-sidebar/effects/effects.js.map +1 -1
  55. package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.d.ts +44 -0
  56. package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js +70 -0
  57. package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js.map +1 -0
  58. package/dist/src/modeller/property-sidebar/effects/property-event-hover.js +3 -3
  59. package/dist/src/modeller/property-sidebar/effects/property-event-hover.js.map +1 -1
  60. package/dist/src/modeller/property-sidebar/effects/property-event.d.ts +13 -3
  61. package/dist/src/modeller/property-sidebar/effects/property-event.js +20 -11
  62. package/dist/src/modeller/property-sidebar/effects/property-event.js.map +1 -1
  63. package/dist/src/modeller/property-sidebar/inspector/inspector-controller.d.ts +62 -0
  64. package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js +276 -0
  65. package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js.map +1 -0
  66. package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.d.ts +15 -0
  67. package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js +56 -0
  68. package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js.map +1 -0
  69. package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.d.ts +7 -0
  70. package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js +25 -0
  71. package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js.map +1 -0
  72. package/dist/src/modeller/property-sidebar/inspector/inspector-model.d.ts +44 -0
  73. package/dist/src/modeller/property-sidebar/inspector/inspector-model.js +72 -0
  74. package/dist/src/modeller/property-sidebar/inspector/inspector-model.js.map +1 -0
  75. package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.d.ts +18 -0
  76. package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js +61 -0
  77. package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js.map +1 -0
  78. package/dist/src/modeller/property-sidebar/inspector/inspector.d.ts +17 -12
  79. package/dist/src/modeller/property-sidebar/inspector/inspector.js +281 -154
  80. package/dist/src/modeller/property-sidebar/inspector/inspector.js.map +1 -1
  81. package/dist/src/modeller/property-sidebar/property-sidebar.d.ts +2 -2
  82. package/dist/src/modeller/property-sidebar/property-sidebar.js +11 -24
  83. package/dist/src/modeller/property-sidebar/property-sidebar.js.map +1 -1
  84. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.d.ts +75 -0
  85. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js +346 -0
  86. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js.map +1 -0
  87. package/dist/src/modeller/property-sidebar/shapes/shapes.d.ts +23 -2
  88. package/dist/src/modeller/property-sidebar/shapes/shapes.js +96 -4
  89. package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
  90. package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +25 -1
  91. package/dist/src/modeller/property-sidebar/specifics/specifics.js +112 -2
  92. package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
  93. package/dist/src/modeller/property-sidebar/styles/styles.d.ts +6 -4
  94. package/dist/src/modeller/property-sidebar/styles/styles.js +140 -437
  95. package/dist/src/modeller/property-sidebar/styles/styles.js.map +1 -1
  96. package/dist/src/modeller/scene-viewer/ox-scene-layer.d.ts +4 -3
  97. package/dist/src/modeller/scene-viewer/ox-scene-layer.js +2 -1
  98. package/dist/src/modeller/scene-viewer/ox-scene-layer.js.map +1 -1
  99. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +6 -5
  100. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +7 -3
  101. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  102. package/dist/src/ox-board-component-info.d.ts +3 -3
  103. package/dist/src/ox-board-component-info.js +58 -19
  104. package/dist/src/ox-board-component-info.js.map +1 -1
  105. package/dist/src/ox-board-list.js +12 -12
  106. package/dist/src/ox-board-list.js.map +1 -1
  107. package/dist/src/ox-board-modeller.d.ts +9 -6
  108. package/dist/src/ox-board-modeller.js +32 -33
  109. package/dist/src/ox-board-modeller.js.map +1 -1
  110. package/dist/src/ox-board-viewer.d.ts +129 -17
  111. package/dist/src/ox-board-viewer.js +804 -115
  112. package/dist/src/ox-board-viewer.js.map +1 -1
  113. package/dist/src/ox-editor-board-selector.d.ts +4 -1
  114. package/dist/src/ox-editor-board-selector.js +41 -12
  115. package/dist/src/ox-editor-board-selector.js.map +1 -1
  116. package/dist/src/ox-playback-controls.d.ts +157 -0
  117. package/dist/src/ox-playback-controls.js +1089 -0
  118. package/dist/src/ox-playback-controls.js.map +1 -0
  119. package/dist/src/ox-property-editor-theme.js +2 -2
  120. package/dist/src/ox-property-editor-theme.js.map +1 -1
  121. package/dist/src/playback/carrier-tracking-store.d.ts +51 -0
  122. package/dist/src/playback/carrier-tracking-store.js +115 -0
  123. package/dist/src/playback/carrier-tracking-store.js.map +1 -0
  124. package/dist/src/playback/playback-engine.d.ts +106 -0
  125. package/dist/src/playback/playback-engine.js +395 -0
  126. package/dist/src/playback/playback-engine.js.map +1 -0
  127. package/dist/src/selector/board-creation-popup.d.ts +4 -5
  128. package/dist/src/selector/board-creation-popup.js +16 -19
  129. package/dist/src/selector/board-creation-popup.js.map +1 -1
  130. package/dist/src/selector/board-thumbnail-card.js +8 -8
  131. package/dist/src/selector/board-thumbnail-card.js.map +1 -1
  132. package/dist/src/selector/ox-board-creation-card.d.ts +1 -1
  133. package/dist/src/selector/ox-board-creation-card.js +7 -7
  134. package/dist/src/selector/ox-board-creation-card.js.map +1 -1
  135. package/dist/src/selector/ox-board-selector.js +21 -10
  136. package/dist/src/selector/ox-board-selector.js.map +1 -1
  137. package/dist/tsconfig.tsbuildinfo +1 -1
  138. package/icons/components/text.png +0 -0
  139. package/icons/fab/mode-2d.svg +25 -0
  140. package/icons/fab/mode-3d.svg +25 -0
  141. package/icons/fab/playback-play.svg +49 -0
  142. package/icons/fab/playback-stop.svg +50 -0
  143. package/icons/icon-htoolbar.png +0 -0
  144. package/package.json +22 -21
@@ -0,0 +1,101 @@
1
+ export type Pattern = 'grid' | 'line-h' | 'line-v';
2
+ export interface Area {
3
+ left: number;
4
+ top: number;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface BulkCreateOptions {
9
+ /** 복제할 원본 컴포넌트 state. id, left, top 제외하고 모두 상속. */
10
+ template: Record<string, any>;
11
+ /** 타겟 영역 (보드 좌표). */
12
+ area: Area;
13
+ /** 분포 패턴. */
14
+ pattern: Pattern;
15
+ /**
16
+ * 총 개수. grid 패턴에선 rows × cols 로 분해.
17
+ * rows/cols 가 명시되면 그것 사용, 미명시 시 area 비율과 count 로 자동 결정.
18
+ */
19
+ count: number;
20
+ /** grid pattern 용 — 행 수. 미지정 시 area 비율 + count 로 자동. */
21
+ rows?: number;
22
+ /** grid pattern 용 — 열 수. 미지정 시 area 비율 + count 로 자동. */
23
+ cols?: number;
24
+ /**
25
+ * ID 패턴. placeholder:
26
+ * `{i}` — 일련번호 (0-based + idStart)
27
+ * `{r}` — 행 index (grid 만, 0-based + idStart)
28
+ * `{c}` — 열 index (grid 만, 0-based + idStart)
29
+ * 예: "PORT_{i}", "P_{r}_{c}", "CELL{r}{c}"
30
+ */
31
+ idPattern: string;
32
+ /** ID 시작 번호. default 1. */
33
+ idStart?: number;
34
+ /** ID 숫자 자릿수 (zero-pad). 0 = 미적용. default 0. */
35
+ idPadding?: number;
36
+ /**
37
+ * 컴포넌트의 크기를 사용할지 (= template.width/height 그대로) 또는 cell 에
38
+ * 맞춰 자동 조정할지. default 'template' (원본 크기 유지).
39
+ */
40
+ sizeMode?: 'template' | 'fit-cell';
41
+ }
42
+ export interface GeneratedModel {
43
+ type?: string;
44
+ id: string;
45
+ left: number;
46
+ top: number;
47
+ width: number;
48
+ height: number;
49
+ [key: string]: any;
50
+ }
51
+ /**
52
+ * 타겟 영역과 총 count 로 grid 의 (rows, cols) 자동 결정.
53
+ * area 의 종횡비를 가능한 한 보존.
54
+ */
55
+ export declare function deriveGridShape(count: number, area: Area, rows?: number, cols?: number): {
56
+ rows: number;
57
+ cols: number;
58
+ };
59
+ /**
60
+ * 메인 헬퍼 — 옵션을 받아 새 컴포넌트 model 배열 반환.
61
+ *
62
+ * **주의**: 반환된 model 은 `scene.add(models)` 로 추가하면 된다. ID 충돌 사전
63
+ * 검사는 호출 측 책임 — `findIdConflicts` 와 결합 사용.
64
+ */
65
+ export declare function generateBulkComponents(opts: BulkCreateOptions): GeneratedModel[];
66
+ /**
67
+ * 생성될 model 의 id 중 기존 scene 의 id 와 충돌하는 것 검출.
68
+ * @returns 충돌하는 id 배열 (없으면 빈 배열).
69
+ */
70
+ export declare function findIdConflicts(models: GeneratedModel[], existingIds: Iterable<string>): string[];
71
+ /**
72
+ * 템플릿의 id 문자열로부터 다음 컴포넌트들의 ID 패턴을 자동 추론.
73
+ *
74
+ * 규칙:
75
+ * - 끝에 숫자 있음: `"PORT_05"` → `{ pattern: "PORT_{i}", start: 6, padding: 2 }`
76
+ * - 끝에 숫자 없음: `"abc"` → `{ pattern: "abc_{i}", start: 1, padding: 0 }`
77
+ * - 빈 id: `{ pattern: "{i}", start: 1, padding: 0 }`
78
+ *
79
+ * padding 은 *leading zero* 가 있을 때만 적용:
80
+ * - "05" → padding 2 (= "01", "02"... 형식 유지)
81
+ * - "5" → padding 0 (= "1", "2"... 형식)
82
+ */
83
+ export declare function inferIdPattern(id: string | undefined | null): {
84
+ pattern: string;
85
+ start: number;
86
+ padding: number;
87
+ };
88
+ /**
89
+ * 패턴이 매칭하는 기존 ID 들 중 *최대 번호 + 1* 반환 — 반복 bulk-create 시
90
+ * 시작 번호 자동 advance 용.
91
+ *
92
+ * 예:
93
+ * pattern = "PORT_{i}", existingIds = ["PORT_05", "PORT_12", "Other_1"]
94
+ * → 13 (= 12 + 1)
95
+ *
96
+ * 매칭 안 되면 fallbackStart 반환 (기본 inferIdPattern 의 start).
97
+ *
98
+ * NOTE: 현재 `{i}` placeholder 만 인식. `{r}` / `{c}` 는 grid 전용이므로 별도
99
+ * 처리 필요 (추후).
100
+ */
101
+ export declare function nextAvailableStart(idPattern: string, existingIds: Iterable<string>, fallbackStart: number): number;
@@ -0,0 +1,199 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Bulk-create — 단일 템플릿 컴포넌트로부터 N개의 컴포넌트 model 을 일괄 생성
5
+ * 하는 순수 헬퍼 모음.
6
+ *
7
+ * 설계 원칙:
8
+ * - 순수 함수 (UI / scene side-effect 없음)
9
+ * - 도메인 중립 (factory, board-ai, fmsim, operato-* 등 모든 consumer 가 동일하게 사용)
10
+ * - 의존성 0 (Three.js / Lit 불필요)
11
+ *
12
+ * 사용 패턴:
13
+ * 1. consumer 가 template (= 컴포넌트 model state) + area + 옵션 수집
14
+ * 2. generateBulkComponents() → 새 컴포넌트 model 배열 반환
15
+ * 3. scene.undoableChange(() => scene.add(models)) — 단일 undo 단위로 추가
16
+ *
17
+ * 결합 사용:
18
+ * - deriveGridShape: 격자 패턴의 (rows, cols) 자동 결정
19
+ * - inferIdPattern: 템플릿의 id 로부터 ID 패턴/시작/padding 자동 추출
20
+ * - nextAvailableStart: 반복 호출 시 시작번호 자동 advance (existingIds 스캔)
21
+ * - findIdConflicts: 생성될 ID 가 scene 의 기존 ID 와 충돌하는지 검출
22
+ */
23
+ /**
24
+ * 타겟 영역과 총 count 로 grid 의 (rows, cols) 자동 결정.
25
+ * area 의 종횡비를 가능한 한 보존.
26
+ */
27
+ export function deriveGridShape(count, area, rows, cols) {
28
+ if (rows && cols)
29
+ return { rows, cols };
30
+ if (rows)
31
+ return { rows, cols: Math.ceil(count / rows) };
32
+ if (cols)
33
+ return { rows: Math.ceil(count / cols), cols };
34
+ // 자동: area 의 종횡비와 비슷한 grid 도출.
35
+ // cols / rows ≈ width / height → cols ≈ sqrt(count × width / height)
36
+ const aspect = area.width / Math.max(area.height, 1);
37
+ const c = Math.max(1, Math.round(Math.sqrt(count * aspect)));
38
+ const r = Math.max(1, Math.ceil(count / c));
39
+ return { rows: r, cols: c };
40
+ }
41
+ /**
42
+ * ID 패턴 치환 — {i}, {r}, {c} placeholder.
43
+ */
44
+ function formatId(pattern, i, r, c, start, padding) {
45
+ const pad = (n) => {
46
+ const v = n + start;
47
+ return padding > 0 ? String(v).padStart(padding, '0') : String(v);
48
+ };
49
+ return pattern
50
+ .replace(/\{i\}/g, pad(i))
51
+ .replace(/\{r\}/g, pad(r))
52
+ .replace(/\{c\}/g, pad(c));
53
+ }
54
+ /**
55
+ * 메인 헬퍼 — 옵션을 받아 새 컴포넌트 model 배열 반환.
56
+ *
57
+ * **주의**: 반환된 model 은 `scene.add(models)` 로 추가하면 된다. ID 충돌 사전
58
+ * 검사는 호출 측 책임 — `findIdConflicts` 와 결합 사용.
59
+ */
60
+ export function generateBulkComponents(opts) {
61
+ const { template, area, pattern, count, idPattern, idStart = 1, idPadding = 0, sizeMode = 'template' } = opts;
62
+ if (count <= 0)
63
+ return [];
64
+ if (!template || typeof template !== 'object')
65
+ return [];
66
+ // template 에서 제외할 필드. id/위치 는 새로 생성. (width/height 는 sizeMode 에 따라 처리)
67
+ const { id: _id, left: _l, top: _t, width: _w, height: _h, ...rest } = template;
68
+ const tplW = typeof template.width === 'number' ? template.width : 50;
69
+ const tplH = typeof template.height === 'number' ? template.height : 50;
70
+ const result = [];
71
+ if (pattern === 'grid') {
72
+ const { rows, cols } = deriveGridShape(count, area, opts.rows, opts.cols);
73
+ const cellW = area.width / cols;
74
+ const cellH = area.height / rows;
75
+ const itemW = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplW;
76
+ const itemH = sizeMode === 'fit-cell' ? Math.min(cellW, cellH) * 0.8 : tplH;
77
+ let i = 0;
78
+ outer: for (let r = 0; r < rows; r++) {
79
+ for (let c = 0; c < cols; c++) {
80
+ if (i >= count)
81
+ break outer;
82
+ const cx = area.left + cellW * (c + 0.5);
83
+ const cy = area.top + cellH * (r + 0.5);
84
+ result.push({
85
+ ...rest,
86
+ id: formatId(idPattern, i, r, c, idStart, idPadding),
87
+ left: cx - itemW / 2,
88
+ top: cy - itemH / 2,
89
+ width: itemW,
90
+ height: itemH
91
+ });
92
+ i++;
93
+ }
94
+ }
95
+ }
96
+ else if (pattern === 'line-h' || pattern === 'line-v') {
97
+ const isH = pattern === 'line-h';
98
+ const span = isH ? area.width : area.height;
99
+ const cellSize = span / count;
100
+ const itemW = sizeMode === 'fit-cell' && isH ? cellSize * 0.8 : tplW;
101
+ const itemH = sizeMode === 'fit-cell' && !isH ? cellSize * 0.8 : tplH;
102
+ for (let i = 0; i < count; i++) {
103
+ const along = (i + 0.5) * cellSize;
104
+ const cx = isH ? area.left + along : area.left + area.width / 2;
105
+ const cy = isH ? area.top + area.height / 2 : area.top + along;
106
+ result.push({
107
+ ...rest,
108
+ id: formatId(idPattern, i, 0, i, idStart, idPadding),
109
+ left: cx - itemW / 2,
110
+ top: cy - itemH / 2,
111
+ width: itemW,
112
+ height: itemH
113
+ });
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ /**
119
+ * 생성될 model 의 id 중 기존 scene 의 id 와 충돌하는 것 검출.
120
+ * @returns 충돌하는 id 배열 (없으면 빈 배열).
121
+ */
122
+ export function findIdConflicts(models, existingIds) {
123
+ const set = existingIds instanceof Set ? existingIds : new Set(existingIds);
124
+ const conflicts = [];
125
+ for (const m of models) {
126
+ if (set.has(m.id))
127
+ conflicts.push(m.id);
128
+ }
129
+ return conflicts;
130
+ }
131
+ /**
132
+ * 템플릿의 id 문자열로부터 다음 컴포넌트들의 ID 패턴을 자동 추론.
133
+ *
134
+ * 규칙:
135
+ * - 끝에 숫자 있음: `"PORT_05"` → `{ pattern: "PORT_{i}", start: 6, padding: 2 }`
136
+ * - 끝에 숫자 없음: `"abc"` → `{ pattern: "abc_{i}", start: 1, padding: 0 }`
137
+ * - 빈 id: `{ pattern: "{i}", start: 1, padding: 0 }`
138
+ *
139
+ * padding 은 *leading zero* 가 있을 때만 적용:
140
+ * - "05" → padding 2 (= "01", "02"... 형식 유지)
141
+ * - "5" → padding 0 (= "1", "2"... 형식)
142
+ */
143
+ export function inferIdPattern(id) {
144
+ if (!id || typeof id !== 'string') {
145
+ return { pattern: '{i}', start: 1, padding: 0 };
146
+ }
147
+ const m = id.match(/^(.*?)(\d+)$/);
148
+ if (m) {
149
+ const prefix = m[1];
150
+ const numStr = m[2];
151
+ const num = parseInt(numStr, 10);
152
+ const padding = numStr.length > 1 && numStr.startsWith('0') ? numStr.length : 0;
153
+ return {
154
+ pattern: `${prefix}{i}`,
155
+ start: num + 1,
156
+ padding
157
+ };
158
+ }
159
+ return {
160
+ pattern: `${id}_{i}`,
161
+ start: 1,
162
+ padding: 0
163
+ };
164
+ }
165
+ /**
166
+ * 패턴이 매칭하는 기존 ID 들 중 *최대 번호 + 1* 반환 — 반복 bulk-create 시
167
+ * 시작 번호 자동 advance 용.
168
+ *
169
+ * 예:
170
+ * pattern = "PORT_{i}", existingIds = ["PORT_05", "PORT_12", "Other_1"]
171
+ * → 13 (= 12 + 1)
172
+ *
173
+ * 매칭 안 되면 fallbackStart 반환 (기본 inferIdPattern 의 start).
174
+ *
175
+ * NOTE: 현재 `{i}` placeholder 만 인식. `{r}` / `{c}` 는 grid 전용이므로 별도
176
+ * 처리 필요 (추후).
177
+ */
178
+ export function nextAvailableStart(idPattern, existingIds, fallbackStart) {
179
+ if (!idPattern.includes('{i}'))
180
+ return fallbackStart;
181
+ // pattern → regex. {i} → 캡쳐, {r}/{c} → 그냥 숫자, 특수문자 escape.
182
+ const escaped = idPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
183
+ const re = new RegExp('^' +
184
+ escaped
185
+ .replace(/\\\{i\\\}/g, '(\\d+)')
186
+ .replace(/\\\{[rc]\\\}/g, '\\d+') +
187
+ '$');
188
+ let max = -1;
189
+ for (const id of existingIds) {
190
+ const m = id.match(re);
191
+ if (m) {
192
+ const n = parseInt(m[1], 10);
193
+ if (!Number.isNaN(n) && n > max)
194
+ max = n;
195
+ }
196
+ }
197
+ return max >= 0 ? max + 1 : fallbackStart;
198
+ }
199
+ //# sourceMappingURL=bulk-create.js.map
@@ -0,0 +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"]}
@@ -5,7 +5,7 @@ import { LitElement, PropertyValues } from 'lit';
5
5
  import { Scene } from '@hatiolab/things-scene';
6
6
  import { Pallet, PalletItem } from '../../types';
7
7
  import { ComponentDetail } from './component-detail';
8
- declare const ComponentMenu_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types/src/types").ScopedElementsHost>;
8
+ declare const ComponentMenu_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types.js").ScopedElementsHost>;
9
9
  export declare class ComponentMenu extends ComponentMenu_base {
10
10
  static styles: import("lit").CSSResult[];
11
11
  groups: Pallet[];
@@ -2,16 +2,16 @@
2
2
  * @license Copyright © HatioLab Inc. All rights reserved.
3
3
  */
4
4
  import { LitElement, PropertyValues } from 'lit';
5
- import { Scene, SCENE_MODE } from '@hatiolab/things-scene';
5
+ import { Scene, SceneMode } from '@hatiolab/things-scene';
6
6
  import { ComponentGroup } from '../../types';
7
7
  import { ComponentMenu } from './component-menu.js';
8
- declare const ComponentToolbar_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types/src/types").ScopedElementsHost>;
8
+ declare const ComponentToolbar_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types.js").ScopedElementsHost>;
9
9
  export declare class ComponentToolbar extends ComponentToolbar_base {
10
10
  static styles: import("lit").CSSResult[];
11
11
  componentGroupList: ComponentGroup[];
12
12
  group?: string | null;
13
13
  scene: Scene;
14
- mode: SCENE_MODE;
14
+ mode: SceneMode;
15
15
  shift: HTMLImageElement;
16
16
  private menu;
17
17
  private componentsContainer;
@@ -23,7 +23,7 @@ export declare class ComponentToolbar extends ComponentToolbar_base {
23
23
  };
24
24
  render(): import("lit-html").TemplateResult<1>;
25
25
  onWheelEvent(e: Event): void;
26
- _setMode(mode: SCENE_MODE): void;
26
+ _setMode(mode: SceneMode): void;
27
27
  _onClickShift(e: MouseEvent): void;
28
28
  _onClickGroup(e: MouseEvent): Promise<void>;
29
29
  }
@@ -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 } 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: SCENE_MODE = 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: SCENE_MODE) {\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 { 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"]}
@@ -4,8 +4,7 @@
4
4
  import { css } from 'lit';
5
5
  export const style = css `
6
6
  :host {
7
- color: white;
8
- background-color: var(--edit-toolbar-background-color, #394e64);
7
+ background-color: var(--edit-toolbar-background-color, var(--md-sys-color-secondary, #394e64));
9
8
 
10
9
  overflow-x: hidden;
11
10
  }
@@ -13,7 +12,7 @@ export const style = css `
13
12
  [tools] {
14
13
  display: flex;
15
14
  align-items: center;
16
- overflow: none;
15
+ overflow: hidden;
17
16
  padding: 0px 10px;
18
17
  }
19
18
 
@@ -170,6 +169,10 @@ export const style = css `
170
169
  background-position-y: -1142px;
171
170
  }
172
171
 
172
+ #databind-copy {
173
+ background-position-y: -1692px;
174
+ }
175
+
173
176
  #context-menu {
174
177
  background-position-y: -692px;
175
178
  }
@@ -198,6 +201,43 @@ export const style = css `
198
201
  background-position-y: -1593px;
199
202
  }
200
203
 
204
+ #align-z-front {
205
+ background-position-y: -142px;
206
+ filter: hue-rotate(180deg);
207
+ }
208
+
209
+ #align-z-middle {
210
+ background-position-y: -192px;
211
+ filter: hue-rotate(180deg);
212
+ }
213
+
214
+ #align-z-back {
215
+ background-position-y: -242px;
216
+ filter: hue-rotate(180deg);
217
+ }
218
+
219
+ #distribute-z {
220
+ background-position-y: -1593px;
221
+ filter: hue-rotate(180deg);
222
+ }
223
+
224
+ .gizmo-btn {
225
+ background: none !important;
226
+ color: rgba(255, 255, 255, 0.5);
227
+ min-width: 24px;
228
+ display: inline-flex;
229
+ align-items: center;
230
+ justify-content: center;
231
+ cursor: pointer;
232
+ border-radius: 3px;
233
+ padding: 2px;
234
+ }
235
+
236
+ .gizmo-btn[disabled] {
237
+ opacity: 0.3;
238
+ pointer-events: none;
239
+ }
240
+
201
241
  #toggle-property {
202
242
  background-position-y: -1392px;
203
243
  }
@@ -1 +1 @@
1
- {"version":3,"file":"edit-toolbar-style.js","sourceRoot":"","sources":["../../../src/modeller/edit-toolbar-style.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkOvB,CAAA","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css } from 'lit'\n\nexport const style = css`\n :host {\n color: white;\n background-color: var(--edit-toolbar-background-color, #394e64);\n\n overflow-x: hidden;\n }\n\n [tools] {\n display: flex;\n align-items: center;\n overflow: none;\n padding: 0px 10px;\n }\n\n [tools] > * {\n padding: 0px;\n }\n\n [tools] > span[button] {\n min-width: 30px;\n }\n\n [tools] > span[padding] {\n flex: 1;\n }\n\n [tools] > .vline {\n display: block;\n flex: none;\n border-left: 1px solid rgba(255, 255, 255, 0.2);\n border-right: 1px solid rgba(0, 0, 0, 0.15);\n width: 0px;\n height: 18px;\n margin: 0 3px;\n }\n\n span[button] {\n min-height: 35px;\n\n background: var(--url-icon-htoolbar) no-repeat;\n background-position-x: 50%;\n opacity: 0.8;\n }\n span[button]:hover {\n opacity: 1;\n background-color: rgba(0, 0, 0, 0.1);\n cursor: pointer;\n }\n\n #fullscreen,\n #toggle-property {\n flex: none;\n }\n\n #align-left {\n background-position-y: 8px;\n }\n\n #align-center {\n background-position-y: -42px;\n }\n\n #align-right {\n background-position-y: -92px;\n }\n\n #align-top {\n background-position-y: -142px;\n }\n\n #align-middle {\n background-position-y: -192px;\n }\n\n #align-bottom {\n background-position-y: -242px;\n }\n\n #undo {\n background-position-y: -592px;\n }\n\n #redo {\n background-position-y: -642px;\n }\n\n #front {\n background-position-y: -292px;\n }\n\n #back {\n background-position-y: -342px;\n }\n\n #forward {\n background-position-y: -392px;\n }\n\n #backward {\n background-position-y: -442px;\n }\n\n #symmetry-x {\n background-position-y: -492px;\n }\n\n #symmetry-y {\n background-position-y: -542px;\n }\n\n #group {\n background-position-y: -492px;\n }\n\n #ungroup {\n background-position-y: -542px;\n }\n\n #fullscreen {\n background-position-y: -692px;\n }\n\n #toggle-property {\n background-position-y: -692px;\n float: right;\n }\n\n #zoomin {\n background-position-y: -742px;\n }\n\n #zoomout {\n background-position-y: -792px;\n }\n\n #fit-scene {\n background-position-y: -1492px;\n }\n\n #cut {\n background-position-y: -842px;\n }\n\n #copy {\n background-position-y: -892px;\n }\n\n #paste {\n background-position-y: -942px;\n }\n\n #delete {\n background-position-y: -992px;\n }\n\n #font-increase {\n background-position-y: -1042px;\n }\n\n #font-decrease {\n background-position-y: -1092px;\n }\n\n #style-copy {\n background-position-y: -1142px;\n }\n\n #context-menu {\n background-position-y: -692px;\n }\n\n #symmetry-x {\n background-position-y: -1192px;\n }\n\n #symmetry-y {\n background-position-y: -1242px;\n }\n\n #rotate-cw {\n background-position-y: -1292px;\n }\n\n #rotate-ccw {\n background-position-y: -1342px;\n }\n\n #distribute-horizontal {\n background-position-y: -1542px;\n }\n\n #distribute-vertical {\n background-position-y: -1593px;\n }\n\n #toggle-property {\n background-position-y: -1392px;\n }\n\n #preview {\n background-position-y: -1640px;\n }\n\n /* bigger buttons */\n #fullscreen {\n background: var(--url-icon-fullscreen) 50% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property {\n background: var(--url-icon-collapse) 80% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property[active] {\n background: var(--url-icon-collapse-active) 80% 10px no-repeat;\n }\n\n #generate-model {\n background-position-y: -1492px;\n }\n`\n"]}
1
+ {"version":3,"file":"edit-toolbar-style.js","sourceRoot":"","sources":["../../../src/modeller/edit-toolbar-style.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0QvB,CAAA","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css } from 'lit'\n\nexport const style = css`\n :host {\n background-color: var(--edit-toolbar-background-color, var(--md-sys-color-secondary, #394e64));\n\n overflow-x: hidden;\n }\n\n [tools] {\n display: flex;\n align-items: center;\n overflow: hidden;\n padding: 0px 10px;\n }\n\n [tools] > * {\n padding: 0px;\n }\n\n [tools] > span[button] {\n min-width: 30px;\n }\n\n [tools] > span[padding] {\n flex: 1;\n }\n\n [tools] > .vline {\n display: block;\n flex: none;\n border-left: 1px solid rgba(255, 255, 255, 0.2);\n border-right: 1px solid rgba(0, 0, 0, 0.15);\n width: 0px;\n height: 18px;\n margin: 0 3px;\n }\n\n span[button] {\n min-height: 35px;\n\n background: var(--url-icon-htoolbar) no-repeat;\n background-position-x: 50%;\n opacity: 0.8;\n }\n span[button]:hover {\n opacity: 1;\n background-color: rgba(0, 0, 0, 0.1);\n cursor: pointer;\n }\n\n #fullscreen,\n #toggle-property {\n flex: none;\n }\n\n #align-left {\n background-position-y: 8px;\n }\n\n #align-center {\n background-position-y: -42px;\n }\n\n #align-right {\n background-position-y: -92px;\n }\n\n #align-top {\n background-position-y: -142px;\n }\n\n #align-middle {\n background-position-y: -192px;\n }\n\n #align-bottom {\n background-position-y: -242px;\n }\n\n #undo {\n background-position-y: -592px;\n }\n\n #redo {\n background-position-y: -642px;\n }\n\n #front {\n background-position-y: -292px;\n }\n\n #back {\n background-position-y: -342px;\n }\n\n #forward {\n background-position-y: -392px;\n }\n\n #backward {\n background-position-y: -442px;\n }\n\n #symmetry-x {\n background-position-y: -492px;\n }\n\n #symmetry-y {\n background-position-y: -542px;\n }\n\n #group {\n background-position-y: -492px;\n }\n\n #ungroup {\n background-position-y: -542px;\n }\n\n #fullscreen {\n background-position-y: -692px;\n }\n\n #toggle-property {\n background-position-y: -692px;\n float: right;\n }\n\n #zoomin {\n background-position-y: -742px;\n }\n\n #zoomout {\n background-position-y: -792px;\n }\n\n #fit-scene {\n background-position-y: -1492px;\n }\n\n #cut {\n background-position-y: -842px;\n }\n\n #copy {\n background-position-y: -892px;\n }\n\n #paste {\n background-position-y: -942px;\n }\n\n #delete {\n background-position-y: -992px;\n }\n\n #font-increase {\n background-position-y: -1042px;\n }\n\n #font-decrease {\n background-position-y: -1092px;\n }\n\n #style-copy {\n background-position-y: -1142px;\n }\n\n #databind-copy {\n background-position-y: -1692px;\n }\n\n #context-menu {\n background-position-y: -692px;\n }\n\n #symmetry-x {\n background-position-y: -1192px;\n }\n\n #symmetry-y {\n background-position-y: -1242px;\n }\n\n #rotate-cw {\n background-position-y: -1292px;\n }\n\n #rotate-ccw {\n background-position-y: -1342px;\n }\n\n #distribute-horizontal {\n background-position-y: -1542px;\n }\n\n #distribute-vertical {\n background-position-y: -1593px;\n }\n\n #align-z-front {\n background-position-y: -142px;\n filter: hue-rotate(180deg);\n }\n\n #align-z-middle {\n background-position-y: -192px;\n filter: hue-rotate(180deg);\n }\n\n #align-z-back {\n background-position-y: -242px;\n filter: hue-rotate(180deg);\n }\n\n #distribute-z {\n background-position-y: -1593px;\n filter: hue-rotate(180deg);\n }\n\n .gizmo-btn {\n background: none !important;\n color: rgba(255, 255, 255, 0.5);\n min-width: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n border-radius: 3px;\n padding: 2px;\n }\n\n .gizmo-btn[disabled] {\n opacity: 0.3;\n pointer-events: none;\n }\n\n #toggle-property {\n background-position-y: -1392px;\n }\n\n #preview {\n background-position-y: -1640px;\n }\n\n /* bigger buttons */\n #fullscreen {\n background: var(--url-icon-fullscreen) 50% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property {\n background: var(--url-icon-collapse) 80% 10px no-repeat;\n width: var(--edit-toolbar-bigger-icon-size);\n height: var(--edit-toolbar-bigger-icon-size);\n border-left: var(--edit-toolbar-bigger-icon-line);\n }\n\n #toggle-property[active] {\n background: var(--url-icon-collapse-active) 80% 10px no-repeat;\n }\n\n #generate-model {\n background-position-y: -1492px;\n }\n`\n"]}
@@ -3,26 +3,15 @@
3
3
  */
4
4
  import { LitElement, PropertyValues } from 'lit';
5
5
  import { Component, Scene } from '@hatiolab/things-scene';
6
+ import './bulk-create-dialog.js';
6
7
  export declare class EditToolbar extends LitElement {
7
8
  static styles: import("lit").CSSResult[];
8
9
  scene?: Scene;
9
10
  selected: any[];
10
11
  hideProperty: boolean;
12
+ private _dimension;
13
+ private _gizmoAttached;
11
14
  private cliped?;
12
- private redo;
13
- private undo;
14
- private fullscreen;
15
- private cut;
16
- private copy;
17
- private paste;
18
- private delete;
19
- private forward;
20
- private backward;
21
- private front;
22
- private back;
23
- private aligners;
24
- private zorders;
25
- private distributes;
26
15
  firstUpdated(): void;
27
16
  updated(changes: PropertyValues<this>): void;
28
17
  render(): import("lit-html").TemplateResult<1>;
@@ -31,17 +20,35 @@ export declare class EditToolbar extends LitElement {
31
20
  getSymbol(key: string): string;
32
21
  private getShortcutString;
33
22
  onShortcut(e: KeyboardEvent): boolean;
23
+ private _setDisabled;
34
24
  onExecute(command: string, undoable: boolean, redoable: boolean): void;
35
25
  onUndo(undoable: boolean, redoable: boolean): void;
36
26
  onRedo(undoable: boolean, redoable: boolean): void;
37
27
  onSceneChanged(after?: Scene, before?: Scene): void;
28
+ private _onDimensionChanged;
29
+ private _onGizmoAttachChanged;
30
+ private _setGizmoMode;
38
31
  onSelectedChanged(after: Component[], before: Component[]): void;
39
32
  onTapUndo(): void;
40
33
  onTapRedo(): void;
34
+ onStartStylePasteMode(): void;
35
+ onStartDatabindPasteMode(): void;
41
36
  onTapCut(): void;
42
37
  onTapCopy(): Promise<void>;
43
38
  onTapPaste(): void;
44
39
  onTapDelete(): void;
40
+ /**
41
+ * 멀티 컴포넌트 일괄 붙여넣기 — Ctrl+Shift+V 진입점.
42
+ *
43
+ * 흐름:
44
+ * 1. 클립보드 (this.cliped) 검증 — *단일* 컴포넌트만 허용
45
+ * 2. scene.startBulkCreateMode → AddLayer 가 캔버스 위에 드래그 박스 그림
46
+ * 3. 사용자가 영역 드래그 → Promise resolve({area, template})
47
+ * ESC/취소 → Promise resolve(null)
48
+ * 4. 영역이 결정되면 dialog 오픈
49
+ */
50
+ onBulkPaste(): Promise<void>;
51
+ private _openBulkCreateDialog;
45
52
  onTapSelectAll(): void;
46
53
  onTapFontIncrease(e: TouchEvent): void;
47
54
  onTapFontDecrease(e: TouchEvent): void;