@operato/board 10.0.0-beta.9 → 10.0.0

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 +530 -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/data-subscription.js +5 -1
  39. package/dist/src/graphql/data-subscription.js.map +1 -1
  40. package/dist/src/graphql/playback-buffer.d.ts +79 -0
  41. package/dist/src/graphql/playback-buffer.js +139 -0
  42. package/dist/src/graphql/playback-buffer.js.map +1 -0
  43. package/dist/src/graphql/playback-buffer.test.d.ts +1 -0
  44. package/dist/src/graphql/playback-buffer.test.js +261 -0
  45. package/dist/src/graphql/playback-buffer.test.js.map +1 -0
  46. package/dist/src/graphql/playback-subscription.d.ts +33 -18
  47. package/dist/src/graphql/playback-subscription.js +158 -91
  48. package/dist/src/graphql/playback-subscription.js.map +1 -1
  49. package/dist/src/index.d.ts +3 -0
  50. package/dist/src/index.js +2 -0
  51. package/dist/src/index.js.map +1 -1
  52. package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
  53. package/dist/src/modeller/bulk-create-dialog.js +531 -0
  54. package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
  55. package/dist/src/modeller/bulk-create.d.ts +101 -0
  56. package/dist/src/modeller/bulk-create.js +199 -0
  57. package/dist/src/modeller/bulk-create.js.map +1 -0
  58. package/dist/src/modeller/component-toolbar/component-menu.js +8 -1
  59. package/dist/src/modeller/component-toolbar/component-menu.js.map +1 -1
  60. package/dist/src/modeller/edit-toolbar.d.ts +16 -16
  61. package/dist/src/modeller/edit-toolbar.js +248 -208
  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 +6 -1
  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 +8 -1
  76. package/dist/src/ox-board-viewer.js +99 -32
  77. package/dist/src/ox-board-viewer.js.map +1 -1
  78. package/dist/src/ox-playback-controls.d.ts +8 -0
  79. package/dist/src/ox-playback-controls.js +114 -18
  80. package/dist/src/ox-playback-controls.js.map +1 -1
  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 +21 -16
  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
@@ -3,10 +3,56 @@
3
3
  */
4
4
  import { __decorate } from "tslib";
5
5
  import { html, LitElement } from 'lit';
6
- import { property, state, query, queryAll } from 'lit/decorators.js';
6
+ import { property, state } from 'lit/decorators.js';
7
7
  import { copyToClipboard, isMacOS } from '@operato/utils';
8
+ import { OxPopup } from '@operato/popup';
8
9
  import { style } from './edit-toolbar-style.js';
10
+ import './bulk-create-dialog.js';
9
11
  const MACOS = isMacOS();
12
+ /**
13
+ * 컴포넌트 model JSON 만으로 type-aware bounding box (width, height) 추출.
14
+ *
15
+ * things-scene 의 component.bounds 는 *path 의 bounding box* 인데, model JSON 만
16
+ * 으로는 path 를 그릴 수 없으므로 type-specific 필드를 직접 본다.
17
+ * - width/height 명시 (Rect, Container 등) → 그대로
18
+ * - rx/ry (Ellipse 등) → 2*rx, 2*ry
19
+ * - points 배열 (Line, Polygon 등) → 점들의 bounding box
20
+ * - 그 외 → 50x50 fallback
21
+ *
22
+ * bulk-paste 가 영역 안에 N 개 복제할 때 *원본 크기를 보존* 하기 위해 사용.
23
+ */
24
+ function deriveBoundsFromModel(model) {
25
+ if (typeof model.width === 'number' && typeof model.height === 'number') {
26
+ return { width: model.width, height: model.height };
27
+ }
28
+ if (typeof model.rx === 'number' && typeof model.ry === 'number') {
29
+ return { width: 2 * Math.abs(model.rx), height: 2 * Math.abs(model.ry) };
30
+ }
31
+ if (Array.isArray(model.points) && model.points.length > 0) {
32
+ let minX = Infinity;
33
+ let maxX = -Infinity;
34
+ let minY = Infinity;
35
+ let maxY = -Infinity;
36
+ for (const p of model.points) {
37
+ if (typeof (p === null || p === void 0 ? void 0 : p.x) === 'number') {
38
+ if (p.x < minX)
39
+ minX = p.x;
40
+ if (p.x > maxX)
41
+ maxX = p.x;
42
+ }
43
+ if (typeof (p === null || p === void 0 ? void 0 : p.y) === 'number') {
44
+ if (p.y < minY)
45
+ minY = p.y;
46
+ if (p.y > maxY)
47
+ maxY = p.y;
48
+ }
49
+ }
50
+ if (isFinite(minX) && isFinite(maxX) && isFinite(minY) && isFinite(maxY)) {
51
+ return { width: maxX - minX, height: maxY - minY };
52
+ }
53
+ }
54
+ return { width: 50, height: 50 };
55
+ }
10
56
  export class EditToolbar extends LitElement {
11
57
  constructor() {
12
58
  super(...arguments);
@@ -16,7 +62,6 @@ export class EditToolbar extends LitElement {
16
62
  this._gizmoAttached = false;
17
63
  }
18
64
  firstUpdated() {
19
- var _a, _b;
20
65
  this.addEventListener('mousewheel', this.onWheelEvent.bind(this), false);
21
66
  // 툴바 버튼 클릭 후 씬으로 포커스를 돌려서 키보드 단축키가 계속 작동하도록 한다
22
67
  this.addEventListener('click', () => {
@@ -33,33 +78,7 @@ export class EditToolbar extends LitElement {
33
78
  console.error('model paste failed', e);
34
79
  }
35
80
  });
36
- this.aligners.forEach(aligner => aligner.addEventListener('click', this.onTapAlign.bind(this)));
37
- this.zorders.forEach(zorder => zorder.addEventListener('click', this.onTapZorder.bind(this)));
38
- this.distributes.forEach(distribute => distribute.addEventListener('click', this.onTapDistribute.bind(this)));
39
- this.undo.addEventListener('click', this.onTapUndo.bind(this));
40
- this.redo.addEventListener('click', this.onTapRedo.bind(this));
41
- this.fullscreen.addEventListener('click', this.onTapFullscreen.bind(this));
42
- this.styleCopy.addEventListener('click', this.onStartStylePasteMode.bind(this));
43
- this.databindCopy.addEventListener('click', this.onStartDatabindPasteMode.bind(this));
44
- this.cut.addEventListener('click', this.onTapCut.bind(this));
45
- this.copy.addEventListener('click', this.onTapCopy.bind(this));
46
- this.paste.addEventListener('click', this.onTapPaste.bind(this));
47
- this.delete.addEventListener('click', this.onTapDelete.bind(this));
48
- this.renderRoot
49
- .querySelector('#font-increase')
50
- .addEventListener('click', this.onTapFontIncrease.bind(this));
51
- this.renderRoot
52
- .querySelector('#font-decrease')
53
- .addEventListener('click', this.onTapFontDecrease.bind(this));
54
- (_a = this.renderRoot.querySelector('#group')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', this.onTapGroup.bind(this));
55
- (_b = this.renderRoot.querySelector('#ungroup')) === null || _b === void 0 ? void 0 : _b.addEventListener('click', this.onTapUngroup.bind(this));
56
- this.renderRoot
57
- .querySelector('#toggle-property')
58
- .addEventListener('click', this.onTapToggle.bind(this));
59
- this.renderRoot
60
- .querySelector('#fit-scene')
61
- .addEventListener('click', this.onTapFitScene.bind(this));
62
- this.renderRoot.querySelector('#preview').addEventListener('click', this.onTapPreview.bind(this));
81
+ // 모든 버튼은 템플릿 @click 디렉티브로 바인딩 — 조건부 렌더링 시에도 정상 작동
63
82
  }
64
83
  updated(changes) {
65
84
  changes.has('scene') && this.onSceneChanged(this.scene, changes.get('scene'));
@@ -70,82 +89,43 @@ export class EditToolbar extends LitElement {
70
89
  <div tools>
71
90
  <span><slot></slot></span>
72
91
 
73
- <span button id="undo" title="undo (${this.getShortcutString('cmd', 'z')})"> </span>
74
- <span button id="redo" title="redo (${this.getShortcutString('cmd', 'shift', 'z')})"> </span>
92
+ <span button id="undo" title="undo (${this.getShortcutString('cmd', 'z')})" @click=${this.onTapUndo}> </span>
93
+ <span button id="redo" title="redo (${this.getShortcutString('cmd', 'shift', 'z')})" @click=${this.onTapRedo}> </span>
75
94
 
76
95
  <span class="vline"></span>
77
96
 
78
- <span button id="style-copy" title="style copy (${this.getShortcutString('cmd', '1')})"> </span>
79
- <span button id="databind-copy" title="databind copy (${this.getShortcutString('cmd', '2')})"> </span>
97
+ <span button id="style-copy" title="style copy (${this.getShortcutString('cmd', '1')})" @click=${this.onStartStylePasteMode}> </span>
98
+ <span button id="databind-copy" title="databind copy (${this.getShortcutString('cmd', '2')})" @click=${this.onStartDatabindPasteMode}> </span>
80
99
 
81
100
  <span class="vline"></span>
82
101
 
83
- <span button id="cut" title="cut (${this.getShortcutString('cmd', 'x')})"> </span>
84
- <span button id="copy" title="copy (${this.getShortcutString('cmd', 'c')})"> </span>
85
- <span button id="paste" title="paste (${this.getShortcutString('cmd', 'v')})"> </span>
86
- <span
87
- button
88
- id="delete"
102
+ <span button id="cut" title="cut (${this.getShortcutString('cmd', 'x')})" @click=${this.onTapCut}> </span>
103
+ <span button id="copy" title="copy (${this.getShortcutString('cmd', 'c')})" @click=${this.onTapCopy}> </span>
104
+ <span button id="paste" title="paste (${this.getShortcutString('cmd', 'v')})" @click=${this.onTapPaste}> </span>
105
+ <span button id="delete"
89
106
  title="delete (${this.getShortcutString('backspace')}, ${this.getShortcutString('delete')})"
90
- >
91
- </span>
107
+ @click=${this.onTapDelete}> </span>
92
108
 
93
109
  <span class="vline"></span>
94
110
 
95
- <span
96
- button
97
- data-align="left"
98
- id="align-left"
99
- title="align left (${this.getShortcutString('alt', 'shift', 'l')})"
100
- >
101
- </span>
102
- <span
103
- button
104
- data-align="center"
105
- id="align-center"
106
- title="align center (${this.getShortcutString('alt', 'shift', 'c')})"
107
- >
108
- </span>
109
- <span
110
- button
111
- data-align="right"
112
- id="align-right"
113
- title="align right (${this.getShortcutString('alt', 'shift', 'r')})"
114
- >
115
- </span>
116
-
117
- <span button data-align="top" id="align-top" title="align top (${this.getShortcutString('alt', 'shift', 't')})">
118
- </span>
119
- <span
120
- button
121
- data-align="middle"
122
- id="align-middle"
123
- title="align middle (${this.getShortcutString('alt', 'shift', 'm')})"
124
- >
125
- </span>
126
- <span
127
- button
128
- data-align="bottom"
129
- id="align-bottom"
130
- title="align bottom (${this.getShortcutString('alt', 'shift', 'b')})"
131
- >
132
- </span>
111
+ <span button data-align="left" id="align-left"
112
+ title="align left (${this.getShortcutString('alt', 'shift', 'l')})" @click=${this.onTapAlign}> </span>
113
+ <span button data-align="center" id="align-center"
114
+ title="align center (${this.getShortcutString('alt', 'shift', 'c')})" @click=${this.onTapAlign}> </span>
115
+ <span button data-align="right" id="align-right"
116
+ title="align right (${this.getShortcutString('alt', 'shift', 'r')})" @click=${this.onTapAlign}> </span>
133
117
 
134
- <span
135
- button
136
- data-distribute="HORIZONTAL"
137
- id="distribute-horizontal"
138
- title="distribute horizontally (${this.getShortcutString('alt', 'shift', 'h')})"
139
- >
140
- </span>
118
+ <span button data-align="top" id="align-top"
119
+ title="align top (${this.getShortcutString('alt', 'shift', 't')})" @click=${this.onTapAlign}> </span>
120
+ <span button data-align="middle" id="align-middle"
121
+ title="align middle (${this.getShortcutString('alt', 'shift', 'm')})" @click=${this.onTapAlign}> </span>
122
+ <span button data-align="bottom" id="align-bottom"
123
+ title="align bottom (${this.getShortcutString('alt', 'shift', 'b')})" @click=${this.onTapAlign}> </span>
141
124
 
142
- <span
143
- button
144
- data-distribute="VERTICAL"
145
- id="distribute-vertical"
146
- title="distribute vertically (${this.getShortcutString('alt', 'shift', 'v')})"
147
- >
148
- </span>
125
+ <span button data-distribute="HORIZONTAL" id="distribute-horizontal"
126
+ title="distribute horizontally (${this.getShortcutString('alt', 'shift', 'h')})" @click=${this.onTapDistribute}> </span>
127
+ <span button data-distribute="VERTICAL" id="distribute-vertical"
128
+ title="distribute vertically (${this.getShortcutString('alt', 'shift', 'v')})" @click=${this.onTapDistribute}> </span>
149
129
 
150
130
  ${this._dimension === '3d'
151
131
  ? html `
@@ -160,42 +140,22 @@ export class EditToolbar extends LitElement {
160
140
  ? html `
161
141
  <span class="vline"></span>
162
142
 
163
- <span
164
- button
165
- id="front"
166
- data-zorder="front"
167
- title="bring to front (${this.getShortcutString('cmd', 'shift', 'f')})"
168
- >
169
- </span>
170
- <span
171
- button
172
- id="back"
173
- data-zorder="back"
174
- title="send to back (${this.getShortcutString('cmd', 'shift', 'b')})"
175
- >
176
- </span>
177
- <span
178
- button
179
- id="forward"
180
- data-zorder="forward"
181
- title="bring forward (${this.getShortcutString('cmd', 'f')})"
182
- >
183
- </span>
184
- <span
185
- button
186
- id="backward"
187
- data-zorder="backward"
188
- title="send backward (${this.getShortcutString('cmd', 'b')})"
189
- >
190
- </span>
143
+ <span button id="front" data-zorder="front"
144
+ title="bring to front (${this.getShortcutString('cmd', 'shift', 'f')})" @click=${this.onTapZorder}> </span>
145
+ <span button id="back" data-zorder="back"
146
+ title="send to back (${this.getShortcutString('cmd', 'shift', 'b')})" @click=${this.onTapZorder}> </span>
147
+ <span button id="forward" data-zorder="forward"
148
+ title="bring forward (${this.getShortcutString('cmd', 'f')})" @click=${this.onTapZorder}> </span>
149
+ <span button id="backward" data-zorder="backward"
150
+ title="send backward (${this.getShortcutString('cmd', 'b')})" @click=${this.onTapZorder}> </span>
191
151
  `
192
152
  : ''}
193
153
 
194
154
  ${this._dimension === '2d' ? html `
195
155
  <span class="vline"></span>
196
156
 
197
- <span button id="group" title="group (${this.getShortcutString('cmd', 'g')})"> </span>
198
- <span button id="ungroup" title="ungroup (${this.getShortcutString('cmd', 'shift', 'g')})"> </span>
157
+ <span button id="group" title="group (${this.getShortcutString('cmd', 'g')})" @click=${this.onTapGroup}> </span>
158
+ <span button id="ungroup" title="ungroup (${this.getShortcutString('cmd', 'shift', 'g')})" @click=${this.onTapUngroup}> </span>
199
159
  ` : this._dimension === '3d' ? html `
200
160
  <span class="vline"></span>
201
161
 
@@ -228,27 +188,23 @@ export class EditToolbar extends LitElement {
228
188
 
229
189
  <span class="vline"></span>
230
190
 
231
- <span button id="font-increase" title="increase font size"></span>
232
- <span button id="font-decrease" title="decrease font size" style="scale: 0.7;"></span>
191
+ <span button id="font-increase" title="increase font size" @click=${this.onTapFontIncrease}></span>
192
+ <span button id="font-decrease" title="decrease font size" style="scale: 0.7;" @click=${this.onTapFontDecrease}></span>
233
193
 
234
194
  <span class="vline"></span>
235
195
  <span padding></span>
236
196
 
237
- <span button id="fit-scene" title="fit scene (${this.getShortcutString('cmd', 'd')})"> </span>
197
+ <span button id="fit-scene" title="fit scene (${this.getShortcutString('cmd', 'd')})" @click=${this.onTapFitScene}> </span>
238
198
 
239
199
  <span class="vline"></span>
240
200
 
241
- <span button id="preview" title="preview (${this.getShortcutString('ctrl', 'p')})"> </span>
201
+ <span button id="preview" title="preview (${this.getShortcutString('ctrl', 'p')})" @click=${this.onTapPreview}> </span>
242
202
 
243
- <span button id="fullscreen" title="fullscreen (${this.getShortcutString('f11')})"> </span>
203
+ <span button id="fullscreen" title="fullscreen (${this.getShortcutString('f11')})" @click=${this.onTapFullscreen}> </span>
244
204
 
245
- <span
246
- button
247
- id="toggle-property"
205
+ <span button id="toggle-property"
248
206
  title="toggle property panel (${this.getShortcutString('cmd', 'h')})"
249
- toggles="true"
250
- >
251
- </span>
207
+ toggles="true" @click=${this.onTapToggle}> </span>
252
208
  </div>
253
209
  `;
254
210
  }
@@ -329,7 +285,12 @@ export class EditToolbar extends LitElement {
329
285
  this.onTapCut();
330
286
  break;
331
287
  case 'KeyV':
332
- if (ctrlKey && !shiftKey) {
288
+ if (ctrlKey && shiftKey && !altKey) {
289
+ // Ctrl+Shift+V = Bulk Paste — 클립보드의 *단일* 컴포넌트를 N개로 일괄 붙여넣기.
290
+ this.onBulkPaste();
291
+ defaultPrevent = true;
292
+ }
293
+ else if (ctrlKey && !shiftKey) {
333
294
  this.onTapPaste();
334
295
  defaultPrevent = false;
335
296
  }
@@ -349,21 +310,35 @@ export class EditToolbar extends LitElement {
349
310
  this.onTapUngroup();
350
311
  }
351
312
  break;
352
- case 'KeyF':
313
+ case 'BracketRight':
314
+ // Ctrl+] = Bring Forward, Ctrl+Shift+] = Bring to Front
315
+ // (Photoshop / Illustrator 표준 컨벤션)
353
316
  if (this._dimension === '2d') {
354
- if (ctrlKey && !shiftKey)
317
+ if (ctrlKey && !shiftKey) {
355
318
  (_a = this.scene) === null || _a === void 0 ? void 0 : _a.zorder('forward');
356
- else if (ctrlKey && shiftKey)
319
+ defaultPrevent = true;
320
+ }
321
+ else if (ctrlKey && shiftKey) {
357
322
  (_b = this.scene) === null || _b === void 0 ? void 0 : _b.zorder('front');
323
+ defaultPrevent = true;
324
+ }
358
325
  }
359
326
  break;
360
- case 'KeyB':
327
+ case 'BracketLeft':
328
+ // Ctrl+[ = Send Backward, Ctrl+Shift+[ = Send to Back
361
329
  if (this._dimension === '2d') {
362
- if (ctrlKey && !shiftKey)
330
+ if (ctrlKey && !shiftKey) {
363
331
  (_c = this.scene) === null || _c === void 0 ? void 0 : _c.zorder('backward');
364
- else if (ctrlKey && shiftKey)
332
+ defaultPrevent = true;
333
+ }
334
+ else if (ctrlKey && shiftKey) {
365
335
  (_d = this.scene) === null || _d === void 0 ? void 0 : _d.zorder('back');
336
+ defaultPrevent = true;
337
+ }
366
338
  }
339
+ break;
340
+ case 'KeyB':
341
+ // zorder 는 BracketLeft 로 이동. KeyB 의 Alt+Shift+B = align bottom 만 유지.
367
342
  if (altKey && shiftKey)
368
343
  this.onTapAlign('bottom');
369
344
  break;
@@ -408,11 +383,23 @@ export class EditToolbar extends LitElement {
408
383
  this.onTapFitScene();
409
384
  defaultPrevent = true;
410
385
  }
386
+ else if (!shiftKey && !altKey) {
387
+ const target = e.composedPath()[0];
388
+ const tagName = target.tagName;
389
+ if (!target.isContentEditable && tagName !== 'INPUT' && tagName !== 'SELECT' && tagName !== 'TEXTAREA') {
390
+ this.onTapDataBinding();
391
+ defaultPrevent = true;
392
+ }
393
+ }
411
394
  break;
412
395
  case 'KeyE':
413
396
  if (ctrlKey && shiftKey)
414
397
  this.onTapDownloadModel();
415
398
  break;
399
+ case 'F10':
400
+ this.onTapEditModel();
401
+ defaultPrevent = true;
402
+ break;
416
403
  case 'Digit1':
417
404
  if (ctrlKey) {
418
405
  defaultPrevent = true;
@@ -435,19 +422,24 @@ export class EditToolbar extends LitElement {
435
422
  e.preventDefault();
436
423
  return true;
437
424
  }
425
+ _setDisabled(id, disabled) {
426
+ const el = this.renderRoot.querySelector(`#${id}`);
427
+ disabled ? el === null || el === void 0 ? void 0 : el.setAttribute('disabled', '') : el === null || el === void 0 ? void 0 : el.removeAttribute('disabled');
428
+ }
438
429
  onExecute(command, undoable, redoable) {
439
- !undoable ? this.undo.setAttribute('disabled', '') : this.undo.removeAttribute('disabled');
440
- !redoable ? this.redo.setAttribute('disabled', '') : this.redo.removeAttribute('disabled');
430
+ this._setDisabled('undo', !undoable);
431
+ this._setDisabled('redo', !redoable);
441
432
  }
442
433
  onUndo(undoable, redoable) {
443
- !undoable ? this.undo.setAttribute('disabled', '') : this.undo.removeAttribute('disabled');
444
- !redoable ? this.redo.setAttribute('disabled', '') : this.redo.removeAttribute('disabled');
434
+ this._setDisabled('undo', !undoable);
435
+ this._setDisabled('redo', !redoable);
445
436
  }
446
437
  onRedo(undoable, redoable) {
447
- !undoable ? this.undo.setAttribute('disabled', '') : this.undo.removeAttribute('disabled');
448
- !redoable ? this.redo.setAttribute('disabled', '') : this.redo.removeAttribute('disabled');
438
+ this._setDisabled('undo', !undoable);
439
+ this._setDisabled('redo', !redoable);
449
440
  }
450
441
  onSceneChanged(after, before) {
442
+ var _a, _b;
451
443
  if (before) {
452
444
  before.off('execute', this.onExecute, this);
453
445
  before.off('undo', this.onUndo, this);
@@ -461,6 +453,9 @@ export class EditToolbar extends LitElement {
461
453
  after.on('redo', this.onRedo, this);
462
454
  after.on('dimension', this._onDimensionChanged, this);
463
455
  after.on('gizmoattach', this._onGizmoAttachChanged, this);
456
+ // scene 설정 시 현재 dimension을 즉시 반영 (초기 이벤트를 놓친 경우 대비)
457
+ const threed = (_b = (_a = after.model_layer) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.threed;
458
+ this._dimension = threed ? '3d' : '2d';
464
459
  }
465
460
  }
466
461
  _onDimensionChanged(dimension) {
@@ -475,27 +470,23 @@ export class EditToolbar extends LitElement {
475
470
  (_b = (_a = this.scene) === null || _a === void 0 ? void 0 : _a.root) === null || _b === void 0 ? void 0 : _b.setGizmoMode(mode);
476
471
  }
477
472
  onSelectedChanged(after, before) {
478
- var _a, _b, _c, _d, _e, _f, _g, _h;
479
473
  var hasSelection = after.length > 0;
480
474
  var alignable = after.length > 1;
481
- // 선택 필요 버튼: 스타일 복사, 데이터바인드 복사, cut, copy, delete
482
- const selectionButtons = [this.styleCopy, this.databindCopy, this.cut, this.copy, this.delete];
483
- selectionButtons.forEach(btn => {
484
- hasSelection ? btn === null || btn === void 0 ? void 0 : btn.removeAttribute('disabled') : btn === null || btn === void 0 ? void 0 : btn.setAttribute('disabled', '');
485
- });
486
- this.aligners.forEach(aligner => alignable ? aligner.removeAttribute('disabled') : aligner.setAttribute('disabled', ''));
475
+ ['style-copy', 'databind-copy', 'cut', 'copy', 'delete'].forEach(id => this._setDisabled(id, !hasSelection));
476
+ // 정렬 버튼
477
+ this.renderRoot.querySelectorAll('[data-align]').forEach(el => alignable ? el.removeAttribute('disabled') : el.setAttribute('disabled', ''));
487
478
  if (this._dimension === '2d') {
488
479
  var movable = after.length === 1;
489
480
  /* forward, backward 이동은 한 컴포넌트만 가능하다. */
490
- !movable ? (_a = this.forward) === null || _a === void 0 ? void 0 : _a.setAttribute('disabled', '') : (_b = this.forward) === null || _b === void 0 ? void 0 : _b.removeAttribute('disabled');
491
- !movable ? (_c = this.backward) === null || _c === void 0 ? void 0 : _c.setAttribute('disabled', '') : (_d = this.backward) === null || _d === void 0 ? void 0 : _d.removeAttribute('disabled');
481
+ this._setDisabled('forward', !movable);
482
+ this._setDisabled('backward', !movable);
492
483
  /* 여러 컴포넌트는 front, back 이동이 가능하다. */
493
- !(alignable || movable) ? (_e = this.front) === null || _e === void 0 ? void 0 : _e.setAttribute('disabled', '') : (_f = this.front) === null || _f === void 0 ? void 0 : _f.removeAttribute('disabled');
494
- !(alignable || movable) ? (_g = this.back) === null || _g === void 0 ? void 0 : _g.setAttribute('disabled', '') : (_h = this.back) === null || _h === void 0 ? void 0 : _h.removeAttribute('disabled');
484
+ this._setDisabled('front', !(alignable || movable));
485
+ this._setDisabled('back', !(alignable || movable));
495
486
  }
496
- this.distributes.forEach(distribute => alignable ? distribute.removeAttribute('disabled') : distribute.setAttribute('disabled', ''));
487
+ // 분배 버튼
488
+ this.renderRoot.querySelectorAll('[data-distribute]').forEach(el => alignable ? el.removeAttribute('disabled') : el.setAttribute('disabled', ''));
497
489
  if (this._dimension === '3d') {
498
- const hasSelection = after.length > 0;
499
490
  this.renderRoot.querySelectorAll('.gizmo-btn').forEach(btn => {
500
491
  hasSelection ? btn.removeAttribute('disabled') : btn.setAttribute('disabled', '');
501
492
  });
@@ -547,6 +538,97 @@ export class EditToolbar extends LitElement {
547
538
  var _a;
548
539
  (_a = this.scene) === null || _a === void 0 ? void 0 : _a.remove();
549
540
  }
541
+ /**
542
+ * 멀티 컴포넌트 일괄 붙여넣기 — Ctrl+Shift+V 진입점.
543
+ *
544
+ * 흐름:
545
+ * 1. 클립보드 (this.cliped) 검증 — *단일* 컴포넌트만 허용
546
+ * 2. scene.startBulkCreateMode → AddLayer 가 캔버스 위에 드래그 박스 그림
547
+ * 3. 사용자가 영역 드래그 → Promise resolve({area, template})
548
+ * ESC/취소 → Promise resolve(null)
549
+ * 4. 영역이 결정되면 dialog 오픈
550
+ */
551
+ async onBulkPaste() {
552
+ var _a;
553
+ if (!this.cliped) {
554
+ console.warn('bulk-paste: 먼저 Ctrl+C 로 컴포넌트를 복사하세요.');
555
+ return;
556
+ }
557
+ let parsed;
558
+ try {
559
+ parsed = JSON.parse(this.cliped);
560
+ }
561
+ catch (e) {
562
+ console.warn('bulk-paste: 클립보드 파싱 실패', e);
563
+ return;
564
+ }
565
+ if (!Array.isArray(parsed) || parsed.length === 0)
566
+ return;
567
+ if (parsed.length > 1) {
568
+ console.warn('bulk-paste: 단일 컴포넌트만 일괄 붙여넣기 가능. 다중은 일반 paste 사용.');
569
+ return;
570
+ }
571
+ const template = parsed[0];
572
+ // bulk 모드 임을 시각 구분하기 위해 파란 점선 (일반 add 의 검정과 색만 차별).
573
+ const result = await ((_a = this.scene) === null || _a === void 0 ? void 0 : _a.startBulkCreateMode(template, { boxStrokeStyle: '#2964c2' }));
574
+ if (result) {
575
+ this._openBulkCreateDialog(template, result.area);
576
+ }
577
+ }
578
+ _openBulkCreateDialog(template, initialArea) {
579
+ var _a;
580
+ const dialogEl = document.createElement('bulk-create-dialog');
581
+ dialogEl.template = template;
582
+ dialogEl.initialArea = initialArea;
583
+ // scene 의 모든 id 수집 — indexMap 기반 scene.ids 대신 트리 walk 로 안정성 확보.
584
+ const existingIds = new Set();
585
+ const collectIds = (comp) => {
586
+ var _a, _b, _c;
587
+ if (!comp)
588
+ return;
589
+ const id = (_b = (_a = comp === null || comp === void 0 ? void 0 : comp.state) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : (_c = comp === null || comp === void 0 ? void 0 : comp.model) === null || _c === void 0 ? void 0 : _c.id;
590
+ if (typeof id === 'string' && id)
591
+ existingIds.add(id);
592
+ const children = comp.components || [];
593
+ for (const c of children)
594
+ collectIds(c);
595
+ };
596
+ const root = (_a = this.scene) === null || _a === void 0 ? void 0 : _a.root;
597
+ if (root)
598
+ collectIds(root);
599
+ dialogEl.existingIds = existingIds;
600
+ dialogEl.onConfirm = (models) => {
601
+ const scene = this.scene;
602
+ if (!scene || !models.length) {
603
+ popup === null || popup === void 0 ? void 0 : popup.close();
604
+ return;
605
+ }
606
+ // cliped 의 template model 자체에서 type-aware bounds 추출. things-scene 의
607
+ // component.bounds (= path bounding box) 와 동일 결과를 model JSON 의
608
+ // type-specific 필드 (width/height, rx/ry, points) 만으로 계산.
609
+ const bounds = deriveBoundsFromModel(template);
610
+ scene.undoableChange(() => {
611
+ for (const m of models) {
612
+ const cx = m.left + m.width / 2;
613
+ const cy = m.top + m.height / 2;
614
+ scene.add(m, {
615
+ left: cx - bounds.width / 2,
616
+ top: cy - bounds.height / 2,
617
+ width: bounds.width,
618
+ height: bounds.height
619
+ });
620
+ }
621
+ });
622
+ popup === null || popup === void 0 ? void 0 : popup.close();
623
+ };
624
+ dialogEl.onCancel = () => {
625
+ popup === null || popup === void 0 ? void 0 : popup.close();
626
+ };
627
+ const popup = OxPopup.open({
628
+ template: dialogEl,
629
+ backdrop: true
630
+ });
631
+ }
550
632
  onTapSelectAll() {
551
633
  var _a;
552
634
  (_a = this.scene) === null || _a === void 0 ? void 0 : _a.select('(child)');
@@ -638,9 +720,15 @@ export class EditToolbar extends LitElement {
638
720
  onTapPreview() {
639
721
  this.dispatchEvent(new CustomEvent('open-preview'));
640
722
  }
723
+ onTapDataBinding() {
724
+ this.dispatchEvent(new CustomEvent('open-data-binding'));
725
+ }
641
726
  onTapDownloadModel() {
642
727
  this.dispatchEvent(new CustomEvent('download-model'));
643
728
  }
729
+ onTapEditModel() {
730
+ this.dispatchEvent(new CustomEvent('edit-model'));
731
+ }
644
732
  onTapDistribute(e) {
645
733
  if (!this.scene)
646
734
  return;
@@ -668,52 +756,4 @@ __decorate([
668
756
  __decorate([
669
757
  state()
670
758
  ], EditToolbar.prototype, "_gizmoAttached", void 0);
671
- __decorate([
672
- query('#redo')
673
- ], EditToolbar.prototype, "redo", void 0);
674
- __decorate([
675
- query('#undo')
676
- ], EditToolbar.prototype, "undo", void 0);
677
- __decorate([
678
- query('#fullscreen')
679
- ], EditToolbar.prototype, "fullscreen", void 0);
680
- __decorate([
681
- query('#style-copy')
682
- ], EditToolbar.prototype, "styleCopy", void 0);
683
- __decorate([
684
- query('#databind-copy')
685
- ], EditToolbar.prototype, "databindCopy", void 0);
686
- __decorate([
687
- query('#cut')
688
- ], EditToolbar.prototype, "cut", void 0);
689
- __decorate([
690
- query('#copy')
691
- ], EditToolbar.prototype, "copy", void 0);
692
- __decorate([
693
- query('#paste')
694
- ], EditToolbar.prototype, "paste", void 0);
695
- __decorate([
696
- query('#delete')
697
- ], EditToolbar.prototype, "delete", void 0);
698
- __decorate([
699
- query('#forward')
700
- ], EditToolbar.prototype, "forward", void 0);
701
- __decorate([
702
- query('#backward')
703
- ], EditToolbar.prototype, "backward", void 0);
704
- __decorate([
705
- query('#front')
706
- ], EditToolbar.prototype, "front", void 0);
707
- __decorate([
708
- query('#back')
709
- ], EditToolbar.prototype, "back", void 0);
710
- __decorate([
711
- queryAll('[data-align]')
712
- ], EditToolbar.prototype, "aligners", void 0);
713
- __decorate([
714
- queryAll('[data-zorder]')
715
- ], EditToolbar.prototype, "zorders", void 0);
716
- __decorate([
717
- queryAll('[data-distribute]')
718
- ], EditToolbar.prototype, "distributes", void 0);
719
759
  //# sourceMappingURL=edit-toolbar.js.map