@operato/board 10.0.0-beta.5 → 10.0.0-beta.51

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 (89) hide show
  1. package/CHANGELOG.md +422 -0
  2. package/dist/src/component/container.js +18 -3
  3. package/dist/src/component/container.js.map +1 -1
  4. package/dist/src/component/conveyance.d.ts +2 -0
  5. package/dist/src/component/conveyance.js +38 -0
  6. package/dist/src/component/conveyance.js.map +1 -0
  7. package/dist/src/component/etc.js +2 -10
  8. package/dist/src/component/etc.js.map +1 -1
  9. package/dist/src/component/facility.d.ts +2 -0
  10. package/dist/src/component/facility.js +35 -0
  11. package/dist/src/component/facility.js.map +1 -0
  12. package/dist/src/component/index.d.ts +5 -0
  13. package/dist/src/component/index.js +5 -0
  14. package/dist/src/component/index.js.map +1 -1
  15. package/dist/src/component/line.js +4 -28
  16. package/dist/src/component/line.js.map +1 -1
  17. package/dist/src/component/manufacturing.d.ts +2 -0
  18. package/dist/src/component/manufacturing.js +41 -0
  19. package/dist/src/component/manufacturing.js.map +1 -0
  20. package/dist/src/component/register-default-groups.js +19 -2
  21. package/dist/src/component/register-default-groups.js.map +1 -1
  22. package/dist/src/component/shape.js +5 -29
  23. package/dist/src/component/shape.js.map +1 -1
  24. package/dist/src/component/storage.d.ts +2 -0
  25. package/dist/src/component/storage.js +27 -0
  26. package/dist/src/component/storage.js.map +1 -0
  27. package/dist/src/component/text-and-media.js +2 -25
  28. package/dist/src/component/text-and-media.js.map +1 -1
  29. package/dist/src/component/transport.d.ts +2 -0
  30. package/dist/src/component/transport.js +36 -0
  31. package/dist/src/component/transport.js.map +1 -0
  32. package/dist/src/component/warehouse.d.ts +1 -0
  33. package/dist/src/component/warehouse.js +8 -1
  34. package/dist/src/component/warehouse.js.map +1 -1
  35. package/dist/src/data-storage/board-model-cache.d.ts +30 -0
  36. package/dist/src/data-storage/board-model-cache.js +93 -0
  37. package/dist/src/data-storage/board-model-cache.js.map +1 -0
  38. package/dist/src/graphql/playback-buffer.d.ts +79 -0
  39. package/dist/src/graphql/playback-buffer.js +139 -0
  40. package/dist/src/graphql/playback-buffer.js.map +1 -0
  41. package/dist/src/graphql/playback-buffer.test.d.ts +1 -0
  42. package/dist/src/graphql/playback-buffer.test.js +261 -0
  43. package/dist/src/graphql/playback-buffer.test.js.map +1 -0
  44. package/dist/src/graphql/playback-subscription.d.ts +89 -0
  45. package/dist/src/graphql/playback-subscription.js +258 -0
  46. package/dist/src/graphql/playback-subscription.js.map +1 -0
  47. package/dist/src/index.d.ts +3 -0
  48. package/dist/src/index.js +2 -0
  49. package/dist/src/index.js.map +1 -1
  50. package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
  51. package/dist/src/modeller/bulk-create-dialog.js +531 -0
  52. package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
  53. package/dist/src/modeller/bulk-create.d.ts +101 -0
  54. package/dist/src/modeller/bulk-create.js +199 -0
  55. package/dist/src/modeller/bulk-create.js.map +1 -0
  56. package/dist/src/modeller/component-toolbar/component-menu.js +8 -1
  57. package/dist/src/modeller/component-toolbar/component-menu.js.map +1 -1
  58. package/dist/src/modeller/edit-toolbar-style.js +38 -1
  59. package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
  60. package/dist/src/modeller/edit-toolbar.d.ts +21 -16
  61. package/dist/src/modeller/edit-toolbar.js +362 -201
  62. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  63. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
  64. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +7 -11
  65. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  66. package/dist/src/ox-board-modeller.d.ts +8 -1
  67. package/dist/src/ox-board-modeller.js +125 -6
  68. package/dist/src/ox-board-modeller.js.map +1 -1
  69. package/dist/src/ox-board-preview.d.ts +36 -0
  70. package/dist/src/ox-board-preview.js +114 -0
  71. package/dist/src/ox-board-preview.js.map +1 -0
  72. package/dist/src/ox-board-template-list.d.ts +1 -0
  73. package/dist/src/ox-board-template-list.js +19 -1
  74. package/dist/src/ox-board-template-list.js.map +1 -1
  75. package/dist/src/ox-board-viewer.d.ts +50 -1
  76. package/dist/src/ox-board-viewer.js +271 -28
  77. package/dist/src/ox-board-viewer.js.map +1 -1
  78. package/dist/src/ox-playback-controls.d.ts +56 -0
  79. package/dist/src/ox-playback-controls.js +515 -0
  80. package/dist/src/ox-playback-controls.js.map +1 -0
  81. package/dist/src/selector/ox-board-selector.js +11 -1
  82. package/dist/src/selector/ox-board-selector.js.map +1 -1
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/package.json +17 -12
  85. package/translations/en.json +19 -1
  86. package/translations/ja.json +19 -1
  87. package/translations/ko.json +19 -1
  88. package/translations/ms.json +19 -1
  89. package/translations/zh.json +19 -1
@@ -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"]}
@@ -25,7 +25,14 @@ export class ComponentMenu extends ScopedElementsMixin(LitElement) {
25
25
  render() {
26
26
  return this.group
27
27
  ? html `
28
- <h2 onclick=${(e) => e.stopPropagation()}>${this.group} list</h2>
28
+ <h2 onclick=${(e) => e.stopPropagation()}>
29
+ ${ /* group label — translate via group.<name> key, fall back to the
30
+ raw group name so untranslated groups keep working.
31
+ "list" is also translatable; defaults to the literal "list"
32
+ which is what users have been seeing all along. */''}
33
+ ${i18next.t(`group.${this.group}`, { defaultValue: this.group })}
34
+ ${i18next.t('label.list', { defaultValue: 'list' })}
35
+ </h2>
29
36
 
30
37
  <div templates @mouseover=${(e) => this.onHoverComponent(e)}>
31
38
  ${(this.templates || []).map(template => {
@@ -1 +1 @@
1
- {"version":3,"file":"component-menu.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-menu.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, state } from 'lit/decorators.js'\n\nimport { Scene } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { i18next } from '@operato/i18n'\n\nimport { Pallet, PalletItem } from '../../types.js'\nimport { ComponentDetail } from './component-detail.js'\n\nconst noImage = new URL('../../../../icons/components/no-image.png', import.meta.url).href\n\nexport class ComponentMenu extends ScopedElementsMixin(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n align-content: stretch;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n margin: 0px;\n padding: 0px;\n\n width: 180px;\n height: 100%;\n\n overflow: visible;\n\n border: 2px solid var(--component-menu-border-color, var(--md-sys-color-secondary));\n box-sizing: border-box;\n\n position: absolute;\n top: 0px;\n\n z-index: 1;\n }\n\n h2 {\n background-color: var(--component-menu-border-color, var(--md-sys-color-secondary));\n padding: 1px 5px;\n margin: 0;\n font: var(--component-menu-title);\n color: var(--md-sys-color-on-secondary);\n text-transform: capitalize;\n }\n\n [templates] {\n flex: 1;\n\n display: block;\n margin: 0;\n padding: 0;\n overflow-y: auto;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n }\n\n [template] {\n display: flex;\n align-items: center;\n min-height: var(--component-menu-item-icon-size);\n padding: 0 5px 0 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n font-size: 11px;\n color: var(--component-menu-item-color, var(--md-sys-color-on-secondary-container));\n text-transform: capitalize;\n }\n\n [template]:hover,\n [template]:focus {\n color: var(--component-menu-item-hover-color, var(--md-sys-color-secondary));\n font-weight: bold;\n cursor: pointer;\n }\n\n [template] img {\n margin: 5px;\n width: var(--component-menu-item-icon-size);\n height: var(--component-menu-item-icon-size);\n }\n\n component-detail {\n position: absolute;\n top: 0;\n left: 180px;\n height: 100%;\n outline: none;\n }\n\n component-detail[hidden] {\n display: none;\n }\n `\n ]\n\n @property({ type: Object }) groups: Pallet[] = []\n @property({ type: Object }) scene: Scene | null = null\n @property({ type: String }) group: string | null = ''\n\n @state() templates: PalletItem[] = []\n @state() template: PalletItem | any\n\n @query('component-detail') detail!: HTMLElement\n\n static get scopedElements() {\n return {\n 'component-detail': ComponentDetail\n }\n }\n\n render() {\n return this.group\n ? html`\n <h2 onclick=${(e: MouseEvent) => e.stopPropagation()}>${this.group} list</h2>\n\n <div templates @mouseover=${(e: MouseEvent) => this.onHoverComponent(e)}>\n ${(this.templates || []).map(template => {\n const { type } = template\n\n return html`\n <div @click=${this.onClickTemplate} data-type=${type} template>\n <img src=${String(this.templateIcon(template))} />${i18next.t(`component.${type}`)}\n </div>\n `\n })}\n </div>\n\n <component-detail\n tabindex=\"-1\"\n @focusout=${() => {\n this.template = null\n }}\n .template=${this.template}\n hidden\n >\n </component-detail>\n `\n : html``\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('group')) {\n if (!this.group) {\n this.templates = []\n this.setAttribute('hidden', '')\n } else {\n this.templates = this.groups.find((g: Pallet) => g.name === this.group)?.templates || []\n this.removeAttribute('active')\n }\n }\n\n if (changes.has('template')) {\n this.template && this.template.about\n ? this.detail.removeAttribute('hidden')\n : this.detail.setAttribute('hidden', '')\n }\n }\n\n findTemplate(type: string | null | undefined) {\n this.template = type && this.templates.find(template => template.type == type)\n }\n\n onHoverComponent(e: MouseEvent) {\n const button = e.target as HTMLElement\n this.findTemplate(button!.closest('[data-type]')?.getAttribute('data-type'))\n }\n\n onClickTemplate(e: MouseEvent) {\n var item = e.target as HTMLElement\n var type = item.closest('[data-type]')?.getAttribute('data-type')\n\n if (!type) {\n return\n }\n\n if (this.scene) {\n this.scene.rootContainer?.trigger('addstop') // 새로운 컴포넌트 클릭시 이전 추가 모드 정지\n this.template = this.templates.find(template => template.type == type)\n this.template && this.scene.startAddMode(JSON.parse(JSON.stringify(this.template.model)))\n }\n\n this.group = null\n }\n\n templateIcon(template: PalletItem) {\n return template.icon || noImage\n }\n}\n"]}
1
+ {"version":3,"file":"component-menu.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-menu.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, state } from 'lit/decorators.js'\n\nimport { Scene } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { i18next } from '@operato/i18n'\n\nimport { Pallet, PalletItem } from '../../types.js'\nimport { ComponentDetail } from './component-detail.js'\n\nconst noImage = new URL('../../../../icons/components/no-image.png', import.meta.url).href\n\nexport class ComponentMenu extends ScopedElementsMixin(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n align-content: stretch;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n margin: 0px;\n padding: 0px;\n\n width: 180px;\n height: 100%;\n\n overflow: visible;\n\n border: 2px solid var(--component-menu-border-color, var(--md-sys-color-secondary));\n box-sizing: border-box;\n\n position: absolute;\n top: 0px;\n\n z-index: 1;\n }\n\n h2 {\n background-color: var(--component-menu-border-color, var(--md-sys-color-secondary));\n padding: 1px 5px;\n margin: 0;\n font: var(--component-menu-title);\n color: var(--md-sys-color-on-secondary);\n text-transform: capitalize;\n }\n\n [templates] {\n flex: 1;\n\n display: block;\n margin: 0;\n padding: 0;\n overflow-y: auto;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n }\n\n [template] {\n display: flex;\n align-items: center;\n min-height: var(--component-menu-item-icon-size);\n padding: 0 5px 0 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n font-size: 11px;\n color: var(--component-menu-item-color, var(--md-sys-color-on-secondary-container));\n text-transform: capitalize;\n }\n\n [template]:hover,\n [template]:focus {\n color: var(--component-menu-item-hover-color, var(--md-sys-color-secondary));\n font-weight: bold;\n cursor: pointer;\n }\n\n [template] img {\n margin: 5px;\n width: var(--component-menu-item-icon-size);\n height: var(--component-menu-item-icon-size);\n }\n\n component-detail {\n position: absolute;\n top: 0;\n left: 180px;\n height: 100%;\n outline: none;\n }\n\n component-detail[hidden] {\n display: none;\n }\n `\n ]\n\n @property({ type: Object }) groups: Pallet[] = []\n @property({ type: Object }) scene: Scene | null = null\n @property({ type: String }) group: string | null = ''\n\n @state() templates: PalletItem[] = []\n @state() template: PalletItem | any\n\n @query('component-detail') detail!: HTMLElement\n\n static get scopedElements() {\n return {\n 'component-detail': ComponentDetail\n }\n }\n\n render() {\n return this.group\n ? html`\n <h2 onclick=${(e: MouseEvent) => e.stopPropagation()}>\n ${/* group label — translate via group.<name> key, fall back to the\n raw group name so untranslated groups keep working.\n \"list\" is also translatable; defaults to the literal \"list\"\n which is what users have been seeing all along. */ ''}\n ${i18next.t(`group.${this.group}`, { defaultValue: this.group })}\n ${i18next.t('label.list', { defaultValue: 'list' })}\n </h2>\n\n <div templates @mouseover=${(e: MouseEvent) => this.onHoverComponent(e)}>\n ${(this.templates || []).map(template => {\n const { type } = template\n\n return html`\n <div @click=${this.onClickTemplate} data-type=${type} template>\n <img src=${String(this.templateIcon(template))} />${i18next.t(`component.${type}`)}\n </div>\n `\n })}\n </div>\n\n <component-detail\n tabindex=\"-1\"\n @focusout=${() => {\n this.template = null\n }}\n .template=${this.template}\n hidden\n >\n </component-detail>\n `\n : html``\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('group')) {\n if (!this.group) {\n this.templates = []\n this.setAttribute('hidden', '')\n } else {\n this.templates = this.groups.find((g: Pallet) => g.name === this.group)?.templates || []\n this.removeAttribute('active')\n }\n }\n\n if (changes.has('template')) {\n this.template && this.template.about\n ? this.detail.removeAttribute('hidden')\n : this.detail.setAttribute('hidden', '')\n }\n }\n\n findTemplate(type: string | null | undefined) {\n this.template = type && this.templates.find(template => template.type == type)\n }\n\n onHoverComponent(e: MouseEvent) {\n const button = e.target as HTMLElement\n this.findTemplate(button!.closest('[data-type]')?.getAttribute('data-type'))\n }\n\n onClickTemplate(e: MouseEvent) {\n var item = e.target as HTMLElement\n var type = item.closest('[data-type]')?.getAttribute('data-type')\n\n if (!type) {\n return\n }\n\n if (this.scene) {\n this.scene.rootContainer?.trigger('addstop') // 새로운 컴포넌트 클릭시 이전 추가 모드 정지\n this.template = this.templates.find(template => template.type == type)\n this.template && this.scene.startAddMode(JSON.parse(JSON.stringify(this.template.model)))\n }\n\n this.group = null\n }\n\n templateIcon(template: PalletItem) {\n return template.icon || noImage\n }\n}\n"]}
@@ -12,7 +12,7 @@ export const style = css `
12
12
  [tools] {
13
13
  display: flex;
14
14
  align-items: center;
15
- overflow: none;
15
+ overflow: hidden;
16
16
  padding: 0px 10px;
17
17
  }
18
18
 
@@ -201,6 +201,43 @@ export const style = css `
201
201
  background-position-y: -1593px;
202
202
  }
203
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
+
204
241
  #toggle-property {
205
242
  background-position-y: -1392px;
206
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiOvB,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: 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 #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 #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"]}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsQvB,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"]}
@@ -3,28 +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 styleCopy;
16
- private databindCopy;
17
- private cut;
18
- private copy;
19
- private paste;
20
- private delete;
21
- private forward;
22
- private backward;
23
- private front;
24
- private back;
25
- private aligners;
26
- private zorders;
27
- private distributes;
28
15
  firstUpdated(): void;
29
16
  updated(changes: PropertyValues<this>): void;
30
17
  render(): import("lit-html").TemplateResult<1>;
@@ -32,10 +19,14 @@ export declare class EditToolbar extends LitElement {
32
19
  getSymbol(key: string): string;
33
20
  private getShortcutString;
34
21
  onShortcut(e: KeyboardEvent): boolean;
22
+ private _setDisabled;
35
23
  onExecute(command: string, undoable: boolean, redoable: boolean): void;
36
24
  onUndo(undoable: boolean, redoable: boolean): void;
37
25
  onRedo(undoable: boolean, redoable: boolean): void;
38
26
  onSceneChanged(after?: Scene, before?: Scene): void;
27
+ private _onDimensionChanged;
28
+ private _onGizmoAttachChanged;
29
+ private _setGizmoMode;
39
30
  onSelectedChanged(after: Component[], before: Component[]): void;
40
31
  onTapUndo(): void;
41
32
  onTapRedo(): void;
@@ -45,6 +36,18 @@ export declare class EditToolbar extends LitElement {
45
36
  onTapCopy(): Promise<void>;
46
37
  onTapPaste(): void;
47
38
  onTapDelete(): void;
39
+ /**
40
+ * 멀티 컴포넌트 일괄 붙여넣기 — Ctrl+Shift+V 진입점.
41
+ *
42
+ * 흐름:
43
+ * 1. 클립보드 (this.cliped) 검증 — *단일* 컴포넌트만 허용
44
+ * 2. scene.startBulkCreateMode → AddLayer 가 캔버스 위에 드래그 박스 그림
45
+ * 3. 사용자가 영역 드래그 → Promise resolve({area, template})
46
+ * ESC/취소 → Promise resolve(null)
47
+ * 4. 영역이 결정되면 dialog 오픈
48
+ */
49
+ onBulkPaste(): Promise<void>;
50
+ private _openBulkCreateDialog;
48
51
  onTapSelectAll(): void;
49
52
  onTapFontIncrease(e: TouchEvent): void;
50
53
  onTapFontDecrease(e: TouchEvent): void;
@@ -56,6 +59,8 @@ export declare class EditToolbar extends LitElement {
56
59
  onTapToggle(): void;
57
60
  onTapFitScene(): void;
58
61
  onTapPreview(): void;
62
+ onTapDataBinding(): void;
59
63
  onTapDownloadModel(): void;
64
+ onTapEditModel(): void;
60
65
  onTapDistribute(e: TouchEvent | string): void;
61
66
  }