@operato/scene-visualizer 10.0.0-beta.4 → 10.0.0-beta.8

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 (49) hide show
  1. package/dist/editors/index.d.ts +1 -0
  2. package/dist/editors/index.js +5 -0
  3. package/dist/editors/index.js.map +1 -1
  4. package/dist/editors/property-editor-gltf-fill-targets.d.ts +4 -1
  5. package/dist/editors/property-editor-gltf-fill-targets.js +178 -74
  6. package/dist/editors/property-editor-gltf-fill-targets.js.map +1 -1
  7. package/dist/editors/property-editor-gltf-info.d.ts +19 -4
  8. package/dist/editors/property-editor-gltf-info.js +279 -84
  9. package/dist/editors/property-editor-gltf-info.js.map +1 -1
  10. package/dist/editors/property-editor-gltf-play-targets.d.ts +25 -0
  11. package/dist/editors/property-editor-gltf-play-targets.js +388 -0
  12. package/dist/editors/property-editor-gltf-play-targets.js.map +1 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +1 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/rack-table-3d.js +1 -0
  17. package/dist/rack-table-3d.js.map +1 -1
  18. package/dist/rack-table.js +13 -0
  19. package/dist/rack-table.js.map +1 -1
  20. package/dist/rack.d.ts +6 -0
  21. package/dist/rack.js +10 -1
  22. package/dist/rack.js.map +1 -1
  23. package/dist/stock-hub.d.ts +25 -0
  24. package/dist/stock-hub.js +147 -0
  25. package/dist/stock-hub.js.map +1 -0
  26. package/dist/stock.d.ts +2 -0
  27. package/dist/stock.js +22 -33
  28. package/dist/stock.js.map +1 -1
  29. package/dist/templates/index.d.ts +0 -41
  30. package/dist/templates/index.js +2 -0
  31. package/dist/templates/index.js.map +1 -1
  32. package/dist/templates/rack-table.d.ts +2 -0
  33. package/dist/templates/rack-table.js +4 -2
  34. package/dist/templates/rack-table.js.map +1 -1
  35. package/dist/templates/stock-hub.d.ts +14 -0
  36. package/dist/templates/stock-hub.js +15 -0
  37. package/dist/templates/stock-hub.js.map +1 -0
  38. package/dist/templates/visualizer.js +1 -1
  39. package/dist/templates/visualizer.js.map +1 -1
  40. package/dist/visualizer.d.ts +2 -1
  41. package/dist/visualizer.js +47 -48
  42. package/dist/visualizer.js.map +1 -1
  43. package/icons/stock-hub.png +0 -0
  44. package/package.json +2 -2
  45. package/translations/en.json +6 -0
  46. package/translations/ja.json +5 -0
  47. package/translations/ko.json +6 -1
  48. package/translations/ms.json +5 -0
  49. package/translations/zh.json +5 -0
@@ -0,0 +1,388 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * GLTF 애니메이션 타겟 선택 에디터.
5
+ * All / None / Custom 모드로 started에 바인딩할 애니메이션 선택.
6
+ * 각 애니메이션을 개별 미리보기(play/stop) 가능.
7
+ */
8
+ import { __decorate } from "tslib";
9
+ import '@material/web/icon/icon.js';
10
+ import '@operato/i18n/ox-i18n.js';
11
+ import { css, html, nothing } from 'lit';
12
+ import { customElement, property, state } from 'lit/decorators.js';
13
+ import { OxPropertyEditor } from '@operato/property-editor';
14
+ import { ScrollbarStyles } from '@operato/styles/scrollbar-styles.js';
15
+ function getMode(value) {
16
+ if (value === '*')
17
+ return 'all';
18
+ if (value === 'none')
19
+ return 'none';
20
+ if (Array.isArray(value) && value.length > 0)
21
+ return 'custom';
22
+ if (Array.isArray(value))
23
+ return 'custom'; // empty array = custom with nothing
24
+ if (!value)
25
+ return 'all'; // undefined = default = all
26
+ return 'all';
27
+ }
28
+ let GLTFPlayTargetsEditor = class GLTFPlayTargetsEditor extends OxPropertyEditor {
29
+ static styles = [
30
+ ...OxPropertyEditor.styles,
31
+ ScrollbarStyles,
32
+ css `
33
+ .mode-selector {
34
+ display: flex;
35
+ gap: 0;
36
+ margin-bottom: 6px;
37
+ border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.15));
38
+ border-radius: 6px;
39
+ overflow: hidden;
40
+ }
41
+
42
+ .mode-btn {
43
+ flex: 1;
44
+ padding: 5px 0;
45
+ font-size: 11px;
46
+ font-weight: 500;
47
+ text-align: center;
48
+ cursor: pointer;
49
+ background: var(--md-sys-color-surface-container, #f3edf7);
50
+ color: var(--md-sys-color-on-surface, #1c1b1f);
51
+ border: none;
52
+ border-right: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.1));
53
+ transition: all 0.15s;
54
+ }
55
+
56
+ .mode-btn:last-child {
57
+ border-right: none;
58
+ }
59
+
60
+ .mode-btn:hover {
61
+ background: var(--md-sys-color-surface-container-highest, #e6e0e9);
62
+ }
63
+
64
+ .mode-btn[active] {
65
+ background: var(--md-sys-color-primary, #6750a4);
66
+ color: var(--md-sys-color-on-primary, #fff);
67
+ }
68
+
69
+ .anim-list {
70
+ display: flex;
71
+ flex-direction: column;
72
+ gap: 1px;
73
+ max-height: 200px;
74
+ overflow-y: auto;
75
+ font-size: 12px;
76
+ }
77
+
78
+ .anim-item {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 6px;
82
+ padding: 4px 8px;
83
+ border-radius: 4px;
84
+ transition: background 0.1s;
85
+ }
86
+
87
+ .anim-item:hover {
88
+ background: var(--md-sys-color-primary-container, rgba(103, 80, 164, 0.12));
89
+ }
90
+
91
+ .anim-item input[type='checkbox'] {
92
+ margin: 0;
93
+ accent-color: var(--md-sys-color-primary, #6750a4);
94
+ }
95
+
96
+ .anim-item span {
97
+ flex: 1;
98
+ font-size: 12px;
99
+ color: var(--md-sys-color-on-surface, #1c1b1f);
100
+ }
101
+
102
+ .anim-item md-icon {
103
+ --md-icon-size: 18px;
104
+ cursor: pointer;
105
+ color: var(--md-sys-color-on-surface-variant, #666);
106
+ transition: color 0.15s;
107
+ }
108
+
109
+ .anim-item md-icon:hover {
110
+ color: var(--md-sys-color-primary, #6750a4);
111
+ }
112
+
113
+ .anim-item md-icon[playing] {
114
+ color: var(--md-sys-color-primary, #6750a4);
115
+ }
116
+
117
+ .mode-desc {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 6px;
121
+ font-size: 11px;
122
+ color: var(--md-sys-color-on-surface-variant, #666);
123
+ padding: 4px 0;
124
+ }
125
+
126
+ .mini-btn {
127
+ font-size: 10px;
128
+ padding: 2px 8px;
129
+ border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.15));
130
+ border-radius: 4px;
131
+ background: var(--md-sys-color-surface-container, #f3edf7);
132
+ color: var(--md-sys-color-on-surface, #1c1b1f);
133
+ cursor: pointer;
134
+ }
135
+
136
+ .mini-btn:hover {
137
+ background: var(--md-sys-color-surface-container-highest, #e6e0e9);
138
+ }
139
+
140
+ .play-toggle {
141
+ display: flex;
142
+ align-items: center;
143
+ gap: 6px;
144
+ padding: 4px 0;
145
+ margin-bottom: 4px;
146
+ cursor: pointer;
147
+ font-size: 12px;
148
+ font-weight: 500;
149
+ color: var(--md-sys-color-on-surface, #1c1b1f);
150
+ }
151
+
152
+ .play-toggle input[type='checkbox'] {
153
+ accent-color: var(--md-sys-color-primary, #6750a4);
154
+ }
155
+
156
+ .empty {
157
+ color: var(--md-sys-color-on-surface-variant, #999);
158
+ font-size: 11px;
159
+ padding: 8px;
160
+ text-align: center;
161
+ }
162
+ `
163
+ ];
164
+ _component = null;
165
+ _lastExternalValue = undefined;
166
+ updated(changes) {
167
+ if (changes.has('src')) {
168
+ this.dispatchEvent(new CustomEvent('i-need-selected', {
169
+ bubbles: true,
170
+ composed: true,
171
+ detail: {
172
+ callback: (selected) => {
173
+ this._component = selected?.[0];
174
+ this._refreshAnimNames();
175
+ }
176
+ }
177
+ }));
178
+ }
179
+ }
180
+ _refreshAnimNames() {
181
+ if (!this._component) {
182
+ this._animNames = [];
183
+ return;
184
+ }
185
+ this._animNames = this._component.animationNames ?? [];
186
+ }
187
+ _pollTimer;
188
+ connectedCallback() {
189
+ super.connectedCallback();
190
+ // GLTF 비동기 로드 후 애니메이션 목록 갱신을 위한 폴링 (최대 20회 = 20초)
191
+ let retries = 0;
192
+ this._pollTimer = window.setInterval(() => {
193
+ if (!this._animNames?.length) {
194
+ this._refreshAnimNames();
195
+ }
196
+ if (this._animNames?.length || ++retries >= 20) {
197
+ clearInterval(this._pollTimer);
198
+ this._pollTimer = undefined;
199
+ }
200
+ }, 1000);
201
+ }
202
+ disconnectedCallback() {
203
+ super.disconnectedCallback();
204
+ if (this._pollTimer) {
205
+ clearInterval(this._pollTimer);
206
+ }
207
+ }
208
+ editorTemplate(value, _spec) {
209
+ if (!this._animNames?.length) {
210
+ this._refreshAnimNames();
211
+ }
212
+ if (value !== this._lastExternalValue) {
213
+ this._lastExternalValue = value;
214
+ this._mode = getMode(value);
215
+ }
216
+ const mode = this._mode;
217
+ const animCount = this._animNames?.length ?? 0;
218
+ const targets = Array.isArray(this.value) ? this.value : (Array.isArray(value) ? value : []);
219
+ const isPlaying = this._getPlayState();
220
+ return html `
221
+ <fieldset fullwidth>
222
+ ${animCount > 0
223
+ ? html `
224
+ <label class="play-toggle">
225
+ <input
226
+ type="checkbox"
227
+ .checked=${isPlaying}
228
+ @change=${(e) => {
229
+ e.stopPropagation();
230
+ this._setPlay(e.target.checked);
231
+ }}
232
+ />
233
+ <span>Play</span>
234
+ </label>
235
+
236
+ <div class="mode-selector">
237
+ <div class="mode-btn" ?active=${mode === 'all'} @click=${() => this._setMode('all')}>All</div>
238
+ <div class="mode-btn" ?active=${mode === 'none'} @click=${() => this._setMode('none')}>None</div>
239
+ <div class="mode-btn" ?active=${mode === 'custom'} @click=${() => this._setMode('custom')}>Custom</div>
240
+ </div>
241
+
242
+ ${mode === 'all'
243
+ ? html `<div class="mode-desc">${animCount} animations — play all</div>`
244
+ : nothing}
245
+ ${mode === 'none'
246
+ ? html `<div class="mode-desc">No animations auto-play</div>`
247
+ : nothing}
248
+ ${mode === 'custom'
249
+ ? html `
250
+ <div class="mode-desc">
251
+ <span style="flex:1">${targets.length} / ${animCount} selected</span>
252
+ <button class="mini-btn" @click=${() => this._applyValue([...(this._animNames ?? [])])}>All</button>
253
+ <button class="mini-btn" @click=${() => this._applyValue([])}>Clear</button>
254
+ </div>
255
+ `
256
+ : nothing}
257
+
258
+ <div class="anim-list">
259
+ ${(this._animNames ?? []).map(name => html `
260
+ <div class="anim-item">
261
+ ${mode === 'custom'
262
+ ? html `
263
+ <input
264
+ type="checkbox"
265
+ .checked=${targets.includes(name)}
266
+ @change=${(e) => {
267
+ e.stopPropagation();
268
+ this._onToggle(name, e.target.checked, targets);
269
+ }}
270
+ />
271
+ `
272
+ : nothing}
273
+ <span>${name}</span>
274
+ <md-icon
275
+ ?playing=${this._playingAnim === name}
276
+ @click=${() => this._togglePreview(name)}
277
+ title="preview"
278
+ >${this._playingAnim === name ? 'stop' : 'play_arrow'}</md-icon>
279
+ </div>
280
+ `)}
281
+ </div>
282
+ `
283
+ : html `<div class="empty">No animations available</div>`}
284
+ </fieldset>
285
+ `;
286
+ }
287
+ _setMode(mode) {
288
+ const prevMode = this._mode;
289
+ this._mode = mode;
290
+ let newValue;
291
+ switch (mode) {
292
+ case 'all':
293
+ newValue = '*';
294
+ break;
295
+ case 'none':
296
+ newValue = 'none';
297
+ break;
298
+ case 'custom':
299
+ if (prevMode === 'all') {
300
+ newValue = [...(this._animNames ?? [])];
301
+ }
302
+ else if (prevMode === 'none') {
303
+ newValue = [];
304
+ }
305
+ else {
306
+ newValue = Array.isArray(this.value) ? this.value : [];
307
+ }
308
+ break;
309
+ }
310
+ this._applyValue(newValue);
311
+ }
312
+ _onToggle(name, checked, current) {
313
+ const list = [...current];
314
+ if (checked) {
315
+ if (!list.includes(name))
316
+ list.push(name);
317
+ }
318
+ else {
319
+ const idx = list.indexOf(name);
320
+ if (idx >= 0)
321
+ list.splice(idx, 1);
322
+ }
323
+ this._applyValue(list);
324
+ }
325
+ _applyValue(newValue) {
326
+ this.value = newValue;
327
+ this.requestUpdate();
328
+ this.dispatchEvent(new CustomEvent('i-need-selected', {
329
+ bubbles: true,
330
+ composed: true,
331
+ detail: {
332
+ callback: (selected) => {
333
+ selected[0].set('playTargets', newValue);
334
+ }
335
+ }
336
+ }));
337
+ }
338
+ _getPlayState() {
339
+ return this._component?.state?.play !== false;
340
+ }
341
+ _setPlay(value) {
342
+ if (!this._component)
343
+ return;
344
+ this._component.set('play', value);
345
+ this.requestUpdate();
346
+ }
347
+ _togglePreview(name) {
348
+ if (!this._component)
349
+ return;
350
+ const ro = this._component.realObject;
351
+ if (!ro?._animationActions)
352
+ return;
353
+ const action = ro._animationActions.get(name);
354
+ if (!action)
355
+ return;
356
+ if (this._playingAnim === name) {
357
+ action.stop();
358
+ this._playingAnim = null;
359
+ }
360
+ else {
361
+ // 이전 프리뷰 중지
362
+ if (this._playingAnim) {
363
+ ro._animationActions.get(this._playingAnim)?.stop();
364
+ }
365
+ action.play();
366
+ this._playingAnim = name;
367
+ }
368
+ this._component.invalidate();
369
+ this.requestUpdate();
370
+ }
371
+ };
372
+ __decorate([
373
+ property({ type: String })
374
+ ], GLTFPlayTargetsEditor.prototype, "src", void 0);
375
+ __decorate([
376
+ state()
377
+ ], GLTFPlayTargetsEditor.prototype, "_animNames", void 0);
378
+ __decorate([
379
+ state()
380
+ ], GLTFPlayTargetsEditor.prototype, "_mode", void 0);
381
+ __decorate([
382
+ state()
383
+ ], GLTFPlayTargetsEditor.prototype, "_playingAnim", void 0);
384
+ GLTFPlayTargetsEditor = __decorate([
385
+ customElement('property-editor-gltf-play-targets')
386
+ ], GLTFPlayTargetsEditor);
387
+ export default GLTFPlayTargetsEditor;
388
+ //# sourceMappingURL=property-editor-gltf-play-targets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"property-editor-gltf-play-targets.js","sourceRoot":"","sources":["../../src/editors/property-editor-gltf-play-targets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;;AAEH,OAAO,4BAA4B,CAAA;AACnC,OAAO,0BAA0B,CAAA;AAEjC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAkC,OAAO,EAAE,MAAM,KAAK,CAAA;AACxE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,gBAAgB,EAAgB,MAAM,0BAA0B,CAAA;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAIrE,SAAS,OAAO,CAAC,KAAU;IACzB,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,KAAK,CAAA;IAC/B,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,MAAM,CAAA;IACnC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAA;IAC7D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAA,CAAC,oCAAoC;IAC9E,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA,CAAC,4BAA4B;IACrD,OAAO,KAAK,CAAA;AACd,CAAC;AAGc,IAAM,qBAAqB,GAA3B,MAAM,qBAAsB,SAAQ,gBAAgB;IACjE,MAAM,CAAC,MAAM,GAAG;QACd,GAAG,gBAAgB,CAAC,MAAM;QAC1B,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkIF;KACF,CAAA;IAQO,UAAU,GAAQ,IAAI,CAAA;IACtB,kBAAkB,GAAQ,SAAS,CAAA;IAE3C,OAAO,CAAC,OAA6B;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,iBAAiB,EAAE;gBACjC,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,IAAI;gBACd,MAAM,EAAE;oBACN,QAAQ,EAAE,CAAC,QAAe,EAAE,EAAE;wBAC5B,IAAI,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;wBAC/B,IAAI,CAAC,iBAAiB,EAAE,CAAA;oBAC1B,CAAC;iBACF;aACF,CAAC,CACH,CAAA;QACH,CAAC;IACH,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAA;YACpB,OAAM;QACR,CAAC;QAED,IAAI,CAAC,UAAU,GAAI,IAAI,CAAC,UAAU,CAAC,cAA2B,IAAI,EAAE,CAAA;IACtE,CAAC;IAEO,UAAU,CAAS;IAE3B,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAA;QACzB,kDAAkD;QAClD,IAAI,OAAO,GAAG,CAAC,CAAA;QACf,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;YACxC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;gBAC7B,IAAI,CAAC,iBAAiB,EAAE,CAAA;YAC1B,CAAC;YACD,IAAI,IAAI,CAAC,UAAU,EAAE,MAAM,IAAI,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC;gBAC/C,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;gBAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;YAC7B,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,CAAA;IACV,CAAC;IAED,oBAAoB;QAClB,KAAK,CAAC,oBAAoB,EAAE,CAAA;QAC5B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAChC,CAAC;IACH,CAAC;IAED,cAAc,CAAC,KAAU,EAAE,KAAmB;QAC5C,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;YAC7B,IAAI,CAAC,iBAAiB,EAAE,CAAA;QAC1B,CAAC;QAED,IAAI,KAAK,KAAK,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACtC,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAA;YAC/B,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;QAC7B,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAA;QACvB,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,CAAA;QAC9C,MAAM,OAAO,GAAa,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAEtG,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAA;QAEtC,OAAO,IAAI,CAAA;;UAEL,SAAS,GAAG,CAAC;YACb,CAAC,CAAC,IAAI,CAAA;;;;6BAIa,SAAS;4BACV,CAAC,CAAQ,EAAE,EAAE;gBACrB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,IAAI,CAAC,QAAQ,CAAE,CAAC,CAAC,MAA2B,CAAC,OAAO,CAAC,CAAA;YACvD,CAAC;;;;;;gDAM6B,IAAI,KAAK,KAAK,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gDACnD,IAAI,KAAK,MAAM,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gDACrD,IAAI,KAAK,QAAQ,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;;;gBAGzF,IAAI,KAAK,KAAK;gBACd,CAAC,CAAC,IAAI,CAAA,0BAA0B,SAAS,8BAA8B;gBACvE,CAAC,CAAC,OAAO;gBACT,IAAI,KAAK,MAAM;gBACf,CAAC,CAAC,IAAI,CAAA,sDAAsD;gBAC5D,CAAC,CAAC,OAAO;gBACT,IAAI,KAAK,QAAQ;gBACjB,CAAC,CAAC,IAAI,CAAA;;6CAEuB,OAAO,CAAC,MAAM,MAAM,SAAS;wDAClB,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC;wDACpD,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;;mBAE/D;gBACH,CAAC,CAAC,OAAO;;;kBAGP,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAC3B,IAAI,CAAC,EAAE,CAAC,IAAI,CAAA;;wBAEN,IAAI,KAAK,QAAQ;gBACjB,CAAC,CAAC,IAAI,CAAA;;;yCAGW,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;wCACvB,CAAC,CAAQ,EAAE,EAAE;oBACrB,CAAC,CAAC,eAAe,EAAE,CAAA;oBACnB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAG,CAAC,CAAC,MAA2B,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;gBACvE,CAAC;;2BAEJ;gBACH,CAAC,CAAC,OAAO;8BACH,IAAI;;mCAEC,IAAI,CAAC,YAAY,KAAK,IAAI;iCAC5B,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC;;yBAEvC,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY;;mBAExD,CACF;;aAEJ;YACH,CAAC,CAAC,IAAI,CAAA,kDAAkD;;KAE7D,CAAA;IACH,CAAC;IAEO,QAAQ,CAAC,IAAc;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAA;QAC3B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QAEjB,IAAI,QAAa,CAAA;QACjB,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,KAAK;gBACR,QAAQ,GAAG,GAAG,CAAA;gBACd,MAAK;YACP,KAAK,MAAM;gBACT,QAAQ,GAAG,MAAM,CAAA;gBACjB,MAAK;YACP,KAAK,QAAQ;gBACX,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;oBACvB,QAAQ,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAA;gBACzC,CAAC;qBAAM,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;oBAC/B,QAAQ,GAAG,EAAE,CAAA;gBACf,CAAC;qBAAM,CAAC;oBACN,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;gBACxD,CAAC;gBACD,MAAK;QACT,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;IAC5B,CAAC;IAEO,SAAS,CAAC,IAAY,EAAE,OAAgB,EAAE,OAAiB;QACjE,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,CAAA;QACzB,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3C,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;YAC9B,IAAI,GAAG,IAAI,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACnC,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IACxB,CAAC;IAEO,WAAW,CAAC,QAAa;QAC/B,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAA;QACrB,IAAI,CAAC,aAAa,EAAE,CAAA;QAEpB,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,iBAAiB,EAAE;YACjC,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE;gBACN,QAAQ,EAAE,CAAC,QAAe,EAAE,EAAE;oBAC5B,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAA;gBAC1C,CAAC;aACF;SACF,CAAC,CACH,CAAA;IACH,CAAC;IAEO,aAAa;QACnB,OAAO,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,KAAK,KAAK,CAAA;IAC/C,CAAC;IAEO,QAAQ,CAAC,KAAc;QAC7B,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAM;QAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QAClC,IAAI,CAAC,aAAa,EAAE,CAAA;IACtB,CAAC;IAEO,cAAc,CAAC,IAAY;QACjC,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAM;QAE5B,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,UAAiB,CAAA;QAC5C,IAAI,CAAC,EAAE,EAAE,iBAAiB;YAAE,OAAM;QAElC,MAAM,MAAM,GAAG,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,CAAC,MAAM;YAAE,OAAM;QAEnB,IAAI,IAAI,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,EAAE,CAAA;YACb,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;QAC1B,CAAC;aAAM,CAAC;YACN,YAAY;YACZ,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACtB,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAA;YACrD,CAAC;YACD,MAAM,CAAC,IAAI,EAAE,CAAA;YACb,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;QAC1B,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAA;QAC5B,IAAI,CAAC,aAAa,EAAE,CAAA;IACtB,CAAC;;AAxOmC;IAAnC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;kDAAgC;AAElC;IAAxB,KAAK,EAAE;yDAAqC;AACpB;IAAxB,KAAK,EAAE;oDAAgC;AACf;IAAxB,KAAK,EAAE;2DAA4C;AA7IjC,qBAAqB;IADzC,aAAa,CAAC,mCAAmC,CAAC;GAC9B,qBAAqB,CAkXzC;eAlXoB,qBAAqB","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * GLTF 애니메이션 타겟 선택 에디터.\n * All / None / Custom 모드로 started에 바인딩할 애니메이션 선택.\n * 각 애니메이션을 개별 미리보기(play/stop) 가능.\n */\n\nimport '@material/web/icon/icon.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, PropertyValues, TemplateResult, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { OxPropertyEditor, PropertySpec } from '@operato/property-editor'\nimport { ScrollbarStyles } from '@operato/styles/scrollbar-styles.js'\n\ntype AnimMode = 'all' | 'none' | 'custom'\n\nfunction getMode(value: any): AnimMode {\n if (value === '*') return 'all'\n if (value === 'none') return 'none'\n if (Array.isArray(value) && value.length > 0) return 'custom'\n if (Array.isArray(value)) return 'custom' // empty array = custom with nothing\n if (!value) return 'all' // undefined = default = all\n return 'all'\n}\n\n@customElement('property-editor-gltf-play-targets')\nexport default class GLTFPlayTargetsEditor extends OxPropertyEditor {\n static styles = [\n ...OxPropertyEditor.styles,\n ScrollbarStyles,\n css`\n .mode-selector {\n display: flex;\n gap: 0;\n margin-bottom: 6px;\n border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.15));\n border-radius: 6px;\n overflow: hidden;\n }\n\n .mode-btn {\n flex: 1;\n padding: 5px 0;\n font-size: 11px;\n font-weight: 500;\n text-align: center;\n cursor: pointer;\n background: var(--md-sys-color-surface-container, #f3edf7);\n color: var(--md-sys-color-on-surface, #1c1b1f);\n border: none;\n border-right: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.1));\n transition: all 0.15s;\n }\n\n .mode-btn:last-child {\n border-right: none;\n }\n\n .mode-btn:hover {\n background: var(--md-sys-color-surface-container-highest, #e6e0e9);\n }\n\n .mode-btn[active] {\n background: var(--md-sys-color-primary, #6750a4);\n color: var(--md-sys-color-on-primary, #fff);\n }\n\n .anim-list {\n display: flex;\n flex-direction: column;\n gap: 1px;\n max-height: 200px;\n overflow-y: auto;\n font-size: 12px;\n }\n\n .anim-item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n border-radius: 4px;\n transition: background 0.1s;\n }\n\n .anim-item:hover {\n background: var(--md-sys-color-primary-container, rgba(103, 80, 164, 0.12));\n }\n\n .anim-item input[type='checkbox'] {\n margin: 0;\n accent-color: var(--md-sys-color-primary, #6750a4);\n }\n\n .anim-item span {\n flex: 1;\n font-size: 12px;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n }\n\n .anim-item md-icon {\n --md-icon-size: 18px;\n cursor: pointer;\n color: var(--md-sys-color-on-surface-variant, #666);\n transition: color 0.15s;\n }\n\n .anim-item md-icon:hover {\n color: var(--md-sys-color-primary, #6750a4);\n }\n\n .anim-item md-icon[playing] {\n color: var(--md-sys-color-primary, #6750a4);\n }\n\n .mode-desc {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--md-sys-color-on-surface-variant, #666);\n padding: 4px 0;\n }\n\n .mini-btn {\n font-size: 10px;\n padding: 2px 8px;\n border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.15));\n border-radius: 4px;\n background: var(--md-sys-color-surface-container, #f3edf7);\n color: var(--md-sys-color-on-surface, #1c1b1f);\n cursor: pointer;\n }\n\n .mini-btn:hover {\n background: var(--md-sys-color-surface-container-highest, #e6e0e9);\n }\n\n .play-toggle {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 0;\n margin-bottom: 4px;\n cursor: pointer;\n font-size: 12px;\n font-weight: 500;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n }\n\n .play-toggle input[type='checkbox'] {\n accent-color: var(--md-sys-color-primary, #6750a4);\n }\n\n .empty {\n color: var(--md-sys-color-on-surface-variant, #999);\n font-size: 11px;\n padding: 8px;\n text-align: center;\n }\n `\n ]\n\n @property({ type: String }) declare src: string | undefined\n\n @state() declare private _animNames: string[]\n @state() declare private _mode: AnimMode\n @state() declare private _playingAnim: string | null\n\n private _component: any = null\n private _lastExternalValue: any = undefined\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('src')) {\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: {\n callback: (selected: any[]) => {\n this._component = selected?.[0]\n this._refreshAnimNames()\n }\n }\n })\n )\n }\n }\n\n private _refreshAnimNames() {\n if (!this._component) {\n this._animNames = []\n return\n }\n\n this._animNames = (this._component.animationNames as string[]) ?? []\n }\n\n private _pollTimer?: number\n\n connectedCallback() {\n super.connectedCallback()\n // GLTF 비동기 로드 후 애니메이션 목록 갱신을 위한 폴링 (최대 20회 = 20초)\n let retries = 0\n this._pollTimer = window.setInterval(() => {\n if (!this._animNames?.length) {\n this._refreshAnimNames()\n }\n if (this._animNames?.length || ++retries >= 20) {\n clearInterval(this._pollTimer)\n this._pollTimer = undefined\n }\n }, 1000)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n if (this._pollTimer) {\n clearInterval(this._pollTimer)\n }\n }\n\n editorTemplate(value: any, _spec: PropertySpec): TemplateResult {\n if (!this._animNames?.length) {\n this._refreshAnimNames()\n }\n\n if (value !== this._lastExternalValue) {\n this._lastExternalValue = value\n this._mode = getMode(value)\n }\n\n const mode = this._mode\n const animCount = this._animNames?.length ?? 0\n const targets: string[] = Array.isArray(this.value) ? this.value : (Array.isArray(value) ? value : [])\n\n const isPlaying = this._getPlayState()\n\n return html`\n <fieldset fullwidth>\n ${animCount > 0\n ? html`\n <label class=\"play-toggle\">\n <input\n type=\"checkbox\"\n .checked=${isPlaying}\n @change=${(e: Event) => {\n e.stopPropagation()\n this._setPlay((e.target as HTMLInputElement).checked)\n }}\n />\n <span>Play</span>\n </label>\n\n <div class=\"mode-selector\">\n <div class=\"mode-btn\" ?active=${mode === 'all'} @click=${() => this._setMode('all')}>All</div>\n <div class=\"mode-btn\" ?active=${mode === 'none'} @click=${() => this._setMode('none')}>None</div>\n <div class=\"mode-btn\" ?active=${mode === 'custom'} @click=${() => this._setMode('custom')}>Custom</div>\n </div>\n\n ${mode === 'all'\n ? html`<div class=\"mode-desc\">${animCount} animations — play all</div>`\n : nothing}\n ${mode === 'none'\n ? html`<div class=\"mode-desc\">No animations auto-play</div>`\n : nothing}\n ${mode === 'custom'\n ? html`\n <div class=\"mode-desc\">\n <span style=\"flex:1\">${targets.length} / ${animCount} selected</span>\n <button class=\"mini-btn\" @click=${() => this._applyValue([...(this._animNames ?? [])])}>All</button>\n <button class=\"mini-btn\" @click=${() => this._applyValue([])}>Clear</button>\n </div>\n `\n : nothing}\n\n <div class=\"anim-list\">\n ${(this._animNames ?? []).map(\n name => html`\n <div class=\"anim-item\">\n ${mode === 'custom'\n ? html`\n <input\n type=\"checkbox\"\n .checked=${targets.includes(name)}\n @change=${(e: Event) => {\n e.stopPropagation()\n this._onToggle(name, (e.target as HTMLInputElement).checked, targets)\n }}\n />\n `\n : nothing}\n <span>${name}</span>\n <md-icon\n ?playing=${this._playingAnim === name}\n @click=${() => this._togglePreview(name)}\n title=\"preview\"\n >${this._playingAnim === name ? 'stop' : 'play_arrow'}</md-icon>\n </div>\n `\n )}\n </div>\n `\n : html`<div class=\"empty\">No animations available</div>`}\n </fieldset>\n `\n }\n\n private _setMode(mode: AnimMode) {\n const prevMode = this._mode\n this._mode = mode\n\n let newValue: any\n switch (mode) {\n case 'all':\n newValue = '*'\n break\n case 'none':\n newValue = 'none'\n break\n case 'custom':\n if (prevMode === 'all') {\n newValue = [...(this._animNames ?? [])]\n } else if (prevMode === 'none') {\n newValue = []\n } else {\n newValue = Array.isArray(this.value) ? this.value : []\n }\n break\n }\n\n this._applyValue(newValue)\n }\n\n private _onToggle(name: string, checked: boolean, current: string[]) {\n const list = [...current]\n if (checked) {\n if (!list.includes(name)) list.push(name)\n } else {\n const idx = list.indexOf(name)\n if (idx >= 0) list.splice(idx, 1)\n }\n this._applyValue(list)\n }\n\n private _applyValue(newValue: any) {\n this.value = newValue\n this.requestUpdate()\n\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: {\n callback: (selected: any[]) => {\n selected[0].set('playTargets', newValue)\n }\n }\n })\n )\n }\n\n private _getPlayState(): boolean {\n return this._component?.state?.play !== false\n }\n\n private _setPlay(value: boolean) {\n if (!this._component) return\n this._component.set('play', value)\n this.requestUpdate()\n }\n\n private _togglePreview(name: string) {\n if (!this._component) return\n\n const ro = this._component.realObject as any\n if (!ro?._animationActions) return\n\n const action = ro._animationActions.get(name)\n if (!action) return\n\n if (this._playingAnim === name) {\n action.stop()\n this._playingAnim = null\n } else {\n // 이전 프리뷰 중지\n if (this._playingAnim) {\n ro._animationActions.get(this._playingAnim)?.stop()\n }\n action.play()\n this._playingAnim = name\n }\n\n this._component.invalidate()\n this.requestUpdate()\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './rack-table-cell.js';
5
5
  export * from './visualizer.js';
6
6
  export * from './stock.js';
7
7
  export * from './signal-tower.js';
8
+ export * from './stock-hub.js';
8
9
  export * from './tank.js';
9
10
  export * from './vehicle.js';
10
11
  export * from './carrier.js';
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ export * from './rack-table-cell.js';
10
10
  export * from './visualizer.js';
11
11
  export * from './stock.js';
12
12
  export * from './signal-tower.js';
13
+ export * from './stock-hub.js';
13
14
  export * from './tank.js';
14
15
  export * from './vehicle.js';
15
16
  export * from './carrier.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,6CAA6C;AAC7C,2BAA2B;AAE3B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\n// threed 인프라 + 범용 3D 컴포넌트는 things-scene에서 제공\n// 여기서는 도메인 특화 컴포넌트만 export\n\nexport * from './desk.js'\nexport * from './rack.js'\nexport * from './rack-table.js'\nexport * from './rack-table-cell.js'\nexport * from './visualizer.js'\nexport * from './stock.js'\nexport * from './signal-tower.js'\nexport * from './tank.js'\nexport * from './vehicle.js'\nexport * from './carrier.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,6CAA6C;AAC7C,2BAA2B;AAE3B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\n// threed 인프라 + 범용 3D 컴포넌트는 things-scene에서 제공\n// 여기서는 도메인 특화 컴포넌트만 export\n\nexport * from './desk.js'\nexport * from './rack.js'\nexport * from './rack-table.js'\nexport * from './rack-table-cell.js'\nexport * from './visualizer.js'\nexport * from './stock.js'\nexport * from './signal-tower.js'\nexport * from './stock-hub.js'\nexport * from './tank.js'\nexport * from './vehicle.js'\nexport * from './carrier.js'\n"]}
@@ -40,6 +40,7 @@ export class RackTable3d extends RealObjectGroup {
40
40
  if (!isEmpty) {
41
41
  cell.setState('shelfLocations', shelfLoc);
42
42
  const rack = new Rack(cell);
43
+ cell._realObject = rack; // 중복 생성 방지: addObject 재귀에서 skip
43
44
  rack.update();
44
45
  this.object3d.add(rack.object3d);
45
46
  return rack;
@@ -1 +1 @@
1
- {"version":3,"file":"rack-table-3d.js","sourceRoot":"","sources":["../src/rack-table-3d.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,mBAAmB,MAAM,iDAAiD,CAAA;AACtF,OAAO,EAAa,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAEnE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAGhC,MAAM,mBAAmB,GAAG,QAAQ,CAAA;AAEpC,MAAM,OAAO,WAAY,SAAQ,eAAe;IACtC,cAAc,CAA6B;IAEnD,KAAK;QACH,KAAK,CAAC,KAAK,EAAE,CAAA;QAEb,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAED,IAAI,QAAQ;QACV,MAAM,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAA;QAEzC,OAAO;YACL,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;YACf,CAAC,EAAE,IAAI;YACP,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;SAChB,CAAA;IACH,CAAC;IAEO,mBAAmB;QACzB,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,CAAA;QAE9B,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAA;QAC5C,MAAM,KAAK,GAAG,WAAW,IAAI,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,mBAAmB,CAAA;QAEhG,IAAI,CAAC,cAAc,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC;YACnD,KAAK;YACL,SAAS,EAAE,IAAI;YACf,SAAS,EAAE,IAAI;SAChB,CAAC,CAAA;QAEF,OAAO,IAAI,CAAC,cAAc,CAAA;IAC5B,CAAC;IAED,WAAW;QACT,MAAM,EAAE,QAAQ,GAAG,CAAC,EAAE,cAAc,EAAE,OAAO,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAA;QAE1E,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAA;QAEpC,MAAM,KAAK,GAAI,IAAI,CAAC,SAAkC,CAAC,UAAU;aAC9D,GAAG,CAAC,CAAC,IAAe,EAAE,EAAE;YACvB,MAAM,EAAE,cAAc,EAAE,QAAQ,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;YAEzE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,IAAI,CAAC,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAA;gBAEzC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAA;gBAE3B,IAAI,CAAC,MAAM,EAAE,CAAA;gBACb,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBAEhC,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAM;QACR,CAAC,CAAC;aACD,MAAM,CAAC,CAAC,IAAI,EAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAEzC,IAAI,CAAC,4BAA4B,CAAC,KAAK,CAAC,CAAA;IAC1C,CAAC;IAED,4BAA4B,CAAC,KAAa;QACxC,MAAM,gBAAgB,GAA2B,EAAE,CAAA;QACnD,MAAM,gBAAgB,GAA2B,EAAE,CAAA;QAEnD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;gBACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAA;gBAE3B,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,OAAM;gBACR,CAAC;gBAED,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;gBACrE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;gBACxD,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACjC,CAAC,CAAC,CAAA;YAEF,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChC,MAAM,SAAS,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,eAAe,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAA;gBACnH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAC9B,CAAC;YAED,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;gBACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAA;gBAE3B,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,OAAM;gBACR,CAAC;gBAED,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;gBACrE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;gBACxD,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACjC,CAAC,CAAC,CAAA;YAEF,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAA;gBACnC,QAAQ,CAAC,OAAO,GAAG,GAAG,CAAA;gBACtB,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAA;gBAE3B,MAAM,SAAS,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,eAAe,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,CAAA;gBAEjG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,CAAA;QAC9B,IAAI,CAAC,cAAc,GAAG,SAAS,CAAA;QAE/B,KAAK,CAAC,OAAO,EAAE,CAAA;IACjB,CAAC;IAED,WAAW,KAAI,CAAC;CACjB","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { Component, RealObjectGroup } from '@hatiolab/things-scene'\n\nimport { Rack } from './rack.js'\nimport type { RackTable } from './rack-table.js'\n\nconst DEFAULT_FRAME_COLOR = 0x8a8a8a\n\nexport class RackTable3d extends RealObjectGroup {\n private _frameMaterial?: THREE.MeshStandardMaterial\n\n build() {\n super.build()\n\n this.createRacks()\n }\n\n get position() {\n const { zPos = 0 } = this.component.state\n\n return {\n x: this.cx || 0,\n y: zPos,\n z: this.cy || 0\n }\n }\n\n private createFrameMaterial(): THREE.MeshStandardMaterial {\n this._frameMaterial?.dispose()\n\n const { strokeStyle } = this.component.state\n const color = strokeStyle && typeof strokeStyle === 'string' ? strokeStyle : DEFAULT_FRAME_COLOR\n\n this._frameMaterial = new THREE.MeshStandardMaterial({\n color,\n roughness: 0.35,\n metalness: 0.85\n })\n\n return this._frameMaterial\n }\n\n createRacks() {\n const { rotation = 0, shelfLocations, shelves = 1 } = this.component.state\n\n this.object3d.rotation.y = -rotation\n\n const racks = (this.component as unknown as RackTable).components\n .map((cell: Component) => {\n const { shelfLocations: shelfLoc = shelfLocations, isEmpty } = cell.state\n\n if (!isEmpty) {\n cell.setState('shelfLocations', shelfLoc)\n\n const rack = new Rack(cell)\n\n rack.update()\n this.object3d.add(rack.object3d)\n\n return rack\n }\n return\n })\n .filter((rack): rack is Rack => !!rack)\n\n this.mergeAndAddRackCommonObjects(racks)\n }\n\n mergeAndAddRackCommonObjects(racks: Rack[]) {\n const framesGeometries: THREE.BufferGeometry[] = []\n const boardsGeometries: THREE.BufferGeometry[] = []\n\n if (racks.length > 0) {\n racks.forEach(rack => {\n const geometry = rack.frame\n\n if (!geometry) {\n return\n }\n\n geometry.translate(rack.position.x, rack.position.y, rack.position.z)\n geometry.scale(rack.scale.x, rack.scale.y, rack.scale.z)\n framesGeometries.push(geometry)\n })\n\n if (framesGeometries.length > 0) {\n const frameMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(framesGeometries), this.createFrameMaterial())\n this.object3d.add(frameMesh)\n }\n\n racks.forEach(rack => {\n const geometry = rack.board\n\n if (!geometry) {\n return\n }\n\n geometry.translate(rack.position.x, rack.position.y, rack.position.z)\n geometry.scale(rack.scale.x, rack.scale.y, rack.scale.z)\n boardsGeometries.push(geometry)\n })\n\n if (boardsGeometries.length > 0) {\n const material = Rack.boardMaterial\n material.opacity = 0.5\n material.transparent = true\n\n const boardMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(boardsGeometries), material)\n\n this.object3d.add(boardMesh)\n }\n }\n }\n\n dispose() {\n this._frameMaterial?.dispose()\n this._frameMaterial = undefined\n\n super.dispose()\n }\n\n updateAlpha() {}\n}\n"]}
1
+ {"version":3,"file":"rack-table-3d.js","sourceRoot":"","sources":["../src/rack-table-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { Component, RealObjectGroup } from '@hatiolab/things-scene'\n\nimport { Rack } from './rack.js'\nimport type { RackTable } from './rack-table.js'\n\nconst DEFAULT_FRAME_COLOR = 0x8a8a8a\n\nexport class RackTable3d extends RealObjectGroup {\n private _frameMaterial?: THREE.MeshStandardMaterial\n\n build() {\n super.build()\n\n this.createRacks()\n }\n\n get position() {\n const { zPos = 0 } = this.component.state\n\n return {\n x: this.cx || 0,\n y: zPos,\n z: this.cy || 0\n }\n }\n\n private createFrameMaterial(): THREE.MeshStandardMaterial {\n this._frameMaterial?.dispose()\n\n const { strokeStyle } = this.component.state\n const color = strokeStyle && typeof strokeStyle === 'string' ? strokeStyle : DEFAULT_FRAME_COLOR\n\n this._frameMaterial = new THREE.MeshStandardMaterial({\n color,\n roughness: 0.35,\n metalness: 0.85\n })\n\n return this._frameMaterial\n }\n\n createRacks() {\n const { rotation = 0, shelfLocations, shelves = 1 } = this.component.state\n\n this.object3d.rotation.y = -rotation\n\n const racks = (this.component as unknown as RackTable).components\n .map((cell: Component) => {\n const { shelfLocations: shelfLoc = shelfLocations, isEmpty } = cell.state\n\n if (!isEmpty) {\n cell.setState('shelfLocations', shelfLoc)\n\n const rack = new Rack(cell)\n cell._realObject = rack // 중복 생성 방지: addObject 재귀에서 skip\n\n rack.update()\n this.object3d.add(rack.object3d)\n\n return rack\n }\n return\n })\n .filter((rack): rack is Rack => !!rack)\n\n this.mergeAndAddRackCommonObjects(racks)\n }\n\n mergeAndAddRackCommonObjects(racks: Rack[]) {\n const framesGeometries: THREE.BufferGeometry[] = []\n const boardsGeometries: THREE.BufferGeometry[] = []\n\n if (racks.length > 0) {\n racks.forEach(rack => {\n const geometry = rack.frame\n\n if (!geometry) {\n return\n }\n\n geometry.translate(rack.position.x, rack.position.y, rack.position.z)\n geometry.scale(rack.scale.x, rack.scale.y, rack.scale.z)\n framesGeometries.push(geometry)\n })\n\n if (framesGeometries.length > 0) {\n const frameMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(framesGeometries), this.createFrameMaterial())\n this.object3d.add(frameMesh)\n }\n\n racks.forEach(rack => {\n const geometry = rack.board\n\n if (!geometry) {\n return\n }\n\n geometry.translate(rack.position.x, rack.position.y, rack.position.z)\n geometry.scale(rack.scale.x, rack.scale.y, rack.scale.z)\n boardsGeometries.push(geometry)\n })\n\n if (boardsGeometries.length > 0) {\n const material = Rack.boardMaterial\n material.opacity = 0.5\n material.transparent = true\n\n const boardMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(boardsGeometries), material)\n\n this.object3d.add(boardMesh)\n }\n }\n }\n\n dispose() {\n this._frameMaterial?.dispose()\n this._frameMaterial = undefined\n\n super.dispose()\n }\n\n updateAlpha() {}\n}\n"]}
@@ -245,6 +245,19 @@ let RackTable = class RackTable extends ContainerAbstract {
245
245
  this._legendTarget = this.root.findById?.(legendTarget);
246
246
  this._legendTarget?.on('change', this._onLegendChanged, this);
247
247
  }
248
+ // 하위호환: 자체 legendTarget이 없으면 부모(Visualizer)의 legendTarget 폴백
249
+ if (!this._legendTarget) {
250
+ let ancestor = this.parent;
251
+ while (ancestor) {
252
+ if (ancestor.legendTarget)
253
+ return ancestor.legendTarget;
254
+ ancestor = ancestor.parent;
255
+ }
256
+ // 서비스 레지스트리 폴백: stock-hub의 legendTarget
257
+ const stockHub = this.root?.getService?.('stock');
258
+ if (stockHub?.legendTarget)
259
+ return stockHub.legendTarget;
260
+ }
248
261
  return this._legendTarget;
249
262
  }
250
263
  get hideEmptyStock() {