@fmsim/board 2.0.0-beta.10 → 2.0.0-beta.101

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 (112) hide show
  1. package/custom-elements.json +7445 -3377
  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 +36 -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/mcs-mouse-event-handler.js +8 -0
  21. package/dist/src/layers/mcs-mouse-event-handler.js.map +1 -1
  22. package/dist/src/layers/movement-layer.d.ts +4 -4
  23. package/dist/src/layers/movement-layer.js +16 -8
  24. package/dist/src/layers/movement-layer.js.map +1 -1
  25. package/dist/src/modeller/bulk-create-dialog.d.ts +51 -0
  26. package/dist/src/modeller/bulk-create-dialog.js +531 -0
  27. package/dist/src/modeller/bulk-create-dialog.js.map +1 -0
  28. package/dist/src/modeller/bulk-create.d.ts +101 -0
  29. package/dist/src/modeller/bulk-create.js +199 -0
  30. package/dist/src/modeller/bulk-create.js.map +1 -0
  31. package/dist/src/modeller/component-toolbar/component-menu.d.ts +1 -1
  32. package/dist/src/modeller/component-toolbar/component-toolbar.d.ts +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 +10 -1
  40. package/dist/src/modeller/property-sidebar/effects/effects.js +66 -1
  41. package/dist/src/modeller/property-sidebar/effects/effects.js.map +1 -1
  42. package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.d.ts +44 -0
  43. package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js +70 -0
  44. package/dist/src/modeller/property-sidebar/effects/event-handler-adapter.js.map +1 -0
  45. package/dist/src/modeller/property-sidebar/effects/property-event.d.ts +13 -3
  46. package/dist/src/modeller/property-sidebar/effects/property-event.js +20 -11
  47. package/dist/src/modeller/property-sidebar/effects/property-event.js.map +1 -1
  48. package/dist/src/modeller/property-sidebar/inspector/inspector-controller.d.ts +62 -0
  49. package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js +276 -0
  50. package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js.map +1 -0
  51. package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.d.ts +15 -0
  52. package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js +56 -0
  53. package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js.map +1 -0
  54. package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.d.ts +7 -0
  55. package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js +25 -0
  56. package/dist/src/modeller/property-sidebar/inspector/inspector-highlight.js.map +1 -0
  57. package/dist/src/modeller/property-sidebar/inspector/inspector-model.d.ts +44 -0
  58. package/dist/src/modeller/property-sidebar/inspector/inspector-model.js +72 -0
  59. package/dist/src/modeller/property-sidebar/inspector/inspector-model.js.map +1 -0
  60. package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.d.ts +18 -0
  61. package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js +61 -0
  62. package/dist/src/modeller/property-sidebar/inspector/inspector-tree-view.js.map +1 -0
  63. package/dist/src/modeller/property-sidebar/inspector/inspector.d.ts +17 -12
  64. package/dist/src/modeller/property-sidebar/inspector/inspector.js +281 -151
  65. package/dist/src/modeller/property-sidebar/inspector/inspector.js.map +1 -1
  66. package/dist/src/modeller/property-sidebar/property-sidebar.d.ts +1 -1
  67. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.d.ts +75 -0
  68. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js +346 -0
  69. package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js.map +1 -0
  70. package/dist/src/modeller/property-sidebar/shapes/shapes.d.ts +22 -1
  71. package/dist/src/modeller/property-sidebar/shapes/shapes.js +94 -2
  72. package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
  73. package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +25 -1
  74. package/dist/src/modeller/property-sidebar/specifics/specifics.js +112 -2
  75. package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
  76. package/dist/src/modeller/property-sidebar/styles/styles.d.ts +1 -1
  77. package/dist/src/modeller/scene-viewer/ox-scene-layer.d.ts +4 -3
  78. package/dist/src/modeller/scene-viewer/ox-scene-layer.js +2 -1
  79. package/dist/src/modeller/scene-viewer/ox-scene-layer.js.map +1 -1
  80. package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +2 -1
  81. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +5 -1
  82. package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
  83. package/dist/src/ox-board-component-info.d.ts +1 -0
  84. package/dist/src/ox-board-component-info.js +42 -2
  85. package/dist/src/ox-board-component-info.js.map +1 -1
  86. package/dist/src/ox-board-modeller.d.ts +6 -3
  87. package/dist/src/ox-board-modeller.js +14 -1
  88. package/dist/src/ox-board-modeller.js.map +1 -1
  89. package/dist/src/ox-board-viewer.d.ts +125 -13
  90. package/dist/src/ox-board-viewer.js +787 -97
  91. package/dist/src/ox-board-viewer.js.map +1 -1
  92. package/dist/src/ox-editor-board-selector.d.ts +3 -0
  93. package/dist/src/ox-editor-board-selector.js +38 -9
  94. package/dist/src/ox-editor-board-selector.js.map +1 -1
  95. package/dist/src/ox-playback-controls.d.ts +157 -0
  96. package/dist/src/ox-playback-controls.js +1089 -0
  97. package/dist/src/ox-playback-controls.js.map +1 -0
  98. package/dist/src/playback/carrier-tracking-store.d.ts +51 -0
  99. package/dist/src/playback/carrier-tracking-store.js +115 -0
  100. package/dist/src/playback/carrier-tracking-store.js.map +1 -0
  101. package/dist/src/playback/playback-engine.d.ts +106 -0
  102. package/dist/src/playback/playback-engine.js +395 -0
  103. package/dist/src/playback/playback-engine.js.map +1 -0
  104. package/dist/src/selector/ox-board-selector.js +12 -1
  105. package/dist/src/selector/ox-board-selector.js.map +1 -1
  106. package/dist/tsconfig.tsbuildinfo +1 -1
  107. package/icons/components/text.png +0 -0
  108. package/icons/fab/mode-2d.svg +25 -0
  109. package/icons/fab/mode-3d.svg +25 -0
  110. package/icons/fab/playback-play.svg +49 -0
  111. package/icons/fab/playback-stop.svg +50 -0
  112. 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';