@operato/property-panel 10.0.0-beta.3 → 10.0.0-beta.5

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [10.0.0-beta.5](https://github.com/hatiolab/operato/compare/v10.0.0-beta.4...v10.0.0-beta.5) (2026-03-08)
7
+
8
+
9
+ ### :rocket: New Features
10
+
11
+ * **property-panel:** camera bookmark UI and autoPlay setting in 3D tab ([6508575](https://github.com/hatiolab/operato/commit/6508575f1011257304214c279353701099cf9a3f))
12
+
13
+
14
+
6
15
  ## [10.0.0-beta.3](https://github.com/hatiolab/operato/compare/v10.0.0-beta.2...v10.0.0-beta.3) (2026-03-06)
7
16
 
8
17
 
@@ -16,6 +16,12 @@ export declare class PropertyScene3D extends AbstractProperty {
16
16
  render(): import("lit-html").TemplateResult<1>;
17
17
  private _renderMode;
18
18
  private _renderCamera;
19
+ private _renderBookmarks;
20
+ private _longPressTimer?;
21
+ private _pressTarget?;
22
+ private _onBookmarkMouseDown;
23
+ private _onBookmarkMouseUp;
24
+ private _bookmarkAction;
19
25
  private _renderRenderer;
20
26
  private _renderSky;
21
27
  private _renderHemisphereLight;
@@ -72,8 +72,16 @@ export class PropertyScene3D extends AbstractProperty {
72
72
  <input id="cb-threed" type="checkbox" value-key="threed" .checked=${!!value.threed} />
73
73
  <label for="cb-threed">Enable 3D</label>
74
74
 
75
- <input id="cb-autorotate" type="checkbox" value-key="autoRotate" .checked=${!!value.autoRotate} />
76
- <label for="cb-autorotate">Auto Rotate</label>
75
+ <label>Viewer Auto</label>
76
+ <select value-key="cameraAutoPlay">
77
+ ${[
78
+ { value: '', label: 'None' },
79
+ { value: 'orbit', label: 'Orbit' },
80
+ { value: 'play', label: 'Play Bookmarks' }
81
+ ].map(o => html `<option value=${o.value} ?selected=${(value.cameraAutoPlay || '') === o.value}>
82
+ ${o.label}
83
+ </option>`)}
84
+ </select>
77
85
  </div>
78
86
  </fieldset>
79
87
  `;
@@ -122,8 +130,86 @@ export class PropertyScene3D extends AbstractProperty {
122
130
  <input class="half-editor" type="number" value-key="far" .value=${String((_d = value.far) !== null && _d !== void 0 ? _d : 20000)} />
123
131
  </div>
124
132
  </fieldset>
133
+
134
+ ${this._renderBookmarks(value)}
135
+ `;
136
+ }
137
+ _renderBookmarks(value) {
138
+ const bookmarks = value.cameraBookmarks || [];
139
+ return html `
140
+ <fieldset>
141
+ <legend>Bookmarks</legend>
142
+ <div class="property-grid">
143
+ <div class="bookmark-slots">
144
+ ${[1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => {
145
+ const filled = !!bookmarks[i];
146
+ return html `
147
+ <div
148
+ class="bookmark-slot ${filled ? 'filled' : ''}"
149
+ title=${filled ? 'Click: Go / Long-click: Overwrite / Right-click: Delete' : 'Long-click: Save current camera'}
150
+ @click=${(e) => { if (filled) {
151
+ this._bookmarkAction('navigate', i);
152
+ const t = e.currentTarget;
153
+ t.classList.add('saved');
154
+ setTimeout(() => t.classList.remove('saved'), 300);
155
+ } }}
156
+ @contextmenu=${(e) => { e.preventDefault(); if (filled)
157
+ this._bookmarkAction('clear', i); }}
158
+ @mousedown=${(e) => this._onBookmarkMouseDown(e, i)}
159
+ @mouseup=${() => this._onBookmarkMouseUp()}
160
+ @mouseleave=${() => this._onBookmarkMouseUp()}
161
+ >${i}</div>
162
+ `;
163
+ })}
164
+ </div>
165
+ </div>
166
+ </fieldset>
125
167
  `;
126
168
  }
169
+ _onBookmarkMouseDown(e, index) {
170
+ if (e.button !== 0)
171
+ return;
172
+ const target = e.currentTarget;
173
+ this._pressTarget = target;
174
+ // 롱클릭 시작: filling 애니메이션
175
+ target.classList.add('saving');
176
+ this._longPressTimer = setTimeout(() => {
177
+ this._longPressTimer = undefined;
178
+ target.classList.remove('saving');
179
+ this._bookmarkAction('save', index);
180
+ // 저장 완료 피드백
181
+ target.classList.add('saved');
182
+ setTimeout(() => target.classList.remove('saved'), 300);
183
+ }, 600);
184
+ }
185
+ _onBookmarkMouseUp() {
186
+ if (this._longPressTimer) {
187
+ clearTimeout(this._longPressTimer);
188
+ this._longPressTimer = undefined;
189
+ }
190
+ if (this._pressTarget) {
191
+ this._pressTarget.classList.remove('saving');
192
+ this._pressTarget = undefined;
193
+ }
194
+ }
195
+ _bookmarkAction(action, index) {
196
+ var _a;
197
+ const root = (_a = this.scene) === null || _a === void 0 ? void 0 : _a.root;
198
+ const cap = root === null || root === void 0 ? void 0 : root._threeCapability;
199
+ if (!cap)
200
+ return;
201
+ if (action === 'save') {
202
+ cap.saveCameraToSlot(index);
203
+ }
204
+ else if (action === 'navigate') {
205
+ cap.animateToSlot(index);
206
+ }
207
+ else if (action === 'clear') {
208
+ cap.bookmarkManager.clearSlot(index);
209
+ root.set('cameraBookmarks', cap.bookmarkManager.exportSlots());
210
+ }
211
+ this.requestUpdate();
212
+ }
127
213
  _renderRenderer(value) {
128
214
  return html `
129
215
  <fieldset>
@@ -315,6 +401,64 @@ PropertyScene3D.styles = [
315
401
  font-size: 11px;
316
402
  color: var(--md-sys-color-on-secondary-container);
317
403
  }
404
+
405
+ .bookmark-slots {
406
+ grid-column: 1 / -1;
407
+ display: grid;
408
+ grid-template-columns: repeat(9, 1fr);
409
+ gap: 2px;
410
+ padding: 4px 0 0;
411
+ }
412
+
413
+ .bookmark-slot {
414
+ display: flex;
415
+ align-items: center;
416
+ justify-content: center;
417
+ height: 24px;
418
+ border-radius: 3px;
419
+ font-size: 11px;
420
+ font-weight: 600;
421
+ cursor: pointer;
422
+ color: var(--md-sys-color-outline, #999);
423
+ background: var(--md-sys-color-surface-variant, #f0f0f0);
424
+ border: 1px solid transparent;
425
+ user-select: none;
426
+ transition: all 0.12s ease;
427
+ }
428
+
429
+ .bookmark-slot:hover {
430
+ background: var(--md-sys-color-secondary-container, #e0e0e0);
431
+ }
432
+
433
+ .bookmark-slot.filled {
434
+ color: var(--md-sys-color-primary, #6750A4);
435
+ background: var(--md-sys-color-primary-container, #EADDFF);
436
+ border-color: var(--md-sys-color-primary, #6750A4);
437
+ font-weight: 700;
438
+ }
439
+
440
+ .bookmark-slot:active {
441
+ transform: scale(0.9);
442
+ }
443
+
444
+ .bookmark-slot.saving {
445
+ animation: bookmark-fill 0.6s linear forwards;
446
+ }
447
+
448
+ @keyframes bookmark-fill {
449
+ from { box-shadow: inset 24px 0 0 0 transparent; }
450
+ to { box-shadow: inset 24px 0 0 0 var(--md-sys-color-primary-container, #EADDFF); }
451
+ }
452
+
453
+ .bookmark-slot.saved {
454
+ animation: bookmark-saved 0.3s ease;
455
+ }
456
+
457
+ @keyframes bookmark-saved {
458
+ 0% { transform: scale(1); }
459
+ 50% { transform: scale(1.2); }
460
+ 100% { transform: scale(1); }
461
+ }
318
462
  `
319
463
  ];
320
464
  __decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"property-scene3d.js","sourceRoot":"","sources":["../../../../src/property-panel/threed/property-scene3d.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@operato/input/ox-input-color.js'\n\nimport './property-material3d.js'\n\nimport { css, html } from 'lit'\nimport { property } from 'lit/decorators.js'\n\nimport { Properties, Scene } from '@hatiolab/things-scene'\nimport { PropertyGridStyles } from '@operato/styles/property-grid-styles.js'\n\nimport { AbstractProperty } from '../abstract-property.js'\n\nconst LIGHTING_PRESETS: Record<string, Record<string, unknown>> = {\n Neutral: {\n hemiIntensity: 0.6,\n dirLightEnabled: true,\n dirLightColor: '#ffffff',\n dirLightIntensity: 0.5\n },\n Studio: {\n hemiIntensity: 0.5,\n dirLightEnabled: true,\n dirLightColor: '#ffffff',\n dirLightIntensity: 0.6\n },\n Bright: {\n hemiIntensity: 0.8,\n dirLightEnabled: true,\n dirLightColor: '#ffffff',\n dirLightIntensity: 0.7\n },\n Warm: {\n hemiIntensity: 0.5,\n dirLightEnabled: true,\n dirLightColor: '#ffcc88',\n dirLightIntensity: 0.4\n },\n Cool: {\n hemiIntensity: 0.5,\n dirLightEnabled: true,\n dirLightColor: '#cce0ff',\n dirLightIntensity: 0.4\n },\n Flat: {\n hemiIntensity: 0.8,\n dirLightEnabled: false,\n dirLightColor: '#ffffff',\n dirLightIntensity: 0\n }\n}\n\n/**\n * Scene-level 3D settings for model-layer.\n * Includes 3D mode, camera, renderer, lighting, presets, and floor configuration.\n */\nexport class PropertyScene3D extends AbstractProperty {\n static styles = [\n PropertyGridStyles,\n css`\n .preset-buttons {\n grid-column: 1 / -1;\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n padding: 2px 0;\n }\n\n .preset-buttons button {\n flex: 1;\n min-width: 60px;\n padding: 4px 6px;\n border: 1px solid var(--md-sys-color-outline, #ccc);\n border-radius: 4px;\n background: var(--md-sys-color-surface, #fff);\n color: var(--md-sys-color-on-surface, #333);\n font-size: 11px;\n cursor: pointer;\n }\n\n .preset-buttons button:hover {\n background: var(--md-sys-color-primary-container, #e0e0e0);\n }\n\n .range-with-value {\n grid-column: 9 / -1;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .range-with-value input[type='range'] {\n flex: 1;\n border: none;\n background: transparent;\n padding: 0;\n }\n\n .range-with-value span {\n min-width: 2.5em;\n text-align: right;\n font-size: 11px;\n color: var(--md-sys-color-on-secondary-container);\n }\n `\n ]\n\n @property({ type: Object }) value?: Properties\n @property({ type: Object }) scene: Scene | null = null\n\n render() {\n const value = this.value || {}\n\n return html`\n ${this._renderMode(value)} ${this._renderCamera(value)} ${this._renderRenderer(value)}\n ${this._renderSky(value)}\n ${this._renderHemisphereLight(value)} ${this._renderKeyLight(value)} ${this._renderLightingPresets()}\n ${this._renderFloor(value)}\n `\n }\n\n private _renderMode(value: Properties) {\n return html`\n <fieldset>\n <legend>3D Mode</legend>\n <div class=\"property-grid\">\n <input id=\"cb-threed\" type=\"checkbox\" value-key=\"threed\" .checked=${!!value.threed} />\n <label for=\"cb-threed\">Enable 3D</label>\n\n <input id=\"cb-autorotate\" type=\"checkbox\" value-key=\"autoRotate\" .checked=${!!value.autoRotate} />\n <label for=\"cb-autorotate\">Auto Rotate</label>\n </div>\n </fieldset>\n `\n }\n\n private _renderCamera(value: Properties) {\n return html`\n <fieldset>\n <legend>Camera</legend>\n <div class=\"property-grid\">\n <label>View</label>\n <select value-key=\"initialCameraView\">\n ${['perspective', 'top', 'front', 'back', 'right', 'left'].map(\n v =>\n html`<option value=${v} ?selected=${(value.initialCameraView || 'perspective') === v}>\n ${v.charAt(0).toUpperCase() + v.slice(1)}\n </option>`\n )}\n </select>\n\n <label class=\"half-label\">FOV</label>\n <input\n class=\"half-editor\"\n type=\"number\"\n value-key=\"fov\"\n min=\"10\"\n max=\"120\"\n .value=${String(value.fov ?? 45)}\n />\n <label class=\"half-label\">Zoom</label>\n <input\n class=\"half-editor\"\n type=\"number\"\n value-key=\"zoom\"\n min=\"100\"\n .value=${String(value.zoom ?? 100)}\n />\n\n <label class=\"half-label\">Near</label>\n <input\n class=\"half-editor\"\n type=\"number\"\n value-key=\"near\"\n min=\"0.01\"\n step=\"0.01\"\n .value=${String(value.near ?? 0.1)}\n />\n <label class=\"half-label\">Far</label>\n <input class=\"half-editor\" type=\"number\" value-key=\"far\" .value=${String(value.far ?? 20000)} />\n </div>\n </fieldset>\n `\n }\n\n private _renderRenderer(value: Properties) {\n return html`\n <fieldset>\n <legend>Renderer</legend>\n <div class=\"property-grid\">\n <input id=\"cb-antialias\" type=\"checkbox\" value-key=\"antialias\" .checked=${value.antialias !== false} />\n <label for=\"cb-antialias\">Anti-alias</label>\n\n <label>Precision</label>\n <select value-key=\"precision\">\n ${[\n { value: 'highp', label: 'High' },\n { value: 'mediump', label: 'Medium' },\n { value: 'lowp', label: 'Low' }\n ].map(\n o =>\n html`<option value=${o.value} ?selected=${(value.precision || 'highp') === o.value}>\n ${o.label}\n </option>`\n )}\n </select>\n </div>\n </fieldset>\n `\n }\n\n private _renderSky(value: Properties) {\n const skyOptions = [\n { value: '', label: 'None' },\n { value: 'color', label: 'Color' },\n { value: 'studio', label: 'Studio' },\n { value: 'warehouse', label: 'Warehouse' },\n { value: 'factory', label: 'Factory' },\n { value: 'office', label: 'Office' },\n { value: 'home', label: 'Home' },\n ]\n\n return html`\n <fieldset>\n <legend>Sky</legend>\n <div class=\"property-grid\">\n <label>Preset</label>\n <select value-key=\"sky\">\n ${skyOptions.map(\n o => html`<option value=${o.value} ?selected=${(value.sky || '') === o.value}>${o.label}</option>`\n )}\n </select>\n\n ${value.sky === 'color'\n ? html`\n <label>Color</label>\n <ox-input-color value-key=\"skyColor\" .value=${value.skyColor || '#87ceeb'}> </ox-input-color>\n `\n : ''}\n </div>\n </fieldset>\n `\n }\n\n private _renderHemisphereLight(value: Properties) {\n return html`\n <fieldset>\n <legend>Hemisphere Light</legend>\n <div class=\"property-grid\">\n <label>Intensity</label>\n <div class=\"range-with-value\">\n <input\n type=\"range\"\n min=\"0\"\n max=\"3\"\n step=\"0.1\"\n value-key=\"hemiIntensity\"\n .value=${String(value.hemiIntensity ?? 0.6)}\n />\n <span>${(value.hemiIntensity ?? 0.6).toFixed(1)}</span>\n </div>\n </div>\n </fieldset>\n `\n }\n\n private _renderKeyLight(value: Properties) {\n return html`\n <fieldset>\n <legend>Key Light</legend>\n <div class=\"property-grid\">\n <input\n id=\"cb-dirlight\"\n type=\"checkbox\"\n value-key=\"dirLightEnabled\"\n .checked=${value.dirLightEnabled !== false}\n />\n <label for=\"cb-dirlight\">Enabled</label>\n\n <label>Color</label>\n <ox-input-color value-key=\"dirLightColor\" .value=${value.dirLightColor || '#ffffff'}> </ox-input-color>\n\n <label>Intensity</label>\n <div class=\"range-with-value\">\n <input\n type=\"range\"\n min=\"0\"\n max=\"3\"\n step=\"0.1\"\n value-key=\"dirLightIntensity\"\n .value=${String(value.dirLightIntensity ?? 0.5)}\n />\n <span>${(value.dirLightIntensity ?? 0.5).toFixed(1)}</span>\n </div>\n </div>\n </fieldset>\n `\n }\n\n private _renderLightingPresets() {\n return html`\n <fieldset>\n <legend>Lighting Presets</legend>\n <div class=\"property-grid\">\n <div class=\"preset-buttons\">\n ${Object.entries(LIGHTING_PRESETS).map(\n ([name, values]) =>\n html`<button @click=${() => this._applyLightingPreset(values)}>${name}</button>`\n )}\n </div>\n </div>\n </fieldset>\n `\n }\n\n private _renderFloor(value: Properties) {\n return html`\n <property-material3d\n legend=\"Floor Material\"\n .value=${value.floorMaterial3d || { receiveShadow: true }}\n @property-change=${this._onFloorMaterialChange}\n >\n </property-material3d>\n `\n }\n\n private _applyLightingPreset(values: Record<string, unknown>) {\n this.dispatchEvent(\n new CustomEvent('property-change', {\n bubbles: true,\n composed: true,\n detail: values\n })\n )\n }\n\n private _onFloorMaterialChange(e: CustomEvent) {\n e.stopPropagation()\n this.dispatchEvent(\n new CustomEvent('property-change', {\n bubbles: true,\n composed: true,\n detail: { floorMaterial3d: e.detail.material3d }\n })\n )\n }\n\n}\n"]}
1
+ {"version":3,"file":"property-scene3d.js","sourceRoot":"","sources":["../../../../src/property-panel/threed/property-scene3d.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@operato/input/ox-input-color.js'\n\nimport './property-material3d.js'\n\nimport { css, html } from 'lit'\nimport { property } from 'lit/decorators.js'\n\nimport { Properties, Scene } from '@hatiolab/things-scene'\nimport { PropertyGridStyles } from '@operato/styles/property-grid-styles.js'\n\nimport { AbstractProperty } from '../abstract-property.js'\n\nconst LIGHTING_PRESETS: Record<string, Record<string, unknown>> = {\n Neutral: {\n hemiIntensity: 0.6,\n dirLightEnabled: true,\n dirLightColor: '#ffffff',\n dirLightIntensity: 0.5\n },\n Studio: {\n hemiIntensity: 0.5,\n dirLightEnabled: true,\n dirLightColor: '#ffffff',\n dirLightIntensity: 0.6\n },\n Bright: {\n hemiIntensity: 0.8,\n dirLightEnabled: true,\n dirLightColor: '#ffffff',\n dirLightIntensity: 0.7\n },\n Warm: {\n hemiIntensity: 0.5,\n dirLightEnabled: true,\n dirLightColor: '#ffcc88',\n dirLightIntensity: 0.4\n },\n Cool: {\n hemiIntensity: 0.5,\n dirLightEnabled: true,\n dirLightColor: '#cce0ff',\n dirLightIntensity: 0.4\n },\n Flat: {\n hemiIntensity: 0.8,\n dirLightEnabled: false,\n dirLightColor: '#ffffff',\n dirLightIntensity: 0\n }\n}\n\n/**\n * Scene-level 3D settings for model-layer.\n * Includes 3D mode, camera, renderer, lighting, presets, and floor configuration.\n */\nexport class PropertyScene3D extends AbstractProperty {\n static styles = [\n PropertyGridStyles,\n css`\n .preset-buttons {\n grid-column: 1 / -1;\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n padding: 2px 0;\n }\n\n .preset-buttons button {\n flex: 1;\n min-width: 60px;\n padding: 4px 6px;\n border: 1px solid var(--md-sys-color-outline, #ccc);\n border-radius: 4px;\n background: var(--md-sys-color-surface, #fff);\n color: var(--md-sys-color-on-surface, #333);\n font-size: 11px;\n cursor: pointer;\n }\n\n .preset-buttons button:hover {\n background: var(--md-sys-color-primary-container, #e0e0e0);\n }\n\n .range-with-value {\n grid-column: 9 / -1;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .range-with-value input[type='range'] {\n flex: 1;\n border: none;\n background: transparent;\n padding: 0;\n }\n\n .range-with-value span {\n min-width: 2.5em;\n text-align: right;\n font-size: 11px;\n color: var(--md-sys-color-on-secondary-container);\n }\n\n .bookmark-slots {\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: repeat(9, 1fr);\n gap: 2px;\n padding: 4px 0 0;\n }\n\n .bookmark-slot {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 24px;\n border-radius: 3px;\n font-size: 11px;\n font-weight: 600;\n cursor: pointer;\n color: var(--md-sys-color-outline, #999);\n background: var(--md-sys-color-surface-variant, #f0f0f0);\n border: 1px solid transparent;\n user-select: none;\n transition: all 0.12s ease;\n }\n\n .bookmark-slot:hover {\n background: var(--md-sys-color-secondary-container, #e0e0e0);\n }\n\n .bookmark-slot.filled {\n color: var(--md-sys-color-primary, #6750A4);\n background: var(--md-sys-color-primary-container, #EADDFF);\n border-color: var(--md-sys-color-primary, #6750A4);\n font-weight: 700;\n }\n\n .bookmark-slot:active {\n transform: scale(0.9);\n }\n\n .bookmark-slot.saving {\n animation: bookmark-fill 0.6s linear forwards;\n }\n\n @keyframes bookmark-fill {\n from { box-shadow: inset 24px 0 0 0 transparent; }\n to { box-shadow: inset 24px 0 0 0 var(--md-sys-color-primary-container, #EADDFF); }\n }\n\n .bookmark-slot.saved {\n animation: bookmark-saved 0.3s ease;\n }\n\n @keyframes bookmark-saved {\n 0% { transform: scale(1); }\n 50% { transform: scale(1.2); }\n 100% { transform: scale(1); }\n }\n `\n ]\n\n @property({ type: Object }) value?: Properties\n @property({ type: Object }) scene: Scene | null = null\n\n render() {\n const value = this.value || {}\n\n return html`\n ${this._renderMode(value)} ${this._renderCamera(value)} ${this._renderRenderer(value)}\n ${this._renderSky(value)}\n ${this._renderHemisphereLight(value)} ${this._renderKeyLight(value)} ${this._renderLightingPresets()}\n ${this._renderFloor(value)}\n `\n }\n\n private _renderMode(value: Properties) {\n return html`\n <fieldset>\n <legend>3D Mode</legend>\n <div class=\"property-grid\">\n <input id=\"cb-threed\" type=\"checkbox\" value-key=\"threed\" .checked=${!!value.threed} />\n <label for=\"cb-threed\">Enable 3D</label>\n\n <label>Viewer Auto</label>\n <select value-key=\"cameraAutoPlay\">\n ${[\n { value: '', label: 'None' },\n { value: 'orbit', label: 'Orbit' },\n { value: 'play', label: 'Play Bookmarks' }\n ].map(\n o =>\n html`<option value=${o.value} ?selected=${(value.cameraAutoPlay || '') === o.value}>\n ${o.label}\n </option>`\n )}\n </select>\n </div>\n </fieldset>\n `\n }\n\n private _renderCamera(value: Properties) {\n return html`\n <fieldset>\n <legend>Camera</legend>\n <div class=\"property-grid\">\n <label>View</label>\n <select value-key=\"initialCameraView\">\n ${['perspective', 'top', 'front', 'back', 'right', 'left'].map(\n v =>\n html`<option value=${v} ?selected=${(value.initialCameraView || 'perspective') === v}>\n ${v.charAt(0).toUpperCase() + v.slice(1)}\n </option>`\n )}\n </select>\n\n <label class=\"half-label\">FOV</label>\n <input\n class=\"half-editor\"\n type=\"number\"\n value-key=\"fov\"\n min=\"10\"\n max=\"120\"\n .value=${String(value.fov ?? 45)}\n />\n <label class=\"half-label\">Zoom</label>\n <input\n class=\"half-editor\"\n type=\"number\"\n value-key=\"zoom\"\n min=\"100\"\n .value=${String(value.zoom ?? 100)}\n />\n\n <label class=\"half-label\">Near</label>\n <input\n class=\"half-editor\"\n type=\"number\"\n value-key=\"near\"\n min=\"0.01\"\n step=\"0.01\"\n .value=${String(value.near ?? 0.1)}\n />\n <label class=\"half-label\">Far</label>\n <input class=\"half-editor\" type=\"number\" value-key=\"far\" .value=${String(value.far ?? 20000)} />\n </div>\n </fieldset>\n\n ${this._renderBookmarks(value)}\n `\n }\n\n private _renderBookmarks(value: Properties) {\n const bookmarks: (any | null)[] = value.cameraBookmarks || []\n\n return html`\n <fieldset>\n <legend>Bookmarks</legend>\n <div class=\"property-grid\">\n <div class=\"bookmark-slots\">\n ${[1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => {\n const filled = !!bookmarks[i]\n return html`\n <div\n class=\"bookmark-slot ${filled ? 'filled' : ''}\"\n title=${filled ? 'Click: Go / Long-click: Overwrite / Right-click: Delete' : 'Long-click: Save current camera'}\n @click=${(e: MouseEvent) => { if (filled) { this._bookmarkAction('navigate', i); const t = e.currentTarget as HTMLElement; t.classList.add('saved'); setTimeout(() => t.classList.remove('saved'), 300) } }}\n @contextmenu=${(e: Event) => { e.preventDefault(); if (filled) this._bookmarkAction('clear', i) }}\n @mousedown=${(e: MouseEvent) => this._onBookmarkMouseDown(e, i)}\n @mouseup=${() => this._onBookmarkMouseUp()}\n @mouseleave=${() => this._onBookmarkMouseUp()}\n >${i}</div>\n `\n })}\n </div>\n </div>\n </fieldset>\n `\n }\n\n private _longPressTimer?: ReturnType<typeof setTimeout>\n private _pressTarget?: HTMLElement\n\n private _onBookmarkMouseDown(e: MouseEvent, index: number) {\n if (e.button !== 0) return\n const target = e.currentTarget as HTMLElement\n this._pressTarget = target\n\n // 롱클릭 시작: filling 애니메이션\n target.classList.add('saving')\n\n this._longPressTimer = setTimeout(() => {\n this._longPressTimer = undefined\n target.classList.remove('saving')\n this._bookmarkAction('save', index)\n\n // 저장 완료 피드백\n target.classList.add('saved')\n setTimeout(() => target.classList.remove('saved'), 300)\n }, 600)\n }\n\n private _onBookmarkMouseUp() {\n if (this._longPressTimer) {\n clearTimeout(this._longPressTimer)\n this._longPressTimer = undefined\n }\n if (this._pressTarget) {\n this._pressTarget.classList.remove('saving')\n this._pressTarget = undefined\n }\n }\n\n private _bookmarkAction(action: 'save' | 'navigate' | 'clear', index: number) {\n const root = this.scene?.root as any\n const cap = root?._threeCapability\n if (!cap) return\n\n if (action === 'save') {\n cap.saveCameraToSlot(index)\n } else if (action === 'navigate') {\n cap.animateToSlot(index)\n } else if (action === 'clear') {\n cap.bookmarkManager.clearSlot(index)\n root.set('cameraBookmarks', cap.bookmarkManager.exportSlots())\n }\n\n this.requestUpdate()\n }\n\n private _renderRenderer(value: Properties) {\n return html`\n <fieldset>\n <legend>Renderer</legend>\n <div class=\"property-grid\">\n <input id=\"cb-antialias\" type=\"checkbox\" value-key=\"antialias\" .checked=${value.antialias !== false} />\n <label for=\"cb-antialias\">Anti-alias</label>\n\n <label>Precision</label>\n <select value-key=\"precision\">\n ${[\n { value: 'highp', label: 'High' },\n { value: 'mediump', label: 'Medium' },\n { value: 'lowp', label: 'Low' }\n ].map(\n o =>\n html`<option value=${o.value} ?selected=${(value.precision || 'highp') === o.value}>\n ${o.label}\n </option>`\n )}\n </select>\n </div>\n </fieldset>\n `\n }\n\n private _renderSky(value: Properties) {\n const skyOptions = [\n { value: '', label: 'None' },\n { value: 'color', label: 'Color' },\n { value: 'studio', label: 'Studio' },\n { value: 'warehouse', label: 'Warehouse' },\n { value: 'factory', label: 'Factory' },\n { value: 'office', label: 'Office' },\n { value: 'home', label: 'Home' },\n ]\n\n return html`\n <fieldset>\n <legend>Sky</legend>\n <div class=\"property-grid\">\n <label>Preset</label>\n <select value-key=\"sky\">\n ${skyOptions.map(\n o => html`<option value=${o.value} ?selected=${(value.sky || '') === o.value}>${o.label}</option>`\n )}\n </select>\n\n ${value.sky === 'color'\n ? html`\n <label>Color</label>\n <ox-input-color value-key=\"skyColor\" .value=${value.skyColor || '#87ceeb'}> </ox-input-color>\n `\n : ''}\n </div>\n </fieldset>\n `\n }\n\n private _renderHemisphereLight(value: Properties) {\n return html`\n <fieldset>\n <legend>Hemisphere Light</legend>\n <div class=\"property-grid\">\n <label>Intensity</label>\n <div class=\"range-with-value\">\n <input\n type=\"range\"\n min=\"0\"\n max=\"3\"\n step=\"0.1\"\n value-key=\"hemiIntensity\"\n .value=${String(value.hemiIntensity ?? 0.6)}\n />\n <span>${(value.hemiIntensity ?? 0.6).toFixed(1)}</span>\n </div>\n </div>\n </fieldset>\n `\n }\n\n private _renderKeyLight(value: Properties) {\n return html`\n <fieldset>\n <legend>Key Light</legend>\n <div class=\"property-grid\">\n <input\n id=\"cb-dirlight\"\n type=\"checkbox\"\n value-key=\"dirLightEnabled\"\n .checked=${value.dirLightEnabled !== false}\n />\n <label for=\"cb-dirlight\">Enabled</label>\n\n <label>Color</label>\n <ox-input-color value-key=\"dirLightColor\" .value=${value.dirLightColor || '#ffffff'}> </ox-input-color>\n\n <label>Intensity</label>\n <div class=\"range-with-value\">\n <input\n type=\"range\"\n min=\"0\"\n max=\"3\"\n step=\"0.1\"\n value-key=\"dirLightIntensity\"\n .value=${String(value.dirLightIntensity ?? 0.5)}\n />\n <span>${(value.dirLightIntensity ?? 0.5).toFixed(1)}</span>\n </div>\n </div>\n </fieldset>\n `\n }\n\n private _renderLightingPresets() {\n return html`\n <fieldset>\n <legend>Lighting Presets</legend>\n <div class=\"property-grid\">\n <div class=\"preset-buttons\">\n ${Object.entries(LIGHTING_PRESETS).map(\n ([name, values]) =>\n html`<button @click=${() => this._applyLightingPreset(values)}>${name}</button>`\n )}\n </div>\n </div>\n </fieldset>\n `\n }\n\n private _renderFloor(value: Properties) {\n return html`\n <property-material3d\n legend=\"Floor Material\"\n .value=${value.floorMaterial3d || { receiveShadow: true }}\n @property-change=${this._onFloorMaterialChange}\n >\n </property-material3d>\n `\n }\n\n private _applyLightingPreset(values: Record<string, unknown>) {\n this.dispatchEvent(\n new CustomEvent('property-change', {\n bubbles: true,\n composed: true,\n detail: values\n })\n )\n }\n\n private _onFloorMaterialChange(e: CustomEvent) {\n e.stopPropagation()\n this.dispatchEvent(\n new CustomEvent('property-change', {\n bubbles: true,\n composed: true,\n detail: { floorMaterial3d: e.detail.material3d }\n })\n )\n }\n\n}\n"]}