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

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 (86) hide show
  1. package/CHANGELOG.md +408 -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/component-toolbar/component-menu.js +8 -1
  54. package/dist/src/modeller/component-toolbar/component-menu.js.map +1 -1
  55. package/dist/src/modeller/edit-toolbar-style.js +38 -1
  56. package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
  57. package/dist/src/modeller/edit-toolbar.d.ts +21 -16
  58. package/dist/src/modeller/edit-toolbar.js +305 -201
  59. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  60. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
  61. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +7 -11
  62. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  63. package/dist/src/ox-board-modeller.d.ts +8 -1
  64. package/dist/src/ox-board-modeller.js +125 -6
  65. package/dist/src/ox-board-modeller.js.map +1 -1
  66. package/dist/src/ox-board-preview.d.ts +36 -0
  67. package/dist/src/ox-board-preview.js +114 -0
  68. package/dist/src/ox-board-preview.js.map +1 -0
  69. package/dist/src/ox-board-template-list.d.ts +1 -0
  70. package/dist/src/ox-board-template-list.js +19 -1
  71. package/dist/src/ox-board-template-list.js.map +1 -1
  72. package/dist/src/ox-board-viewer.d.ts +50 -1
  73. package/dist/src/ox-board-viewer.js +271 -28
  74. package/dist/src/ox-board-viewer.js.map +1 -1
  75. package/dist/src/ox-playback-controls.d.ts +56 -0
  76. package/dist/src/ox-playback-controls.js +515 -0
  77. package/dist/src/ox-playback-controls.js.map +1 -0
  78. package/dist/src/selector/ox-board-selector.js +11 -1
  79. package/dist/src/selector/ox-board-selector.js.map +1 -1
  80. package/dist/tsconfig.tsbuildinfo +1 -1
  81. package/package.json +17 -12
  82. package/translations/en.json +19 -1
  83. package/translations/ja.json +19 -1
  84. package/translations/ko.json +19 -1
  85. package/translations/ms.json +19 -1
  86. package/translations/zh.json +19 -1
@@ -3,18 +3,28 @@
3
3
  */
4
4
  import { __decorate } from "tslib";
5
5
  import { html, LitElement } from 'lit';
6
- import { property, 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();
10
12
  export class EditToolbar extends LitElement {
11
13
  constructor() {
12
14
  super(...arguments);
13
15
  this.selected = [];
14
16
  this.hideProperty = false;
17
+ this._dimension = null;
18
+ this._gizmoAttached = false;
15
19
  }
16
20
  firstUpdated() {
17
21
  this.addEventListener('mousewheel', this.onWheelEvent.bind(this), false);
22
+ // 툴바 버튼 클릭 후 씬으로 포커스를 돌려서 키보드 단축키가 계속 작동하도록 한다
23
+ this.addEventListener('click', () => {
24
+ var _a, _b, _c;
25
+ ;
26
+ (_c = (_b = (_a = this.scene) === null || _a === void 0 ? void 0 : _a.root) === null || _b === void 0 ? void 0 : _b.element) === null || _c === void 0 ? void 0 : _c.focus();
27
+ });
18
28
  window.addEventListener('paste', (e) => {
19
29
  var _a;
20
30
  try {
@@ -24,33 +34,7 @@ export class EditToolbar extends LitElement {
24
34
  console.error('model paste failed', e);
25
35
  }
26
36
  });
27
- this.aligners.forEach(aligner => aligner.addEventListener('click', this.onTapAlign.bind(this)));
28
- this.zorders.forEach(zorder => zorder.addEventListener('click', this.onTapZorder.bind(this)));
29
- this.distributes.forEach(distribute => distribute.addEventListener('click', this.onTapDistribute.bind(this)));
30
- this.undo.addEventListener('click', this.onTapUndo.bind(this));
31
- this.redo.addEventListener('click', this.onTapRedo.bind(this));
32
- this.fullscreen.addEventListener('click', this.onTapFullscreen.bind(this));
33
- this.styleCopy.addEventListener('click', this.onStartStylePasteMode.bind(this));
34
- this.databindCopy.addEventListener('click', this.onStartDatabindPasteMode.bind(this));
35
- this.cut.addEventListener('click', this.onTapCut.bind(this));
36
- this.copy.addEventListener('click', this.onTapCopy.bind(this));
37
- this.paste.addEventListener('click', this.onTapPaste.bind(this));
38
- this.delete.addEventListener('click', this.onTapDelete.bind(this));
39
- this.renderRoot
40
- .querySelector('#font-increase')
41
- .addEventListener('click', this.onTapFontIncrease.bind(this));
42
- this.renderRoot
43
- .querySelector('#font-decrease')
44
- .addEventListener('click', this.onTapFontDecrease.bind(this));
45
- this.renderRoot.querySelector('#group').addEventListener('click', this.onTapGroup.bind(this));
46
- this.renderRoot.querySelector('#ungroup').addEventListener('click', this.onTapUngroup.bind(this));
47
- this.renderRoot
48
- .querySelector('#toggle-property')
49
- .addEventListener('click', this.onTapToggle.bind(this));
50
- this.renderRoot
51
- .querySelector('#fit-scene')
52
- .addEventListener('click', this.onTapFitScene.bind(this));
53
- this.renderRoot.querySelector('#preview').addEventListener('click', this.onTapPreview.bind(this));
37
+ // 모든 버튼은 템플릿 @click 디렉티브로 바인딩 — 조건부 렌더링 시에도 정상 작동
54
38
  }
55
39
  updated(changes) {
56
40
  changes.has('scene') && this.onSceneChanged(this.scene, changes.get('scene'));
@@ -61,127 +45,122 @@ export class EditToolbar extends LitElement {
61
45
  <div tools>
62
46
  <span><slot></slot></span>
63
47
 
64
- <span button id="undo" title="undo (${this.getShortcutString('cmd', 'z')})"> </span>
65
- <span button id="redo" title="redo (${this.getShortcutString('cmd', 'shift', 'z')})"> </span>
48
+ <span button id="undo" title="undo (${this.getShortcutString('cmd', 'z')})" @click=${this.onTapUndo}> </span>
49
+ <span button id="redo" title="redo (${this.getShortcutString('cmd', 'shift', 'z')})" @click=${this.onTapRedo}> </span>
66
50
 
67
51
  <span class="vline"></span>
68
52
 
69
- <span button id="style-copy" title="style copy (${this.getShortcutString('cmd', '1')})"> </span>
70
- <span button id="databind-copy" title="databind copy (${this.getShortcutString('cmd', '2')})"> </span>
53
+ <span button id="style-copy" title="style copy (${this.getShortcutString('cmd', '1')})" @click=${this.onStartStylePasteMode}> </span>
54
+ <span button id="databind-copy" title="databind copy (${this.getShortcutString('cmd', '2')})" @click=${this.onStartDatabindPasteMode}> </span>
71
55
 
72
56
  <span class="vline"></span>
73
57
 
74
- <span button id="cut" title="cut (${this.getShortcutString('cmd', 'x')})"> </span>
75
- <span button id="copy" title="copy (${this.getShortcutString('cmd', 'c')})"> </span>
76
- <span button id="paste" title="paste (${this.getShortcutString('cmd', 'v')})"> </span>
77
- <span
78
- button
79
- id="delete"
58
+ <span button id="cut" title="cut (${this.getShortcutString('cmd', 'x')})" @click=${this.onTapCut}> </span>
59
+ <span button id="copy" title="copy (${this.getShortcutString('cmd', 'c')})" @click=${this.onTapCopy}> </span>
60
+ <span button id="paste" title="paste (${this.getShortcutString('cmd', 'v')})" @click=${this.onTapPaste}> </span>
61
+ <span button id="delete"
80
62
  title="delete (${this.getShortcutString('backspace')}, ${this.getShortcutString('delete')})"
81
- >
82
- </span>
63
+ @click=${this.onTapDelete}> </span>
83
64
 
84
65
  <span class="vline"></span>
85
66
 
86
- <span
87
- button
88
- data-align="left"
89
- id="align-left"
90
- title="align left (${this.getShortcutString('alt', 'shift', 'l')})"
91
- >
92
- </span>
93
- <span
94
- button
95
- data-align="center"
96
- id="align-center"
97
- title="align center (${this.getShortcutString('alt', 'shift', 'c')})"
98
- >
99
- </span>
100
- <span
101
- button
102
- data-align="right"
103
- id="align-right"
104
- title="align right (${this.getShortcutString('alt', 'shift', 'r')})"
105
- >
106
- </span>
67
+ <span button data-align="left" id="align-left"
68
+ title="align left (${this.getShortcutString('alt', 'shift', 'l')})" @click=${this.onTapAlign}> </span>
69
+ <span button data-align="center" id="align-center"
70
+ title="align center (${this.getShortcutString('alt', 'shift', 'c')})" @click=${this.onTapAlign}> </span>
71
+ <span button data-align="right" id="align-right"
72
+ title="align right (${this.getShortcutString('alt', 'shift', 'r')})" @click=${this.onTapAlign}> </span>
107
73
 
108
- <span button data-align="top" id="align-top" title="align top (${this.getShortcutString('alt', 'shift', 't')})">
109
- </span>
110
- <span
111
- button
112
- data-align="middle"
113
- id="align-middle"
114
- title="align middle (${this.getShortcutString('alt', 'shift', 'm')})"
115
- >
116
- </span>
117
- <span
118
- button
119
- data-align="bottom"
120
- id="align-bottom"
121
- title="align bottom (${this.getShortcutString('alt', 'shift', 'b')})"
122
- >
123
- </span>
74
+ <span button data-align="top" id="align-top"
75
+ title="align top (${this.getShortcutString('alt', 'shift', 't')})" @click=${this.onTapAlign}> </span>
76
+ <span button data-align="middle" id="align-middle"
77
+ title="align middle (${this.getShortcutString('alt', 'shift', 'm')})" @click=${this.onTapAlign}> </span>
78
+ <span button data-align="bottom" id="align-bottom"
79
+ title="align bottom (${this.getShortcutString('alt', 'shift', 'b')})" @click=${this.onTapAlign}> </span>
124
80
 
125
- <span
126
- button
127
- data-distribute="HORIZONTAL"
128
- id="distribute-horizontal"
129
- title="distribute horizontally (${this.getShortcutString('alt', 'shift', 'h')})"
130
- >
131
- </span>
81
+ <span button data-distribute="HORIZONTAL" id="distribute-horizontal"
82
+ title="distribute horizontally (${this.getShortcutString('alt', 'shift', 'h')})" @click=${this.onTapDistribute}> </span>
83
+ <span button data-distribute="VERTICAL" id="distribute-vertical"
84
+ title="distribute vertically (${this.getShortcutString('alt', 'shift', 'v')})" @click=${this.onTapDistribute}> </span>
132
85
 
133
- <span
134
- button
135
- data-distribute="VERTICAL"
136
- id="distribute-vertical"
137
- title="distribute vertically (${this.getShortcutString('alt', 'shift', 'v')})"
138
- >
139
- </span>
86
+ ${this._dimension === '3d'
87
+ ? html `
88
+ <span class="vline"></span>
140
89
 
141
- <span class="vline"></span>
90
+ <span button data-align="z-front" id="align-z-front" title="align Z front" @click=${this.onTapAlign}> </span>
91
+ <span button data-align="z-middle" id="align-z-middle" title="align Z middle" @click=${this.onTapAlign}> </span>
92
+ <span button data-align="z-back" id="align-z-back" title="align Z back" @click=${this.onTapAlign}> </span>
93
+ <span button data-distribute="Z" id="distribute-z" title="distribute Z" @click=${this.onTapDistribute}> </span>
94
+ `
95
+ : this._dimension === '2d'
96
+ ? html `
97
+ <span class="vline"></span>
142
98
 
143
- <span
144
- button
145
- id="front"
146
- data-zorder="front"
147
- title="bring to front (${this.getShortcutString('cmd', 'shift', 'f')})"
148
- >
149
- </span>
150
- <span button id="back" data-zorder="back" title="send to back (${this.getShortcutString('cmd', 'shift', 'b')})">
151
- </span>
152
- <span button id="forward" data-zorder="forward" title="bring forward (${this.getShortcutString('cmd', 'f')})">
153
- </span>
154
- <span button id="backward" data-zorder="backward" title="send backward (${this.getShortcutString('cmd', 'b')})">
155
- </span>
99
+ <span button id="front" data-zorder="front"
100
+ title="bring to front (${this.getShortcutString('cmd', 'shift', 'f')})" @click=${this.onTapZorder}> </span>
101
+ <span button id="back" data-zorder="back"
102
+ title="send to back (${this.getShortcutString('cmd', 'shift', 'b')})" @click=${this.onTapZorder}> </span>
103
+ <span button id="forward" data-zorder="forward"
104
+ title="bring forward (${this.getShortcutString('cmd', 'f')})" @click=${this.onTapZorder}> </span>
105
+ <span button id="backward" data-zorder="backward"
106
+ title="send backward (${this.getShortcutString('cmd', 'b')})" @click=${this.onTapZorder}> </span>
107
+ `
108
+ : ''}
156
109
 
157
- <span class="vline"></span>
110
+ ${this._dimension === '2d' ? html `
111
+ <span class="vline"></span>
158
112
 
159
- <span button id="group" title="group (${this.getShortcutString('cmd', 'g')})"> </span>
160
- <span button id="ungroup" title="ungroup (${this.getShortcutString('cmd', 'shift', 'g')})"> </span>
113
+ <span button id="group" title="group (${this.getShortcutString('cmd', 'g')})" @click=${this.onTapGroup}> </span>
114
+ <span button id="ungroup" title="ungroup (${this.getShortcutString('cmd', 'shift', 'g')})" @click=${this.onTapUngroup}> </span>
115
+ ` : this._dimension === '3d' ? html `
116
+ <span class="vline"></span>
117
+
118
+ <span button class="gizmo-btn" data-gizmo="translate" title="Move (W)"
119
+ @click=${() => this._setGizmoMode('translate')}>
120
+ <svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8">
121
+ <line x1="10" y1="2" x2="10" y2="18"/><line x1="2" y1="10" x2="18" y2="10"/>
122
+ <polyline points="10,2 7.5,5"/><polyline points="10,2 12.5,5"/>
123
+ <polyline points="18,10 15,7.5"/><polyline points="18,10 15,12.5"/>
124
+ <polyline points="10,18 7.5,15"/><polyline points="10,18 12.5,15"/>
125
+ <polyline points="2,10 5,7.5"/><polyline points="2,10 5,12.5"/>
126
+ </svg>
127
+ </span>
128
+ <span button class="gizmo-btn" data-gizmo="rotate" title="Rotate (E)"
129
+ @click=${() => this._setGizmoMode('rotate')}>
130
+ <svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8">
131
+ <path d="M14.5 3.5A7 7 0 1 0 17 10"/>
132
+ <polyline points="14,1 15,3.5 12.5,4.5"/>
133
+ </svg>
134
+ </span>
135
+ <span button class="gizmo-btn" data-gizmo="scale" title="Scale (R)"
136
+ @click=${() => this._setGizmoMode('scale')}>
137
+ <svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8">
138
+ <line x1="4" y1="16" x2="16" y2="4"/>
139
+ <rect x="2" y="13" width="4" height="4" rx="0.5" fill="currentColor"/>
140
+ <rect x="14" y="2" width="4" height="4" rx="0.5" fill="currentColor"/>
141
+ </svg>
142
+ </span>
143
+ ` : ''}
161
144
 
162
145
  <span class="vline"></span>
163
146
 
164
- <span button id="font-increase" title="increase font size"></span>
165
- <span button id="font-decrease" title="decrease font size" style="scale: 0.7;"></span>
147
+ <span button id="font-increase" title="increase font size" @click=${this.onTapFontIncrease}></span>
148
+ <span button id="font-decrease" title="decrease font size" style="scale: 0.7;" @click=${this.onTapFontDecrease}></span>
166
149
 
167
150
  <span class="vline"></span>
168
151
  <span padding></span>
169
152
 
170
- <span button id="fit-scene" title="fit scene (${this.getShortcutString('cmd', 'd')})"> </span>
153
+ <span button id="fit-scene" title="fit scene (${this.getShortcutString('cmd', 'd')})" @click=${this.onTapFitScene}> </span>
171
154
 
172
155
  <span class="vline"></span>
173
156
 
174
- <span button id="preview" title="preview (${this.getShortcutString('ctrl', 'p')})"> </span>
157
+ <span button id="preview" title="preview (${this.getShortcutString('ctrl', 'p')})" @click=${this.onTapPreview}> </span>
175
158
 
176
- <span button id="fullscreen" title="fullscreen (${this.getShortcutString('f11')})"> </span>
159
+ <span button id="fullscreen" title="fullscreen (${this.getShortcutString('f11')})" @click=${this.onTapFullscreen}> </span>
177
160
 
178
- <span
179
- button
180
- id="toggle-property"
161
+ <span button id="toggle-property"
181
162
  title="toggle property panel (${this.getShortcutString('cmd', 'h')})"
182
- toggles="true"
183
- >
184
- </span>
163
+ toggles="true" @click=${this.onTapToggle}> </span>
185
164
  </div>
186
165
  `;
187
166
  }
@@ -224,7 +203,7 @@ export class EditToolbar extends LitElement {
224
203
  return symbols.join(MACOS ? '' : '+');
225
204
  }
226
205
  onShortcut(e) {
227
- var _a, _b, _c, _d;
206
+ var _a, _b, _c, _d, _e, _f;
228
207
  if (MACOS)
229
208
  var ctrlKey = e.metaKey;
230
209
  else
@@ -262,7 +241,12 @@ export class EditToolbar extends LitElement {
262
241
  this.onTapCut();
263
242
  break;
264
243
  case 'KeyV':
265
- if (ctrlKey && !shiftKey) {
244
+ if (ctrlKey && shiftKey && !altKey) {
245
+ // Ctrl+Shift+V = Bulk Paste — 클립보드의 *단일* 컴포넌트를 N개로 일괄 붙여넣기.
246
+ this.onBulkPaste();
247
+ defaultPrevent = true;
248
+ }
249
+ else if (ctrlKey && !shiftKey) {
266
250
  this.onTapPaste();
267
251
  defaultPrevent = false;
268
252
  }
@@ -275,23 +259,43 @@ export class EditToolbar extends LitElement {
275
259
  defaultPrevent = true;
276
260
  break;
277
261
  case 'KeyG':
278
- if (ctrlKey && !shiftKey)
279
- this.onTapGroup();
280
- else if (ctrlKey && shiftKey)
281
- this.onTapUngroup();
262
+ if (this._dimension === '2d') {
263
+ if (ctrlKey && !shiftKey)
264
+ this.onTapGroup();
265
+ else if (ctrlKey && shiftKey)
266
+ this.onTapUngroup();
267
+ }
282
268
  break;
283
- case 'KeyF':
284
- if (ctrlKey && !shiftKey)
285
- (_a = this.scene) === null || _a === void 0 ? void 0 : _a.zorder('forward');
286
- else if (ctrlKey && shiftKey)
287
- (_b = this.scene) === null || _b === void 0 ? void 0 : _b.zorder('front');
269
+ case 'BracketRight':
270
+ // Ctrl+] = Bring Forward, Ctrl+Shift+] = Bring to Front
271
+ // (Photoshop / Illustrator 표준 컨벤션)
272
+ if (this._dimension === '2d') {
273
+ if (ctrlKey && !shiftKey) {
274
+ (_a = this.scene) === null || _a === void 0 ? void 0 : _a.zorder('forward');
275
+ defaultPrevent = true;
276
+ }
277
+ else if (ctrlKey && shiftKey) {
278
+ (_b = this.scene) === null || _b === void 0 ? void 0 : _b.zorder('front');
279
+ defaultPrevent = true;
280
+ }
281
+ }
282
+ break;
283
+ case 'BracketLeft':
284
+ // Ctrl+[ = Send Backward, Ctrl+Shift+[ = Send to Back
285
+ if (this._dimension === '2d') {
286
+ if (ctrlKey && !shiftKey) {
287
+ (_c = this.scene) === null || _c === void 0 ? void 0 : _c.zorder('backward');
288
+ defaultPrevent = true;
289
+ }
290
+ else if (ctrlKey && shiftKey) {
291
+ (_d = this.scene) === null || _d === void 0 ? void 0 : _d.zorder('back');
292
+ defaultPrevent = true;
293
+ }
294
+ }
288
295
  break;
289
296
  case 'KeyB':
290
- if (ctrlKey && !shiftKey)
291
- (_c = this.scene) === null || _c === void 0 ? void 0 : _c.zorder('backward');
292
- else if (ctrlKey && shiftKey)
293
- (_d = this.scene) === null || _d === void 0 ? void 0 : _d.zorder('back');
294
- else if (altKey && shiftKey)
297
+ // zorder BracketLeft 로 이동. KeyB 의 Alt+Shift+B = align bottom 만 유지.
298
+ if (altKey && shiftKey)
295
299
  this.onTapAlign('bottom');
296
300
  break;
297
301
  case 'KeyH':
@@ -335,23 +339,38 @@ export class EditToolbar extends LitElement {
335
339
  this.onTapFitScene();
336
340
  defaultPrevent = true;
337
341
  }
342
+ else if (!shiftKey && !altKey) {
343
+ const target = e.composedPath()[0];
344
+ const tagName = target.tagName;
345
+ if (!target.isContentEditable && tagName !== 'INPUT' && tagName !== 'SELECT' && tagName !== 'TEXTAREA') {
346
+ this.onTapDataBinding();
347
+ defaultPrevent = true;
348
+ }
349
+ }
338
350
  break;
339
351
  case 'KeyE':
340
352
  if (ctrlKey && shiftKey)
341
353
  this.onTapDownloadModel();
342
354
  break;
355
+ case 'F10':
356
+ this.onTapEditModel();
357
+ defaultPrevent = true;
358
+ break;
343
359
  case 'Digit1':
344
360
  if (ctrlKey) {
345
- console.log('MODEL', this.scene && this.scene.model);
346
361
  defaultPrevent = true;
347
362
  }
348
363
  break;
349
364
  case 'Digit2':
350
365
  if (ctrlKey) {
351
- console.log('SELECTED', this.scene && this.scene.selected);
352
366
  defaultPrevent = true;
353
367
  }
354
368
  break;
369
+ case 'Escape':
370
+ (_e = this.scene) === null || _e === void 0 ? void 0 : _e.stopStylePasteMode();
371
+ (_f = this.scene) === null || _f === void 0 ? void 0 : _f.stopDatabindPasteMode();
372
+ defaultPrevent = true;
373
+ break;
355
374
  default:
356
375
  return false;
357
376
  }
@@ -359,40 +378,75 @@ export class EditToolbar extends LitElement {
359
378
  e.preventDefault();
360
379
  return true;
361
380
  }
381
+ _setDisabled(id, disabled) {
382
+ const el = this.renderRoot.querySelector(`#${id}`);
383
+ disabled ? el === null || el === void 0 ? void 0 : el.setAttribute('disabled', '') : el === null || el === void 0 ? void 0 : el.removeAttribute('disabled');
384
+ }
362
385
  onExecute(command, undoable, redoable) {
363
- !undoable ? this.undo.setAttribute('disabled', '') : this.undo.removeAttribute('disabled');
364
- !redoable ? this.redo.setAttribute('disabled', '') : this.redo.removeAttribute('disabled');
386
+ this._setDisabled('undo', !undoable);
387
+ this._setDisabled('redo', !redoable);
365
388
  }
366
389
  onUndo(undoable, redoable) {
367
- !undoable ? this.undo.setAttribute('disabled', '') : this.undo.removeAttribute('disabled');
368
- !redoable ? this.redo.setAttribute('disabled', '') : this.redo.removeAttribute('disabled');
390
+ this._setDisabled('undo', !undoable);
391
+ this._setDisabled('redo', !redoable);
369
392
  }
370
393
  onRedo(undoable, redoable) {
371
- !undoable ? this.undo.setAttribute('disabled', '') : this.undo.removeAttribute('disabled');
372
- !redoable ? this.redo.setAttribute('disabled', '') : this.redo.removeAttribute('disabled');
394
+ this._setDisabled('undo', !undoable);
395
+ this._setDisabled('redo', !redoable);
373
396
  }
374
397
  onSceneChanged(after, before) {
398
+ var _a, _b;
375
399
  if (before) {
376
400
  before.off('execute', this.onExecute, this);
377
401
  before.off('undo', this.onUndo, this);
378
402
  before.off('redo', this.onRedo, this);
403
+ before.off('dimension', this._onDimensionChanged, this);
404
+ before.off('gizmoattach', this._onGizmoAttachChanged, this);
379
405
  }
380
406
  if (after) {
381
407
  after.on('execute', this.onExecute, this);
382
408
  after.on('undo', this.onUndo, this);
383
409
  after.on('redo', this.onRedo, this);
410
+ after.on('dimension', this._onDimensionChanged, this);
411
+ after.on('gizmoattach', this._onGizmoAttachChanged, this);
412
+ // scene 설정 시 현재 dimension을 즉시 반영 (초기 이벤트를 놓친 경우 대비)
413
+ const threed = (_b = (_a = after.model_layer) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.threed;
414
+ this._dimension = threed ? '3d' : '2d';
384
415
  }
385
416
  }
417
+ _onDimensionChanged(dimension) {
418
+ this._dimension = dimension;
419
+ }
420
+ _onGizmoAttachChanged(isAttached) {
421
+ this._gizmoAttached = isAttached;
422
+ }
423
+ _setGizmoMode(mode) {
424
+ var _a, _b;
425
+ ;
426
+ (_b = (_a = this.scene) === null || _a === void 0 ? void 0 : _a.root) === null || _b === void 0 ? void 0 : _b.setGizmoMode(mode);
427
+ }
386
428
  onSelectedChanged(after, before) {
429
+ var hasSelection = after.length > 0;
387
430
  var alignable = after.length > 1;
388
- this.aligners.forEach(aligner => alignable ? aligner.removeAttribute('disabled') : aligner.setAttribute('disabled', ''));
389
- var movable = after.length === 1;
390
- /* forward, backward 이동은 컴포넌트만 가능하다. */
391
- !movable ? this.forward.setAttribute('disabled', '') : this.forward.removeAttribute('disabled');
392
- !movable ? this.backward.setAttribute('disabled', '') : this.backward.removeAttribute('disabled');
393
- /* 여러 컴포넌트는 front, back 이동이 가능하다. */
394
- !(alignable || movable) ? this.front.setAttribute('disabled', '') : this.front.removeAttribute('disabled');
395
- !(alignable || movable) ? this.back.setAttribute('disabled', '') : this.back.removeAttribute('disabled');
431
+ ['style-copy', 'databind-copy', 'cut', 'copy', 'delete'].forEach(id => this._setDisabled(id, !hasSelection));
432
+ // 정렬 버튼
433
+ this.renderRoot.querySelectorAll('[data-align]').forEach(el => alignable ? el.removeAttribute('disabled') : el.setAttribute('disabled', ''));
434
+ if (this._dimension === '2d') {
435
+ var movable = after.length === 1;
436
+ /* forward, backward 이동은 컴포넌트만 가능하다. */
437
+ this._setDisabled('forward', !movable);
438
+ this._setDisabled('backward', !movable);
439
+ /* 여러 컴포넌트는 front, back 이동이 가능하다. */
440
+ this._setDisabled('front', !(alignable || movable));
441
+ this._setDisabled('back', !(alignable || movable));
442
+ }
443
+ // 분배 버튼
444
+ this.renderRoot.querySelectorAll('[data-distribute]').forEach(el => alignable ? el.removeAttribute('disabled') : el.setAttribute('disabled', ''));
445
+ if (this._dimension === '3d') {
446
+ this.renderRoot.querySelectorAll('.gizmo-btn').forEach(btn => {
447
+ hasSelection ? btn.removeAttribute('disabled') : btn.setAttribute('disabled', '');
448
+ });
449
+ }
396
450
  }
397
451
  onTapUndo() {
398
452
  var _a;
@@ -403,12 +457,20 @@ export class EditToolbar extends LitElement {
403
457
  (_a = this.scene) === null || _a === void 0 ? void 0 : _a.redo();
404
458
  }
405
459
  onStartStylePasteMode() {
406
- var _a;
407
- (_a = this.scene) === null || _a === void 0 ? void 0 : _a.startStylePasteMode();
460
+ var _a, _b, _c;
461
+ if (((_a = this.selected) === null || _a === void 0 ? void 0 : _a.length) !== 1)
462
+ return;
463
+ if (this.selected[0] === ((_b = this.scene) === null || _b === void 0 ? void 0 : _b.root))
464
+ return;
465
+ (_c = this.scene) === null || _c === void 0 ? void 0 : _c.startStylePasteMode();
408
466
  }
409
467
  onStartDatabindPasteMode() {
410
- var _a;
411
- (_a = this.scene) === null || _a === void 0 ? void 0 : _a.startDatabindPasteMode();
468
+ var _a, _b, _c;
469
+ if (((_a = this.selected) === null || _a === void 0 ? void 0 : _a.length) !== 1)
470
+ return;
471
+ if (this.selected[0] === ((_b = this.scene) === null || _b === void 0 ? void 0 : _b.root))
472
+ return;
473
+ (_c = this.scene) === null || _c === void 0 ? void 0 : _c.startDatabindPasteMode();
412
474
  }
413
475
  onTapCut() {
414
476
  var _a;
@@ -432,6 +494,84 @@ export class EditToolbar extends LitElement {
432
494
  var _a;
433
495
  (_a = this.scene) === null || _a === void 0 ? void 0 : _a.remove();
434
496
  }
497
+ /**
498
+ * 멀티 컴포넌트 일괄 붙여넣기 — Ctrl+Shift+V 진입점.
499
+ *
500
+ * 흐름:
501
+ * 1. 클립보드 (this.cliped) 검증 — *단일* 컴포넌트만 허용
502
+ * 2. scene.startBulkCreateMode → AddLayer 가 캔버스 위에 드래그 박스 그림
503
+ * 3. 사용자가 영역 드래그 → Promise resolve({area, template})
504
+ * ESC/취소 → Promise resolve(null)
505
+ * 4. 영역이 결정되면 dialog 오픈
506
+ */
507
+ async onBulkPaste() {
508
+ var _a;
509
+ if (!this.cliped) {
510
+ console.warn('bulk-paste: 먼저 Ctrl+C 로 컴포넌트를 복사하세요.');
511
+ return;
512
+ }
513
+ let parsed;
514
+ try {
515
+ parsed = JSON.parse(this.cliped);
516
+ }
517
+ catch (e) {
518
+ console.warn('bulk-paste: 클립보드 파싱 실패', e);
519
+ return;
520
+ }
521
+ if (!Array.isArray(parsed) || parsed.length === 0)
522
+ return;
523
+ if (parsed.length > 1) {
524
+ console.warn('bulk-paste: 단일 컴포넌트만 일괄 붙여넣기 가능. 다중은 일반 paste 사용.');
525
+ return;
526
+ }
527
+ const template = parsed[0];
528
+ // bulk 모드 임을 시각 구분하기 위해 파란 점선 (일반 add 의 검정과 색만 차별).
529
+ const result = await ((_a = this.scene) === null || _a === void 0 ? void 0 : _a.startBulkCreateMode(template, { boxStrokeStyle: '#2964c2' }));
530
+ if (result) {
531
+ this._openBulkCreateDialog(template, result.area);
532
+ }
533
+ }
534
+ _openBulkCreateDialog(template, initialArea) {
535
+ var _a;
536
+ const dialogEl = document.createElement('bulk-create-dialog');
537
+ dialogEl.template = template;
538
+ dialogEl.initialArea = initialArea;
539
+ // scene 의 모든 id 수집 — indexMap 기반 scene.ids 대신 트리 walk 로 안정성 확보.
540
+ const existingIds = new Set();
541
+ const collectIds = (comp) => {
542
+ var _a, _b, _c;
543
+ if (!comp)
544
+ return;
545
+ 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;
546
+ if (typeof id === 'string' && id)
547
+ existingIds.add(id);
548
+ const children = comp.components || [];
549
+ for (const c of children)
550
+ collectIds(c);
551
+ };
552
+ const root = (_a = this.scene) === null || _a === void 0 ? void 0 : _a.root;
553
+ if (root)
554
+ collectIds(root);
555
+ dialogEl.existingIds = existingIds;
556
+ dialogEl.onConfirm = (models) => {
557
+ const scene = this.scene;
558
+ if (!scene || !models.length) {
559
+ popup === null || popup === void 0 ? void 0 : popup.close();
560
+ return;
561
+ }
562
+ scene.undoableChange(() => {
563
+ scene.add(models, {});
564
+ });
565
+ popup === null || popup === void 0 ? void 0 : popup.close();
566
+ };
567
+ dialogEl.onCancel = () => {
568
+ popup === null || popup === void 0 ? void 0 : popup.close();
569
+ };
570
+ const popup = OxPopup.open({
571
+ template: dialogEl,
572
+ backdrop: true
573
+ });
574
+ }
435
575
  onTapSelectAll() {
436
576
  var _a;
437
577
  (_a = this.scene) === null || _a === void 0 ? void 0 : _a.select('(child)');
@@ -523,9 +663,15 @@ export class EditToolbar extends LitElement {
523
663
  onTapPreview() {
524
664
  this.dispatchEvent(new CustomEvent('open-preview'));
525
665
  }
666
+ onTapDataBinding() {
667
+ this.dispatchEvent(new CustomEvent('open-data-binding'));
668
+ }
526
669
  onTapDownloadModel() {
527
670
  this.dispatchEvent(new CustomEvent('download-model'));
528
671
  }
672
+ onTapEditModel() {
673
+ this.dispatchEvent(new CustomEvent('edit-model'));
674
+ }
529
675
  onTapDistribute(e) {
530
676
  if (!this.scene)
531
677
  return;
@@ -548,51 +694,9 @@ __decorate([
548
694
  property({ type: Boolean })
549
695
  ], EditToolbar.prototype, "hideProperty", void 0);
550
696
  __decorate([
551
- query('#redo')
552
- ], EditToolbar.prototype, "redo", void 0);
553
- __decorate([
554
- query('#undo')
555
- ], EditToolbar.prototype, "undo", void 0);
556
- __decorate([
557
- query('#fullscreen')
558
- ], EditToolbar.prototype, "fullscreen", void 0);
559
- __decorate([
560
- query('#style-copy')
561
- ], EditToolbar.prototype, "styleCopy", void 0);
562
- __decorate([
563
- query('#databind-copy')
564
- ], EditToolbar.prototype, "databindCopy", void 0);
565
- __decorate([
566
- query('#cut')
567
- ], EditToolbar.prototype, "cut", void 0);
568
- __decorate([
569
- query('#copy')
570
- ], EditToolbar.prototype, "copy", void 0);
571
- __decorate([
572
- query('#paste')
573
- ], EditToolbar.prototype, "paste", void 0);
574
- __decorate([
575
- query('#delete')
576
- ], EditToolbar.prototype, "delete", void 0);
577
- __decorate([
578
- query('#forward')
579
- ], EditToolbar.prototype, "forward", void 0);
580
- __decorate([
581
- query('#backward')
582
- ], EditToolbar.prototype, "backward", void 0);
583
- __decorate([
584
- query('#front')
585
- ], EditToolbar.prototype, "front", void 0);
586
- __decorate([
587
- query('#back')
588
- ], EditToolbar.prototype, "back", void 0);
589
- __decorate([
590
- queryAll('[data-align]')
591
- ], EditToolbar.prototype, "aligners", void 0);
592
- __decorate([
593
- queryAll('[data-zorder]')
594
- ], EditToolbar.prototype, "zorders", void 0);
697
+ state()
698
+ ], EditToolbar.prototype, "_dimension", void 0);
595
699
  __decorate([
596
- queryAll('[data-distribute]')
597
- ], EditToolbar.prototype, "distributes", void 0);
700
+ state()
701
+ ], EditToolbar.prototype, "_gizmoAttached", void 0);
598
702
  //# sourceMappingURL=edit-toolbar.js.map