@fmsim/board 2.0.0-beta.8 → 2.0.0-beta.81

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 (85) hide show
  1. package/custom-elements.json +5568 -3044
  2. package/demo/index-modeller.html +1 -1
  3. package/demo/index-player.html +2 -2
  4. package/demo/index-viewer.html +2 -2
  5. package/demo/index.html +2 -2
  6. package/dist/src/component/etc.js +35 -0
  7. package/dist/src/component/etc.js.map +1 -1
  8. package/dist/src/component/index.d.ts +1 -0
  9. package/dist/src/component/index.js +1 -0
  10. package/dist/src/component/index.js.map +1 -1
  11. package/dist/src/component/restful-property-editor-backdrop-faces.d.ts +50 -0
  12. package/dist/src/component/restful-property-editor-backdrop-faces.js +524 -0
  13. package/dist/src/component/restful-property-editor-backdrop-faces.js.map +1 -0
  14. package/dist/src/data-storage/board-model-cache.d.ts +13 -0
  15. package/dist/src/data-storage/board-model-cache.js +66 -0
  16. package/dist/src/data-storage/board-model-cache.js.map +1 -0
  17. package/dist/src/index.d.ts +3 -0
  18. package/dist/src/index.js +2 -0
  19. package/dist/src/index.js.map +1 -1
  20. package/dist/src/layers/movement-layer.d.ts +4 -4
  21. package/dist/src/layers/movement-layer.js +16 -8
  22. package/dist/src/layers/movement-layer.js.map +1 -1
  23. package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
  24. package/dist/src/modeller/bulk-create-dialog.js +531 -0
  25. package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
  26. package/dist/src/modeller/bulk-create.d.ts +101 -0
  27. package/dist/src/modeller/bulk-create.js +199 -0
  28. package/dist/src/modeller/bulk-create.js.map +1 -0
  29. package/dist/src/modeller/component-toolbar/component-menu.d.ts +1 -1
  30. package/dist/src/modeller/component-toolbar/component-toolbar.d.ts +1 -1
  31. package/dist/src/modeller/edit-toolbar-style.js +43 -3
  32. package/dist/src/modeller/edit-toolbar-style.js.map +1 -1
  33. package/dist/src/modeller/edit-toolbar.d.ts +13 -0
  34. package/dist/src/modeller/edit-toolbar.js +168 -7
  35. package/dist/src/modeller/edit-toolbar.js.map +1 -1
  36. package/dist/src/modeller/property-sidebar/abstract-property.js +10 -2
  37. package/dist/src/modeller/property-sidebar/abstract-property.js.map +1 -1
  38. package/dist/src/modeller/property-sidebar/data-binding/data-binding.d.ts +1 -1
  39. package/dist/src/modeller/property-sidebar/effects/effects.d.ts +1 -1
  40. package/dist/src/modeller/property-sidebar/effects/property-event.d.ts +1 -1
  41. package/dist/src/modeller/property-sidebar/property-sidebar.d.ts +1 -1
  42. package/dist/src/modeller/property-sidebar/shapes/shapes.js +31 -0
  43. package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
  44. package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +2 -1
  45. package/dist/src/modeller/property-sidebar/specifics/specifics.js +10 -0
  46. package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
  47. package/dist/src/modeller/property-sidebar/styles/styles.d.ts +1 -1
  48. package/dist/src/modeller/scene-viewer/ox-scene-layer.d.ts +4 -3
  49. package/dist/src/modeller/scene-viewer/ox-scene-layer.js +2 -1
  50. package/dist/src/modeller/scene-viewer/ox-scene-layer.js.map +1 -1
  51. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
  52. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +5 -1
  53. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  54. package/dist/src/ox-board-component-info.d.ts +1 -0
  55. package/dist/src/ox-board-component-info.js +5 -1
  56. package/dist/src/ox-board-component-info.js.map +1 -1
  57. package/dist/src/ox-board-list.js +2 -2
  58. package/dist/src/ox-board-list.js.map +1 -1
  59. package/dist/src/ox-board-modeller.d.ts +6 -3
  60. package/dist/src/ox-board-modeller.js +13 -1
  61. package/dist/src/ox-board-modeller.js.map +1 -1
  62. package/dist/src/ox-board-viewer.d.ts +65 -13
  63. package/dist/src/ox-board-viewer.js +610 -98
  64. package/dist/src/ox-board-viewer.js.map +1 -1
  65. package/dist/src/ox-editor-board-selector.d.ts +3 -0
  66. package/dist/src/ox-editor-board-selector.js +38 -9
  67. package/dist/src/ox-editor-board-selector.js.map +1 -1
  68. package/dist/src/ox-playback-controls.d.ts +157 -0
  69. package/dist/src/ox-playback-controls.js +1089 -0
  70. package/dist/src/ox-playback-controls.js.map +1 -0
  71. package/dist/src/playback/playback-engine.d.ts +106 -0
  72. package/dist/src/playback/playback-engine.js +395 -0
  73. package/dist/src/playback/playback-engine.js.map +1 -0
  74. package/dist/src/selector/board-creation-popup.js +2 -2
  75. package/dist/src/selector/board-creation-popup.js.map +1 -1
  76. package/dist/src/selector/board-thumbnail-card.js +6 -6
  77. package/dist/src/selector/board-thumbnail-card.js.map +1 -1
  78. package/dist/src/selector/ox-board-creation-card.js +2 -2
  79. package/dist/src/selector/ox-board-creation-card.js.map +1 -1
  80. package/dist/src/selector/ox-board-selector.js +21 -10
  81. package/dist/src/selector/ox-board-selector.js.map +1 -1
  82. package/dist/tsconfig.tsbuildinfo +1 -1
  83. package/icons/components/text.png +0 -0
  84. package/icons/icon-htoolbar.png +0 -0
  85. package/package.json +8 -4
@@ -0,0 +1,524 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * fmsim-restful 환경에서는 GraphQL/attachment 의존이 있는 ox-input-fill-style 대신
5
+ * restful-input-fill-style 을 사용해야 한다. ox-property-editor-backdrop-faces 와
6
+ * 동일한 cube-picker UI 를 유지하되 fillStyle 에디터만 restful 버전으로 교체.
7
+ *
8
+ * 양쪽 동기화 시 주의: ox-property-editor-backdrop-faces.ts (operato/property-editor)
9
+ * 변경 시 이 파일도 함께 업데이트.
10
+ */
11
+ import { __decorate } from "tslib";
12
+ import './restful-input-fill-style.js';
13
+ import '@operato/i18n/ox-i18n.js';
14
+ import { css, html, LitElement, nothing } from 'lit';
15
+ import { customElement, property, state } from 'lit/decorators.js';
16
+ import { OxPropertyEditor } from '@operato/property-editor';
17
+ const OPPOSITE = {
18
+ east: 'west',
19
+ west: 'east',
20
+ north: 'south',
21
+ south: 'north',
22
+ ceiling: 'floor',
23
+ floor: 'ceiling'
24
+ };
25
+ const AXIS_LABEL = {
26
+ east: '+X',
27
+ west: '−X',
28
+ north: '−Z',
29
+ south: '+Z',
30
+ ceiling: '+Y',
31
+ floor: '−Y'
32
+ };
33
+ const DEFAULT_FACES = {
34
+ east: { fillStyle: '#dde2e8', visible: true, alpha: 0.35, rotation: 0, mirror: false },
35
+ west: { fillStyle: '#dde2e8', visible: true, alpha: 0.35, rotation: 0, mirror: false },
36
+ north: { fillStyle: '#dde2e8', visible: true, alpha: 0.35, rotation: 0, mirror: false },
37
+ south: { fillStyle: '#dde2e8', visible: true, alpha: 0.35, rotation: 0, mirror: false },
38
+ ceiling: { fillStyle: '#e8e8ec', visible: true, alpha: 1.0, rotation: 0, mirror: false },
39
+ floor: { fillStyle: '#9c9c9c', visible: true, alpha: 1.0, rotation: 0, mirror: false }
40
+ };
41
+ let RestfulInputBackdropFaces = class RestfulInputBackdropFaces extends LitElement {
42
+ constructor() {
43
+ super(...arguments);
44
+ this.value = {};
45
+ this._selected = 'ceiling';
46
+ }
47
+ _face(name) {
48
+ var _a, _b;
49
+ return { ...DEFAULT_FACES[name], ...((_b = (_a = this.value) === null || _a === void 0 ? void 0 : _a[name]) !== null && _b !== void 0 ? _b : {}) };
50
+ }
51
+ _swatchColor(name) {
52
+ var _a;
53
+ const fs = this._face(name).fillStyle;
54
+ if (typeof fs === 'string' && fs && fs !== 'transparent')
55
+ return fs;
56
+ if (fs && typeof fs === 'object') {
57
+ if (fs.type === 'gradient' && Array.isArray(fs.colorStops) && ((_a = fs.colorStops[0]) === null || _a === void 0 ? void 0 : _a.color)) {
58
+ return fs.colorStops[0].color;
59
+ }
60
+ if (fs.color)
61
+ return fs.color;
62
+ }
63
+ return undefined;
64
+ }
65
+ _select(name) {
66
+ this._selected = name;
67
+ }
68
+ _commit(next) {
69
+ this.value = next;
70
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
71
+ }
72
+ _patchFace(name, patch) {
73
+ const next = { ...(this.value || {}) };
74
+ next[name] = { ...this._face(name), ...patch };
75
+ this._commit(next);
76
+ }
77
+ _onFillStyleChange(e) {
78
+ e.stopPropagation();
79
+ const target = e.target;
80
+ this._patchFace(this._selected, { fillStyle: target.value });
81
+ }
82
+ _onVisibleChange(e) {
83
+ e.stopPropagation();
84
+ const target = e.target;
85
+ this._patchFace(this._selected, { visible: target.checked });
86
+ }
87
+ _onAlphaChange(e) {
88
+ e.stopPropagation();
89
+ const target = e.target;
90
+ const v = parseFloat(target.value);
91
+ const alpha = Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;
92
+ this._patchFace(this._selected, { alpha });
93
+ }
94
+ _onRotationChange(e) {
95
+ e.stopPropagation();
96
+ const target = e.target;
97
+ const n = parseInt(target.value, 10);
98
+ const rotation = n === 90 || n === 180 || n === 270 ? n : 0;
99
+ this._patchFace(this._selected, { rotation });
100
+ }
101
+ _onMirrorChange(e) {
102
+ e.stopPropagation();
103
+ const target = e.target;
104
+ this._patchFace(this._selected, { mirror: target.checked });
105
+ }
106
+ _copyToOpposite() {
107
+ const src = this._face(this._selected);
108
+ const opposite = OPPOSITE[this._selected];
109
+ const next = { ...(this.value || {}) };
110
+ next[opposite] = { ...src };
111
+ this._commit(next);
112
+ }
113
+ _copyToAllWalls() {
114
+ const src = this._face(this._selected);
115
+ const next = { ...(this.value || {}) };
116
+ for (const w of ['east', 'west', 'north', 'south']) {
117
+ next[w] = { ...src };
118
+ }
119
+ this._commit(next);
120
+ }
121
+ _renderLabel(name, x, y) {
122
+ const f = this._face(name);
123
+ const swatch = this._swatchColor(name);
124
+ const selected = this._selected === name;
125
+ return html `
126
+ <div
127
+ class="face-label ${selected ? 'selected' : ''}"
128
+ style="left: ${x}px; top: ${y}px;"
129
+ @click=${() => this._select(name)}
130
+ title=${`${name} (${AXIS_LABEL[name]})`}
131
+ >
132
+ <span
133
+ class="swatch"
134
+ style=${swatch ? `background: ${swatch};` : ''}
135
+ ?data-empty=${!swatch}
136
+ ></span>
137
+ <ox-i18n msgid="label.${name}">${name}</ox-i18n>
138
+ ${f.visible === false ? html `<span class="eye" title="hidden">⊘</span>` : nothing}
139
+ </div>
140
+ `;
141
+ }
142
+ _faceClass(name) {
143
+ const cls = ['face-poly'];
144
+ if (this._selected === name)
145
+ cls.push('selected');
146
+ if (this._face(name).visible === false)
147
+ cls.push('hidden-face');
148
+ return cls.join(' ');
149
+ }
150
+ _ghostClass(name) {
151
+ const cls = ['face-ghost'];
152
+ if (this._selected === name)
153
+ cls.push('selected');
154
+ if (this._face(name).visible === false)
155
+ cls.push('invisible');
156
+ return cls.join(' ');
157
+ }
158
+ render() {
159
+ var _a, _b, _c, _d, _e, _f;
160
+ const sel = this._face(this._selected);
161
+ return html `
162
+ <div class="cube-picker">
163
+ <svg viewBox="0 0 288 160" aria-label="Backdrop face picker">
164
+ <polygon
165
+ class=${this._faceClass('ceiling')}
166
+ points="90,69 170,69 198,41 118,41"
167
+ fill="${(_a = this._swatchColor('ceiling')) !== null && _a !== void 0 ? _a : '#fff8e1'}"
168
+ @click=${() => this._select('ceiling')}
169
+ />
170
+ <polygon
171
+ class=${this._faceClass('east')}
172
+ points="170,125 198,97 198,41 170,69"
173
+ fill="${(_b = this._swatchColor('east')) !== null && _b !== void 0 ? _b : '#ffcdd2'}"
174
+ @click=${() => this._select('east')}
175
+ />
176
+ <polygon
177
+ class=${this._faceClass('south')}
178
+ points="90,125 170,125 170,69 90,69"
179
+ fill="${(_c = this._swatchColor('south')) !== null && _c !== void 0 ? _c : '#c8e6c9'}"
180
+ @click=${() => this._select('south')}
181
+ />
182
+
183
+ <polygon
184
+ class=${this._ghostClass('north')}
185
+ points="118,97 198,97 198,41 118,41"
186
+ @click=${() => this._select('north')}
187
+ />
188
+ <polygon
189
+ class=${this._ghostClass('west')}
190
+ points="118,97 90,125 90,69 118,41"
191
+ @click=${() => this._select('west')}
192
+ />
193
+ <polygon
194
+ class=${this._ghostClass('floor')}
195
+ points="118,97 198,97 170,125 90,125"
196
+ @click=${() => this._select('floor')}
197
+ />
198
+ </svg>
199
+
200
+ ${this._renderLabel('ceiling', 144, 20)} ${this._renderLabel('floor', 144, 146)}
201
+ ${this._renderLabel('east', 220, 83)} ${this._renderLabel('west', 38, 83)}
202
+ ${this._renderLabel('north', 220, 51)} ${this._renderLabel('south', 38, 115)}
203
+ </div>
204
+
205
+ <div class="face-detail">
206
+ <div class="face-detail-title">
207
+ <span><ox-i18n msgid="label.${this._selected}">${this._selected}</ox-i18n></span>
208
+ <small>${AXIS_LABEL[this._selected]}</small>
209
+ </div>
210
+
211
+ <div class="row">
212
+ <label><ox-i18n msgid="label.visible">visible</ox-i18n></label>
213
+ <div class="ctl">
214
+ <input type="checkbox" ?checked=${sel.visible !== false} @change=${this._onVisibleChange} />
215
+ </div>
216
+ </div>
217
+
218
+ <div class="fill-row">
219
+ <restful-input-fill-style .value=${sel.fillStyle} @change=${this._onFillStyleChange}>
220
+ </restful-input-fill-style>
221
+ </div>
222
+
223
+ <div class="row">
224
+ <label><ox-i18n msgid="label.alpha">alpha</ox-i18n></label>
225
+ <div class="ctl">
226
+ <input
227
+ type="range"
228
+ min="0"
229
+ max="1"
230
+ step="0.05"
231
+ .value=${String((_d = sel.alpha) !== null && _d !== void 0 ? _d : 1)}
232
+ @input=${this._onAlphaChange}
233
+ />
234
+ <span class="alpha-readout">${((_e = sel.alpha) !== null && _e !== void 0 ? _e : 1).toFixed(2)}</span>
235
+ </div>
236
+ </div>
237
+
238
+ <div class="row">
239
+ <label><ox-i18n msgid="label.rotation">rotation</ox-i18n></label>
240
+ <div class="ctl">
241
+ <select .value=${String((_f = sel.rotation) !== null && _f !== void 0 ? _f : 0)} @change=${this._onRotationChange}>
242
+ <option value="0">0°</option>
243
+ <option value="90">90°</option>
244
+ <option value="180">180°</option>
245
+ <option value="270">270°</option>
246
+ </select>
247
+ <label class="mirror-toggle">
248
+ <input type="checkbox" ?checked=${sel.mirror === true} @change=${this._onMirrorChange} />
249
+ <ox-i18n msgid="label.mirror">mirror</ox-i18n>
250
+ </label>
251
+ </div>
252
+ </div>
253
+
254
+ <div class="actions">
255
+ <button @click=${this._copyToOpposite} title="Copy current face to opposite">
256
+ ↔ <ox-i18n msgid="label.copy-to-opposite">opposite</ox-i18n>
257
+ </button>
258
+ <button @click=${this._copyToAllWalls} title="Copy current face to all 4 walls">
259
+ <ox-i18n msgid="label.copy-to-walls">all walls</ox-i18n>
260
+ </button>
261
+ </div>
262
+ </div>
263
+ `;
264
+ }
265
+ };
266
+ RestfulInputBackdropFaces.styles = css `
267
+ :host {
268
+ display: block;
269
+ font: 12px Arial, sans-serif;
270
+ }
271
+
272
+ .cube-picker {
273
+ position: relative;
274
+ width: 100%;
275
+ max-width: 260px;
276
+ height: 160px;
277
+ margin: 0 auto 8px;
278
+ }
279
+ .cube-picker svg {
280
+ display: block;
281
+ width: 100%;
282
+ height: 100%;
283
+ overflow: visible;
284
+ }
285
+
286
+ .face-poly {
287
+ cursor: pointer;
288
+ stroke: #3a3f4a;
289
+ stroke-width: 1;
290
+ stroke-linejoin: round;
291
+ transition:
292
+ filter 0.15s,
293
+ stroke 0.15s,
294
+ stroke-width 0.15s;
295
+ }
296
+ .face-poly:hover {
297
+ filter: brightness(1.15);
298
+ }
299
+ .face-poly.selected {
300
+ stroke: var(--md-sys-color-primary, #e15c5c);
301
+ stroke-width: 2.5;
302
+ }
303
+ .face-poly.hidden-face {
304
+ stroke-dasharray: 3 2;
305
+ opacity: 0.4;
306
+ }
307
+
308
+ .face-ghost {
309
+ cursor: pointer;
310
+ fill: none;
311
+ stroke: #8a93a6;
312
+ stroke-width: 1;
313
+ stroke-dasharray: 3 3;
314
+ stroke-linejoin: round;
315
+ opacity: 0.4;
316
+ pointer-events: stroke;
317
+ transition:
318
+ stroke 0.15s,
319
+ stroke-width 0.15s,
320
+ opacity 0.15s;
321
+ }
322
+ .face-ghost:hover {
323
+ opacity: 0.85;
324
+ stroke: var(--md-sys-color-primary, #e15c5c);
325
+ }
326
+ .face-ghost.selected {
327
+ stroke: var(--md-sys-color-primary, #e15c5c);
328
+ stroke-width: 2;
329
+ opacity: 1;
330
+ }
331
+ .face-ghost.invisible {
332
+ opacity: 0.2;
333
+ }
334
+
335
+ .face-label {
336
+ position: absolute;
337
+ display: inline-flex;
338
+ align-items: center;
339
+ gap: 4px;
340
+ padding: 2px 6px 2px 4px;
341
+ font: 600 10px Arial, sans-serif;
342
+ background: var(--md-sys-color-surface, #fff);
343
+ border: 1px solid var(--md-sys-color-outline-variant, #d8dde6);
344
+ border-radius: 10px;
345
+ cursor: pointer;
346
+ user-select: none;
347
+ transform: translate(-50%, -50%);
348
+ white-space: nowrap;
349
+ transition:
350
+ border-color 0.15s,
351
+ background 0.15s,
352
+ color 0.15s;
353
+ text-transform: capitalize;
354
+ }
355
+ .face-label:hover {
356
+ border-color: var(--md-sys-color-primary, #e15c5c);
357
+ }
358
+ .face-label.selected {
359
+ border-color: var(--md-sys-color-primary, #e15c5c);
360
+ background: var(--md-sys-color-primary-container, #fde7e7);
361
+ color: var(--md-sys-color-on-primary-container, #b91c1c);
362
+ }
363
+ .face-label .swatch {
364
+ width: 12px;
365
+ height: 12px;
366
+ border-radius: 2px;
367
+ border: 1px solid rgba(0, 0, 0, 0.15);
368
+ flex: none;
369
+ }
370
+ .face-label .swatch[data-empty] {
371
+ background: repeating-linear-gradient(45deg, #fff, #fff 2px, #ccc 2px, #ccc 4px);
372
+ }
373
+ .face-label .eye {
374
+ font-size: 9px;
375
+ opacity: 0.55;
376
+ margin-left: 1px;
377
+ }
378
+
379
+ .face-detail {
380
+ border-top: 1px dashed var(--md-sys-color-outline-variant, #d8dde6);
381
+ padding-top: 10px;
382
+ margin-top: 4px;
383
+ }
384
+ .face-detail-title {
385
+ display: flex;
386
+ justify-content: space-between;
387
+ align-items: baseline;
388
+ font-weight: 600;
389
+ font-size: 11px;
390
+ color: var(--md-sys-color-primary, #e15c5c);
391
+ text-transform: uppercase;
392
+ letter-spacing: 0.5px;
393
+ margin-bottom: 8px;
394
+ }
395
+ .face-detail-title small {
396
+ font-weight: 400;
397
+ text-transform: none;
398
+ letter-spacing: 0;
399
+ color: var(--md-sys-color-on-secondary-container, #8a93a6);
400
+ font: 10px monospace;
401
+ }
402
+
403
+ .row {
404
+ display: grid;
405
+ grid-template-columns: 60px 1fr;
406
+ gap: 8px;
407
+ align-items: center;
408
+ margin-bottom: 6px;
409
+ }
410
+ .row > label {
411
+ text-align: right;
412
+ font-size: 11px;
413
+ color: var(--md-sys-color-on-secondary-container, #6b7280);
414
+ }
415
+ .row .ctl {
416
+ display: flex;
417
+ align-items: center;
418
+ gap: 6px;
419
+ }
420
+ .ctl input[type='range'] {
421
+ flex: 1;
422
+ accent-color: var(--md-sys-color-primary, #e15c5c);
423
+ }
424
+ .ctl input[type='checkbox'] {
425
+ accent-color: var(--md-sys-color-primary, #e15c5c);
426
+ }
427
+ .alpha-readout {
428
+ width: 30px;
429
+ text-align: right;
430
+ font: 11px monospace;
431
+ }
432
+
433
+ .row select,
434
+ .row .mirror-toggle {
435
+ font: 10px Arial, sans-serif;
436
+ padding: 5px 6px;
437
+ border: 1px solid var(--md-sys-color-outline-variant, #d8dde6);
438
+ border-radius: 4px;
439
+ background: var(--md-sys-color-surface, #fff);
440
+ color: inherit;
441
+ cursor: pointer;
442
+ }
443
+ .row select {
444
+ flex: 1;
445
+ outline: none;
446
+ }
447
+ .row select:hover,
448
+ .row .mirror-toggle:hover {
449
+ border-color: var(--md-sys-color-primary, #e15c5c);
450
+ color: var(--md-sys-color-primary, #e15c5c);
451
+ }
452
+ .row .mirror-toggle {
453
+ display: inline-flex;
454
+ align-items: center;
455
+ gap: 4px;
456
+ }
457
+ .row .mirror-toggle input[type='checkbox'] {
458
+ margin: 0;
459
+ }
460
+
461
+ .fill-row {
462
+ margin-bottom: 6px;
463
+ }
464
+ restful-input-fill-style {
465
+ font: 12px Arial, sans-serif;
466
+ display: block;
467
+ }
468
+
469
+ .actions {
470
+ margin-top: 8px;
471
+ display: flex;
472
+ gap: 6px;
473
+ }
474
+ .actions button {
475
+ flex: 1;
476
+ font: 10px Arial, sans-serif;
477
+ padding: 5px 6px;
478
+ border: 1px solid var(--md-sys-color-outline-variant, #d8dde6);
479
+ border-radius: 4px;
480
+ background: var(--md-sys-color-surface, #fff);
481
+ color: inherit;
482
+ cursor: pointer;
483
+ }
484
+ .actions button:hover {
485
+ border-color: var(--md-sys-color-primary, #e15c5c);
486
+ color: var(--md-sys-color-primary, #e15c5c);
487
+ }
488
+ `;
489
+ __decorate([
490
+ property({ type: Object })
491
+ ], RestfulInputBackdropFaces.prototype, "value", void 0);
492
+ __decorate([
493
+ state()
494
+ ], RestfulInputBackdropFaces.prototype, "_selected", void 0);
495
+ RestfulInputBackdropFaces = __decorate([
496
+ customElement('restful-input-backdrop-faces')
497
+ ], RestfulInputBackdropFaces);
498
+ export { RestfulInputBackdropFaces };
499
+ let RestfulPropertyEditorBackdropFaces = class RestfulPropertyEditorBackdropFaces extends OxPropertyEditor {
500
+ editorTemplate(value, spec) {
501
+ const { fullwidth = true } = spec.editor || {};
502
+ return html `
503
+ <restful-input-backdrop-faces
504
+ id="editor"
505
+ .value=${value}
506
+ ?fullwidth=${fullwidth}
507
+ ?disabled=${this.disabled}
508
+ ></restful-input-backdrop-faces>
509
+ `;
510
+ }
511
+ };
512
+ RestfulPropertyEditorBackdropFaces.styles = [
513
+ ...OxPropertyEditor.styles,
514
+ css `
515
+ restful-input-backdrop-faces#editor {
516
+ font: 12px Arial, sans-serif;
517
+ }
518
+ `
519
+ ];
520
+ RestfulPropertyEditorBackdropFaces = __decorate([
521
+ customElement('restful-property-editor-backdrop-faces')
522
+ ], RestfulPropertyEditorBackdropFaces);
523
+ export { RestfulPropertyEditorBackdropFaces };
524
+ //# sourceMappingURL=restful-property-editor-backdrop-faces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restful-property-editor-backdrop-faces.js","sourceRoot":"","sources":["../../../src/component/restful-property-editor-backdrop-faces.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * fmsim-restful 환경에서는 GraphQL/attachment 의존이 있는 ox-input-fill-style 대신\n * restful-input-fill-style 을 사용해야 한다. ox-property-editor-backdrop-faces 와\n * 동일한 cube-picker UI 를 유지하되 fillStyle 에디터만 restful 버전으로 교체.\n *\n * 양쪽 동기화 시 주의: ox-property-editor-backdrop-faces.ts (operato/property-editor)\n * 변경 시 이 파일도 함께 업데이트.\n */\n\nimport './restful-input-fill-style.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, LitElement, nothing, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { OxPropertyEditor } from '@operato/property-editor'\nimport type { PropertySpec } from '@operato/property-editor'\n\ntype FaceName = 'east' | 'west' | 'north' | 'south' | 'ceiling' | 'floor'\nconst OPPOSITE: Record<FaceName, FaceName> = {\n east: 'west',\n west: 'east',\n north: 'south',\n south: 'north',\n ceiling: 'floor',\n floor: 'ceiling'\n}\nconst AXIS_LABEL: Record<FaceName, string> = {\n east: '+X',\n west: '−X',\n north: '−Z',\n south: '+Z',\n ceiling: '+Y',\n floor: '−Y'\n}\n\ninterface FaceState {\n fillStyle?: any\n visible?: boolean\n alpha?: number\n rotation?: 0 | 90 | 180 | 270\n mirror?: boolean\n}\ntype FacesValue = Partial<Record<FaceName, FaceState>>\n\nconst DEFAULT_FACES: Record<FaceName, FaceState> = {\n east: { fillStyle: '#dde2e8', visible: true, alpha: 0.35, rotation: 0, mirror: false },\n west: { fillStyle: '#dde2e8', visible: true, alpha: 0.35, rotation: 0, mirror: false },\n north: { fillStyle: '#dde2e8', visible: true, alpha: 0.35, rotation: 0, mirror: false },\n south: { fillStyle: '#dde2e8', visible: true, alpha: 0.35, rotation: 0, mirror: false },\n ceiling: { fillStyle: '#e8e8ec', visible: true, alpha: 1.0, rotation: 0, mirror: false },\n floor: { fillStyle: '#9c9c9c', visible: true, alpha: 1.0, rotation: 0, mirror: false }\n}\n\n@customElement('restful-input-backdrop-faces')\nexport class RestfulInputBackdropFaces extends LitElement {\n @property({ type: Object }) value: FacesValue = {}\n @state() private _selected: FaceName = 'ceiling'\n\n static styles = css`\n :host {\n display: block;\n font: 12px Arial, sans-serif;\n }\n\n .cube-picker {\n position: relative;\n width: 100%;\n max-width: 260px;\n height: 160px;\n margin: 0 auto 8px;\n }\n .cube-picker svg {\n display: block;\n width: 100%;\n height: 100%;\n overflow: visible;\n }\n\n .face-poly {\n cursor: pointer;\n stroke: #3a3f4a;\n stroke-width: 1;\n stroke-linejoin: round;\n transition:\n filter 0.15s,\n stroke 0.15s,\n stroke-width 0.15s;\n }\n .face-poly:hover {\n filter: brightness(1.15);\n }\n .face-poly.selected {\n stroke: var(--md-sys-color-primary, #e15c5c);\n stroke-width: 2.5;\n }\n .face-poly.hidden-face {\n stroke-dasharray: 3 2;\n opacity: 0.4;\n }\n\n .face-ghost {\n cursor: pointer;\n fill: none;\n stroke: #8a93a6;\n stroke-width: 1;\n stroke-dasharray: 3 3;\n stroke-linejoin: round;\n opacity: 0.4;\n pointer-events: stroke;\n transition:\n stroke 0.15s,\n stroke-width 0.15s,\n opacity 0.15s;\n }\n .face-ghost:hover {\n opacity: 0.85;\n stroke: var(--md-sys-color-primary, #e15c5c);\n }\n .face-ghost.selected {\n stroke: var(--md-sys-color-primary, #e15c5c);\n stroke-width: 2;\n opacity: 1;\n }\n .face-ghost.invisible {\n opacity: 0.2;\n }\n\n .face-label {\n position: absolute;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px 2px 4px;\n font: 600 10px Arial, sans-serif;\n background: var(--md-sys-color-surface, #fff);\n border: 1px solid var(--md-sys-color-outline-variant, #d8dde6);\n border-radius: 10px;\n cursor: pointer;\n user-select: none;\n transform: translate(-50%, -50%);\n white-space: nowrap;\n transition:\n border-color 0.15s,\n background 0.15s,\n color 0.15s;\n text-transform: capitalize;\n }\n .face-label:hover {\n border-color: var(--md-sys-color-primary, #e15c5c);\n }\n .face-label.selected {\n border-color: var(--md-sys-color-primary, #e15c5c);\n background: var(--md-sys-color-primary-container, #fde7e7);\n color: var(--md-sys-color-on-primary-container, #b91c1c);\n }\n .face-label .swatch {\n width: 12px;\n height: 12px;\n border-radius: 2px;\n border: 1px solid rgba(0, 0, 0, 0.15);\n flex: none;\n }\n .face-label .swatch[data-empty] {\n background: repeating-linear-gradient(45deg, #fff, #fff 2px, #ccc 2px, #ccc 4px);\n }\n .face-label .eye {\n font-size: 9px;\n opacity: 0.55;\n margin-left: 1px;\n }\n\n .face-detail {\n border-top: 1px dashed var(--md-sys-color-outline-variant, #d8dde6);\n padding-top: 10px;\n margin-top: 4px;\n }\n .face-detail-title {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n font-weight: 600;\n font-size: 11px;\n color: var(--md-sys-color-primary, #e15c5c);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 8px;\n }\n .face-detail-title small {\n font-weight: 400;\n text-transform: none;\n letter-spacing: 0;\n color: var(--md-sys-color-on-secondary-container, #8a93a6);\n font: 10px monospace;\n }\n\n .row {\n display: grid;\n grid-template-columns: 60px 1fr;\n gap: 8px;\n align-items: center;\n margin-bottom: 6px;\n }\n .row > label {\n text-align: right;\n font-size: 11px;\n color: var(--md-sys-color-on-secondary-container, #6b7280);\n }\n .row .ctl {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .ctl input[type='range'] {\n flex: 1;\n accent-color: var(--md-sys-color-primary, #e15c5c);\n }\n .ctl input[type='checkbox'] {\n accent-color: var(--md-sys-color-primary, #e15c5c);\n }\n .alpha-readout {\n width: 30px;\n text-align: right;\n font: 11px monospace;\n }\n\n .row select,\n .row .mirror-toggle {\n font: 10px Arial, sans-serif;\n padding: 5px 6px;\n border: 1px solid var(--md-sys-color-outline-variant, #d8dde6);\n border-radius: 4px;\n background: var(--md-sys-color-surface, #fff);\n color: inherit;\n cursor: pointer;\n }\n .row select {\n flex: 1;\n outline: none;\n }\n .row select:hover,\n .row .mirror-toggle:hover {\n border-color: var(--md-sys-color-primary, #e15c5c);\n color: var(--md-sys-color-primary, #e15c5c);\n }\n .row .mirror-toggle {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n }\n .row .mirror-toggle input[type='checkbox'] {\n margin: 0;\n }\n\n .fill-row {\n margin-bottom: 6px;\n }\n restful-input-fill-style {\n font: 12px Arial, sans-serif;\n display: block;\n }\n\n .actions {\n margin-top: 8px;\n display: flex;\n gap: 6px;\n }\n .actions button {\n flex: 1;\n font: 10px Arial, sans-serif;\n padding: 5px 6px;\n border: 1px solid var(--md-sys-color-outline-variant, #d8dde6);\n border-radius: 4px;\n background: var(--md-sys-color-surface, #fff);\n color: inherit;\n cursor: pointer;\n }\n .actions button:hover {\n border-color: var(--md-sys-color-primary, #e15c5c);\n color: var(--md-sys-color-primary, #e15c5c);\n }\n `\n\n private _face(name: FaceName): FaceState {\n return { ...DEFAULT_FACES[name], ...(this.value?.[name] ?? {}) }\n }\n\n private _swatchColor(name: FaceName): string | undefined {\n const fs = this._face(name).fillStyle\n if (typeof fs === 'string' && fs && fs !== 'transparent') return fs\n if (fs && typeof fs === 'object') {\n if (fs.type === 'gradient' && Array.isArray(fs.colorStops) && fs.colorStops[0]?.color) {\n return fs.colorStops[0].color\n }\n if (fs.color) return fs.color\n }\n return undefined\n }\n\n private _select(name: FaceName) {\n this._selected = name\n }\n\n private _commit(next: FacesValue) {\n this.value = next\n this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }))\n }\n\n private _patchFace(name: FaceName, patch: Partial<FaceState>) {\n const next: FacesValue = { ...(this.value || {}) }\n next[name] = { ...this._face(name), ...patch }\n this._commit(next)\n }\n\n private _onFillStyleChange(e: Event) {\n e.stopPropagation()\n const target = e.target as { value?: any }\n this._patchFace(this._selected, { fillStyle: target.value })\n }\n\n private _onVisibleChange(e: Event) {\n e.stopPropagation()\n const target = e.target as HTMLInputElement\n this._patchFace(this._selected, { visible: target.checked })\n }\n\n private _onAlphaChange(e: Event) {\n e.stopPropagation()\n const target = e.target as HTMLInputElement\n const v = parseFloat(target.value)\n const alpha = Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1\n this._patchFace(this._selected, { alpha })\n }\n\n private _onRotationChange(e: Event) {\n e.stopPropagation()\n const target = e.target as HTMLSelectElement\n const n = parseInt(target.value, 10)\n const rotation: 0 | 90 | 180 | 270 = n === 90 || n === 180 || n === 270 ? n : 0\n this._patchFace(this._selected, { rotation })\n }\n\n private _onMirrorChange(e: Event) {\n e.stopPropagation()\n const target = e.target as HTMLInputElement\n this._patchFace(this._selected, { mirror: target.checked })\n }\n\n private _copyToOpposite() {\n const src = this._face(this._selected)\n const opposite = OPPOSITE[this._selected]\n const next: FacesValue = { ...(this.value || {}) }\n next[opposite] = { ...src }\n this._commit(next)\n }\n\n private _copyToAllWalls() {\n const src = this._face(this._selected)\n const next: FacesValue = { ...(this.value || {}) }\n for (const w of ['east', 'west', 'north', 'south'] as const) {\n next[w] = { ...src }\n }\n this._commit(next)\n }\n\n private _renderLabel(name: FaceName, x: number, y: number) {\n const f = this._face(name)\n const swatch = this._swatchColor(name)\n const selected = this._selected === name\n return html`\n <div\n class=\"face-label ${selected ? 'selected' : ''}\"\n style=\"left: ${x}px; top: ${y}px;\"\n @click=${() => this._select(name)}\n title=${`${name} (${AXIS_LABEL[name]})`}\n >\n <span\n class=\"swatch\"\n style=${swatch ? `background: ${swatch};` : ''}\n ?data-empty=${!swatch}\n ></span>\n <ox-i18n msgid=\"label.${name}\">${name}</ox-i18n>\n ${f.visible === false ? html`<span class=\"eye\" title=\"hidden\">⊘</span>` : nothing}\n </div>\n `\n }\n\n private _faceClass(name: FaceName): string {\n const cls = ['face-poly']\n if (this._selected === name) cls.push('selected')\n if (this._face(name).visible === false) cls.push('hidden-face')\n return cls.join(' ')\n }\n\n private _ghostClass(name: FaceName): string {\n const cls = ['face-ghost']\n if (this._selected === name) cls.push('selected')\n if (this._face(name).visible === false) cls.push('invisible')\n return cls.join(' ')\n }\n\n override render() {\n const sel = this._face(this._selected)\n return html`\n <div class=\"cube-picker\">\n <svg viewBox=\"0 0 288 160\" aria-label=\"Backdrop face picker\">\n <polygon\n class=${this._faceClass('ceiling')}\n points=\"90,69 170,69 198,41 118,41\"\n fill=\"${this._swatchColor('ceiling') ?? '#fff8e1'}\"\n @click=${() => this._select('ceiling')}\n />\n <polygon\n class=${this._faceClass('east')}\n points=\"170,125 198,97 198,41 170,69\"\n fill=\"${this._swatchColor('east') ?? '#ffcdd2'}\"\n @click=${() => this._select('east')}\n />\n <polygon\n class=${this._faceClass('south')}\n points=\"90,125 170,125 170,69 90,69\"\n fill=\"${this._swatchColor('south') ?? '#c8e6c9'}\"\n @click=${() => this._select('south')}\n />\n\n <polygon\n class=${this._ghostClass('north')}\n points=\"118,97 198,97 198,41 118,41\"\n @click=${() => this._select('north')}\n />\n <polygon\n class=${this._ghostClass('west')}\n points=\"118,97 90,125 90,69 118,41\"\n @click=${() => this._select('west')}\n />\n <polygon\n class=${this._ghostClass('floor')}\n points=\"118,97 198,97 170,125 90,125\"\n @click=${() => this._select('floor')}\n />\n </svg>\n\n ${this._renderLabel('ceiling', 144, 20)} ${this._renderLabel('floor', 144, 146)}\n ${this._renderLabel('east', 220, 83)} ${this._renderLabel('west', 38, 83)}\n ${this._renderLabel('north', 220, 51)} ${this._renderLabel('south', 38, 115)}\n </div>\n\n <div class=\"face-detail\">\n <div class=\"face-detail-title\">\n <span><ox-i18n msgid=\"label.${this._selected}\">${this._selected}</ox-i18n></span>\n <small>${AXIS_LABEL[this._selected]}</small>\n </div>\n\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.visible\">visible</ox-i18n></label>\n <div class=\"ctl\">\n <input type=\"checkbox\" ?checked=${sel.visible !== false} @change=${this._onVisibleChange} />\n </div>\n </div>\n\n <div class=\"fill-row\">\n <restful-input-fill-style .value=${sel.fillStyle} @change=${this._onFillStyleChange}>\n </restful-input-fill-style>\n </div>\n\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.alpha\">alpha</ox-i18n></label>\n <div class=\"ctl\">\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n .value=${String(sel.alpha ?? 1)}\n @input=${this._onAlphaChange}\n />\n <span class=\"alpha-readout\">${(sel.alpha ?? 1).toFixed(2)}</span>\n </div>\n </div>\n\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.rotation\">rotation</ox-i18n></label>\n <div class=\"ctl\">\n <select .value=${String(sel.rotation ?? 0)} @change=${this._onRotationChange}>\n <option value=\"0\">0°</option>\n <option value=\"90\">90°</option>\n <option value=\"180\">180°</option>\n <option value=\"270\">270°</option>\n </select>\n <label class=\"mirror-toggle\">\n <input type=\"checkbox\" ?checked=${sel.mirror === true} @change=${this._onMirrorChange} />\n <ox-i18n msgid=\"label.mirror\">mirror</ox-i18n>\n </label>\n </div>\n </div>\n\n <div class=\"actions\">\n <button @click=${this._copyToOpposite} title=\"Copy current face to opposite\">\n ↔ <ox-i18n msgid=\"label.copy-to-opposite\">opposite</ox-i18n>\n </button>\n <button @click=${this._copyToAllWalls} title=\"Copy current face to all 4 walls\">\n <ox-i18n msgid=\"label.copy-to-walls\">all walls</ox-i18n>\n </button>\n </div>\n </div>\n `\n }\n}\n\n@customElement('restful-property-editor-backdrop-faces')\nexport class RestfulPropertyEditorBackdropFaces extends OxPropertyEditor {\n static styles = [\n ...OxPropertyEditor.styles,\n css`\n restful-input-backdrop-faces#editor {\n font: 12px Arial, sans-serif;\n }\n `\n ]\n\n override editorTemplate(value: any, spec: PropertySpec): TemplateResult {\n const { fullwidth = true } = spec.editor || {}\n return html`\n <restful-input-backdrop-faces\n id=\"editor\"\n .value=${value}\n ?fullwidth=${fullwidth}\n ?disabled=${this.disabled}\n ></restful-input-backdrop-faces>\n `\n }\n}\n"]}
@@ -0,0 +1,13 @@
1
+ export interface CachedBoard {
2
+ id: string;
3
+ name: string;
4
+ model: any;
5
+ updatedAt: string;
6
+ }
7
+ export declare class BoardModelCache {
8
+ static get(boardId: string): Promise<CachedBoard | null>;
9
+ static put(boardId: string, name: string, model: string, updatedAt: string): Promise<void>;
10
+ static isValid(boardId: string, serverUpdatedAt: string): Promise<boolean>;
11
+ static remove(boardId: string): Promise<void>;
12
+ static clear(): Promise<void>;
13
+ }
@@ -0,0 +1,66 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * BoardModelCache — 보드 모델 JSON을 IndexedDB에 캐싱.
5
+ *
6
+ * 캐시 히트 시 네트워크 대기 없이 즉시 scene 생성 가능.
7
+ * updatedAt 비교로 캐시 무효화.
8
+ */
9
+ import Dexie from 'dexie';
10
+ class BoardModelDatabase extends Dexie {
11
+ constructor() {
12
+ super('board-model-cache');
13
+ this.version(1).stores({
14
+ board_models: 'id'
15
+ });
16
+ }
17
+ }
18
+ const db = new BoardModelDatabase();
19
+ export class BoardModelCache {
20
+ static async get(boardId) {
21
+ try {
22
+ const record = await db.board_models.get(boardId);
23
+ if (!record)
24
+ return null;
25
+ return {
26
+ id: record.id,
27
+ name: record.name,
28
+ model: JSON.parse(record.model),
29
+ updatedAt: record.updatedAt
30
+ };
31
+ }
32
+ catch (_a) {
33
+ return null;
34
+ }
35
+ }
36
+ static async put(boardId, name, model, updatedAt) {
37
+ try {
38
+ await db.board_models.put({ id: boardId, name, model, updatedAt, cachedAt: Date.now() });
39
+ }
40
+ catch (_a) {
41
+ // 캐시 저장 실패는 무시
42
+ }
43
+ }
44
+ static async isValid(boardId, serverUpdatedAt) {
45
+ try {
46
+ const record = await db.board_models.get(boardId);
47
+ return (record === null || record === void 0 ? void 0 : record.updatedAt) === serverUpdatedAt;
48
+ }
49
+ catch (_a) {
50
+ return false;
51
+ }
52
+ }
53
+ static async remove(boardId) {
54
+ try {
55
+ await db.board_models.delete(boardId);
56
+ }
57
+ catch ( /* ignore */_a) { /* ignore */ }
58
+ }
59
+ static async clear() {
60
+ try {
61
+ await db.board_models.clear();
62
+ }
63
+ catch ( /* ignore */_a) { /* ignore */ }
64
+ }
65
+ }
66
+ //# sourceMappingURL=board-model-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-model-cache.js","sourceRoot":"","sources":["../../../src/data-storage/board-model-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,MAAM,kBAAmB,SAAQ,KAAK;IAGpC;QACE,KAAK,CAAC,mBAAmB,CAAC,CAAA;QAC1B,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACrB,YAAY,EAAE,IAAI;SACnB,CAAC,CAAA;IACJ,CAAC;CACF;AAED,MAAM,EAAE,GAAG,IAAI,kBAAkB,EAAE,CAAA;AASnC,MAAM,OAAO,eAAe;IAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAe;QAC9B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACjD,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAA;YACxB,OAAO;gBACL,EAAE,EAAE,MAAM,CAAC,EAAE;gBACb,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAA;QACH,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAe,EAAE,IAAY,EAAE,KAAa,EAAE,SAAiB;QAC9E,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QAC1F,CAAC;QAAC,WAAM,CAAC;YACP,eAAe;QACjB,CAAC;IACH,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAe,EAAE,eAAuB;QAC3D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACjD,OAAO,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,SAAS,MAAK,eAAe,CAAA;QAC9C,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAe;QACjC,IAAI,CAAC;YAAC,MAAM,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAAC,CAAC;QAAC,QAAQ,YAAY,IAAd,CAAC,CAAC,YAAY,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,KAAK;QAChB,IAAI,CAAC;YAAC,MAAM,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,CAAA;QAAC,CAAC;QAAC,QAAQ,YAAY,IAAd,CAAC,CAAC,YAAY,CAAC,CAAC;IAC9D,CAAC;CACF","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * BoardModelCache — 보드 모델 JSON을 IndexedDB에 캐싱.\n *\n * 캐시 히트 시 네트워크 대기 없이 즉시 scene 생성 가능.\n * updatedAt 비교로 캐시 무효화.\n */\n\nimport Dexie from 'dexie'\n\ninterface IBoardModelRecord {\n id: string\n name: string\n model: string // JSON string (파싱 전)\n updatedAt: string\n cachedAt: number\n}\n\nclass BoardModelDatabase extends Dexie {\n board_models!: Dexie.Table<IBoardModelRecord, string>\n\n constructor() {\n super('board-model-cache')\n this.version(1).stores({\n board_models: 'id'\n })\n }\n}\n\nconst db = new BoardModelDatabase()\n\nexport interface CachedBoard {\n id: string\n name: string\n model: any\n updatedAt: string\n}\n\nexport class BoardModelCache {\n static async get(boardId: string): Promise<CachedBoard | null> {\n try {\n const record = await db.board_models.get(boardId)\n if (!record) return null\n return {\n id: record.id,\n name: record.name,\n model: JSON.parse(record.model),\n updatedAt: record.updatedAt\n }\n } catch {\n return null\n }\n }\n\n static async put(boardId: string, name: string, model: string, updatedAt: string): Promise<void> {\n try {\n await db.board_models.put({ id: boardId, name, model, updatedAt, cachedAt: Date.now() })\n } catch {\n // 캐시 저장 실패는 무시\n }\n }\n\n static async isValid(boardId: string, serverUpdatedAt: string): Promise<boolean> {\n try {\n const record = await db.board_models.get(boardId)\n return record?.updatedAt === serverUpdatedAt\n } catch {\n return false\n }\n }\n\n static async remove(boardId: string): Promise<void> {\n try { await db.board_models.delete(boardId) } catch { /* ignore */ }\n }\n\n static async clear(): Promise<void> {\n try { await db.board_models.clear() } catch { /* ignore */ }\n }\n}\n"]}
@@ -1,6 +1,9 @@
1
1
  export * from './types.js';
2
2
  export { registerDefaultGroups } from './component/register-default-groups.js';
3
3
  export { DataStorage } from './data-storage/data-storage.js';
4
+ export { PlaybackEngine } from './playback/playback-engine.js';
5
+ export { BoardModelCache } from './data-storage/board-model-cache.js';
6
+ export type { CachedBoard } from './data-storage/board-model-cache.js';
4
7
  export * from './ox-board-viewer.js';
5
8
  export * from './ox-board-modeller.js';
6
9
  export * from './ox-board-list.js';