iobroker.mywebui 1.42.1 → 1.42.3
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/package.json
CHANGED
|
@@ -6,6 +6,9 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
6
6
|
<div id="editor-container" style="width:100%;height:100%;display:flex;flex-direction:column;overflow:hidden;">
|
|
7
7
|
<div id="toolbar" style="height:40px;background:#2a2a2a;border-bottom:1px solid #444;display:flex;align-items:center;padding:0 10px;gap:10px;">
|
|
8
8
|
<button id="saveBtn" style="padding:5px 15px;background:#0078d4;color:white;border:none;cursor:pointer;border-radius:3px;">Save</button>
|
|
9
|
+
<button id="addAssetBtn" style="padding:5px 15px;background:#28a745;color:white;border:none;cursor:pointer;border-radius:3px;">+ Asset</button>
|
|
10
|
+
<button id="addLightBtn" style="padding:5px 15px;background:#ffa500;color:white;border:none;cursor:pointer;border-radius:3px;">+ Light</button>
|
|
11
|
+
<input id="fileInput" type="file" accept=".glb,.gltf,.fbx,.obj" style="display:none;">
|
|
9
12
|
<button id="undoBtn" style="padding:5px 15px;background:#444;color:#aaa;border:none;cursor:pointer;border-radius:3px;">↶ Undo</button>
|
|
10
13
|
<button id="redoBtn" style="padding:5px 15px;background:#444;color:#aaa;border:none;cursor:pointer;border-radius:3px;">↷ Redo</button>
|
|
11
14
|
<div style="flex:1;"></div>
|
|
@@ -13,9 +16,15 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
13
16
|
<button id="axesToggle" style="padding:5px 10px;background:#444;color:#aaa;border:none;cursor:pointer;border-radius:3px;">Axes</button>
|
|
14
17
|
</div>
|
|
15
18
|
<div id="mainContent" style="flex:1;display:flex;overflow:hidden;">
|
|
16
|
-
<div id="leftPanel" style="width:
|
|
17
|
-
<div style="
|
|
18
|
-
|
|
19
|
+
<div id="leftPanel" style="width:280px;background:#1e1e1e;border-right:1px solid #444;overflow:auto;display:flex;flex-direction:column;">
|
|
20
|
+
<div style="display:flex;border-bottom:1px solid #444;">
|
|
21
|
+
<button id="treeTab" style="flex:1;padding:10px;background:#2a2a2a;color:#0f0;border:none;cursor:pointer;font-weight:bold;">Scene</button>
|
|
22
|
+
<button id="assetsTab" style="flex:1;padding:10px;background:#1e1e1e;color:#aaa;border:none;cursor:pointer;">Assets</button>
|
|
23
|
+
</div>
|
|
24
|
+
<div id="sceneTree" style="padding:10px;flex:1;overflow:auto;"></div>
|
|
25
|
+
<div id="assetsList" style="padding:10px;flex:1;overflow:auto;display:none;">
|
|
26
|
+
<div id="assetsListContent" style="color:#aaa;font-size:12px;">No assets loaded</div>
|
|
27
|
+
</div>
|
|
19
28
|
</div>
|
|
20
29
|
<div id="viewport" style="flex:1;background:#333;position:relative;overflow:hidden;"></div>
|
|
21
30
|
<div id="rightPanel" style="width:300px;background:#1e1e1e;border-left:1px solid #444;overflow:auto;">
|
|
@@ -366,8 +375,18 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
366
375
|
|
|
367
376
|
for (const light of this.sceneData.lights) {
|
|
368
377
|
const itemDiv = document.createElement('div');
|
|
369
|
-
itemDiv.textContent = light.name
|
|
370
|
-
itemDiv.style.cssText = 'padding:5px;cursor:pointer;color:#
|
|
378
|
+
itemDiv.textContent = `${light.name} [${light.type}]`;
|
|
379
|
+
itemDiv.style.cssText = 'padding:5px;cursor:pointer;color:#ff0;margin-left:10px;background:#2a2a1a;border-radius:3px;margin-bottom:3px;';
|
|
380
|
+
itemDiv.addEventListener('click', () => this.showLightEditor(light));
|
|
381
|
+
itemDiv.addEventListener('dblclick', () => {
|
|
382
|
+
if (confirm(`Delete light "${light.name}"?`)) {
|
|
383
|
+
const idx = this.sceneData.lights.indexOf(light);
|
|
384
|
+
if (idx > -1) {
|
|
385
|
+
this.sceneData.lights.splice(idx, 1);
|
|
386
|
+
this.updateSceneTree();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
});
|
|
371
390
|
lightsDiv.appendChild(itemDiv);
|
|
372
391
|
}
|
|
373
392
|
|
|
@@ -386,15 +405,24 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
386
405
|
|
|
387
406
|
const asset = this.selectedObject.userData.assetData;
|
|
388
407
|
|
|
408
|
+
// Asset name
|
|
409
|
+
const nameGroup = document.createElement('div');
|
|
410
|
+
nameGroup.className = 'property-group';
|
|
411
|
+
nameGroup.innerHTML = `
|
|
412
|
+
<div class="property-label">Asset Name</div>
|
|
413
|
+
<input type="text" id="assetName" value="${asset.name}" class="property-input" style="width:100%;">
|
|
414
|
+
`;
|
|
415
|
+
panelEl.appendChild(nameGroup);
|
|
416
|
+
|
|
389
417
|
// Position
|
|
390
418
|
const posGroup = document.createElement('div');
|
|
391
419
|
posGroup.className = 'property-group';
|
|
392
420
|
posGroup.innerHTML = `
|
|
393
|
-
<div class="property-label"
|
|
421
|
+
<div class="property-label">📍 Position</div>
|
|
394
422
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;">
|
|
395
|
-
<input type="number" id="posX" value="${asset.position.x}" step="0.1" class="property-input" placeholder="X">
|
|
396
|
-
<input type="number" id="posY" value="${asset.position.y}" step="0.1" class="property-input" placeholder="Y">
|
|
397
|
-
<input type="number" id="posZ" value="${asset.position.z}" step="0.1" class="property-input" placeholder="Z">
|
|
423
|
+
<input type="number" id="posX" value="${asset.position.x.toFixed(2)}" step="0.1" class="property-input" placeholder="X" data-prop="position.x">
|
|
424
|
+
<input type="number" id="posY" value="${asset.position.y.toFixed(2)}" step="0.1" class="property-input" placeholder="Y" data-prop="position.y">
|
|
425
|
+
<input type="number" id="posZ" value="${asset.position.z.toFixed(2)}" step="0.1" class="property-input" placeholder="Z" data-prop="position.z">
|
|
398
426
|
</div>
|
|
399
427
|
`;
|
|
400
428
|
panelEl.appendChild(posGroup);
|
|
@@ -403,11 +431,11 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
403
431
|
const rotGroup = document.createElement('div');
|
|
404
432
|
rotGroup.className = 'property-group';
|
|
405
433
|
rotGroup.innerHTML = `
|
|
406
|
-
<div class="property-label"
|
|
434
|
+
<div class="property-label">🔄 Rotation (rad)</div>
|
|
407
435
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;">
|
|
408
|
-
<input type="number" id="rotX" value="${asset.rotation.x}" step="0.
|
|
409
|
-
<input type="number" id="rotY" value="${asset.rotation.y}" step="0.
|
|
410
|
-
<input type="number" id="rotZ" value="${asset.rotation.z}" step="0.
|
|
436
|
+
<input type="number" id="rotX" value="${asset.rotation.x.toFixed(2)}" step="0.01" class="property-input" placeholder="X" data-prop="rotation.x">
|
|
437
|
+
<input type="number" id="rotY" value="${asset.rotation.y.toFixed(2)}" step="0.01" class="property-input" placeholder="Y" data-prop="rotation.y">
|
|
438
|
+
<input type="number" id="rotZ" value="${asset.rotation.z.toFixed(2)}" step="0.01" class="property-input" placeholder="Z" data-prop="rotation.z">
|
|
411
439
|
</div>
|
|
412
440
|
`;
|
|
413
441
|
panelEl.appendChild(rotGroup);
|
|
@@ -416,22 +444,244 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
416
444
|
const scaleGroup = document.createElement('div');
|
|
417
445
|
scaleGroup.className = 'property-group';
|
|
418
446
|
scaleGroup.innerHTML = `
|
|
419
|
-
<div class="property-label"
|
|
447
|
+
<div class="property-label">📏 Scale</div>
|
|
420
448
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;">
|
|
421
|
-
<input type="number" id="scaleX" value="${asset.scale.x}" step="0.1" class="property-input" placeholder="X">
|
|
422
|
-
<input type="number" id="scaleY" value="${asset.scale.y}" step="0.1" class="property-input" placeholder="Y">
|
|
423
|
-
<input type="number" id="scaleZ" value="${asset.scale.z}" step="0.1" class="property-input" placeholder="Z">
|
|
449
|
+
<input type="number" id="scaleX" value="${asset.scale.x.toFixed(2)}" step="0.1" class="property-input" placeholder="X" data-prop="scale.x">
|
|
450
|
+
<input type="number" id="scaleY" value="${asset.scale.y.toFixed(2)}" step="0.1" class="property-input" placeholder="Y" data-prop="scale.y">
|
|
451
|
+
<input type="number" id="scaleZ" value="${asset.scale.z.toFixed(2)}" step="0.1" class="property-input" placeholder="Z" data-prop="scale.z">
|
|
424
452
|
</div>
|
|
425
453
|
`;
|
|
426
454
|
panelEl.appendChild(scaleGroup);
|
|
455
|
+
|
|
456
|
+
// Bindings section
|
|
457
|
+
const bindingsGroup = document.createElement('div');
|
|
458
|
+
bindingsGroup.className = 'property-group';
|
|
459
|
+
bindingsGroup.style.borderTop = '2px solid #0f0';
|
|
460
|
+
bindingsGroup.innerHTML = `
|
|
461
|
+
<div class="property-label" style="color:#0f0;margin-bottom:10px;">🔗 ioBroker Bindings</div>
|
|
462
|
+
<div id="bindingsList" style="font-size:11px;"></div>
|
|
463
|
+
<button id="addBindingBtn" style="width:100%;padding:8px;margin-top:10px;background:#1a5a1a;color:#0f0;border:1px solid #0f0;cursor:pointer;border-radius:3px;">+ Add Binding</button>
|
|
464
|
+
`;
|
|
465
|
+
panelEl.appendChild(bindingsGroup);
|
|
466
|
+
|
|
467
|
+
// Add property change listeners
|
|
468
|
+
panelEl.querySelectorAll('input[data-prop]').forEach(input => {
|
|
469
|
+
input.addEventListener('change', (e) => {
|
|
470
|
+
const prop = e.target.dataset.prop;
|
|
471
|
+
const value = parseFloat(e.target.value);
|
|
472
|
+
this.updateAssetProperty(asset, prop, value);
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Add binding button
|
|
477
|
+
panelEl.querySelector('#addBindingBtn').addEventListener('click', () => {
|
|
478
|
+
this.showBindingEditor(asset);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Asset name listener
|
|
482
|
+
panelEl.querySelector('#assetName').addEventListener('change', (e) => {
|
|
483
|
+
asset.name = e.target.value;
|
|
484
|
+
this.selectedObject.userData.assetData.name = e.target.value;
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Update bindings list
|
|
488
|
+
this.updateBindingsList(asset, panelEl);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
updateAssetProperty(asset, propPath, value) {
|
|
492
|
+
const parts = propPath.split('.');
|
|
493
|
+
let obj = asset;
|
|
494
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
495
|
+
obj = obj[parts[i]];
|
|
496
|
+
}
|
|
497
|
+
obj[parts[parts.length - 1]] = value;
|
|
498
|
+
|
|
499
|
+
// Update Three.js object
|
|
500
|
+
if (this.selectedObject) {
|
|
501
|
+
if (propPath.startsWith('position')) {
|
|
502
|
+
this.selectedObject.position.set(asset.position.x, asset.position.y, asset.position.z);
|
|
503
|
+
} else if (propPath.startsWith('rotation')) {
|
|
504
|
+
this.selectedObject.rotation.set(asset.rotation.x, asset.rotation.y, asset.rotation.z);
|
|
505
|
+
} else if (propPath.startsWith('scale')) {
|
|
506
|
+
this.selectedObject.scale.set(asset.scale.x, asset.scale.y, asset.scale.z);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
updateBindingsList(asset, panelEl) {
|
|
512
|
+
const bindingsList = panelEl.querySelector('#bindingsList');
|
|
513
|
+
if (!asset.bindings || Object.keys(asset.bindings).length === 0) {
|
|
514
|
+
bindingsList.innerHTML = '<div style="color:#888;padding:5px;">No bindings configured</div>';
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
let html = '';
|
|
519
|
+
for (const [prop, binding] of Object.entries(asset.bindings)) {
|
|
520
|
+
html += `
|
|
521
|
+
<div style="padding:5px;background:#1a1a1a;margin-bottom:3px;border-left:2px solid #0f0;font-family:monospace;">
|
|
522
|
+
<div style="color:#0f0;">${prop}</div>
|
|
523
|
+
<div style="color:#888;font-size:10px;">→ ${binding.signal || 'unset'}</div>
|
|
524
|
+
</div>
|
|
525
|
+
`;
|
|
526
|
+
}
|
|
527
|
+
bindingsList.innerHTML = html;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
showBindingEditor(asset) {
|
|
531
|
+
const propInput = prompt('Property path (e.g., position.x, rotation.y, scale.z):', 'position.x');
|
|
532
|
+
if (!propInput) return;
|
|
533
|
+
|
|
534
|
+
const signalInput = prompt('ioBroker signal (e.g., system.adapter.0.alive):', '');
|
|
535
|
+
if (!signalInput) return;
|
|
536
|
+
|
|
537
|
+
if (!asset.bindings) {
|
|
538
|
+
asset.bindings = {};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
asset.bindings[propInput] = {
|
|
542
|
+
signal: signalInput,
|
|
543
|
+
type: 'number',
|
|
544
|
+
min: 0,
|
|
545
|
+
max: 100
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
this.updatePropertyPanel();
|
|
549
|
+
console.log('✅ Binding added:', propInput, '→', signalInput);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
showLightCreationDialog() {
|
|
553
|
+
const lightType = prompt('Light type:\n1 = Ambient\n2 = Directional\n3 = Point\n4 = Spot\n\nEnter number (default: 2):', '2');
|
|
554
|
+
if (!lightType) return;
|
|
555
|
+
|
|
556
|
+
const typeMap = { '1': 'ambient', '2': 'directional', '3': 'point', '4': 'spot' };
|
|
557
|
+
const type = typeMap[lightType] || 'directional';
|
|
558
|
+
|
|
559
|
+
const name = prompt(`Create new ${type} light\n\nLight name:`, `${type}_${Date.now().toString(36).substring(5)}`);
|
|
560
|
+
if (!name) return;
|
|
561
|
+
|
|
562
|
+
const light = {
|
|
563
|
+
id: 'light_' + Date.now().toString(36),
|
|
564
|
+
name: name,
|
|
565
|
+
type: type,
|
|
566
|
+
color: '#ffffff',
|
|
567
|
+
intensity: type === 'ambient' ? 0.6 : 0.8,
|
|
568
|
+
position: type === 'directional' ? { x: 10, y: 10, z: 10 } : { x: 0, y: 5, z: 0 },
|
|
569
|
+
castShadow: type !== 'ambient',
|
|
570
|
+
visible: true
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
if (!this.sceneData.lights) {
|
|
574
|
+
this.sceneData.lights = [];
|
|
575
|
+
}
|
|
576
|
+
this.sceneData.lights.push(light);
|
|
577
|
+
|
|
578
|
+
// Add to Three.js scene
|
|
579
|
+
this.addLightToScene(light);
|
|
580
|
+
|
|
581
|
+
// Update scene tree
|
|
582
|
+
this.updateSceneTree();
|
|
583
|
+
|
|
584
|
+
console.log('✅ Light created:', name);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
showLightEditor(light) {
|
|
588
|
+
const panelEl = this._getDomElement('propertyPanel');
|
|
589
|
+
panelEl.innerHTML = '';
|
|
590
|
+
|
|
591
|
+
// Light name
|
|
592
|
+
const nameGroup = document.createElement('div');
|
|
593
|
+
nameGroup.className = 'property-group';
|
|
594
|
+
nameGroup.innerHTML = `
|
|
595
|
+
<div class="property-label">💡 Light: ${light.type}</div>
|
|
596
|
+
<input type="text" id="lightName" value="${light.name}" class="property-input" style="width:100%;">
|
|
597
|
+
`;
|
|
598
|
+
panelEl.appendChild(nameGroup);
|
|
599
|
+
|
|
600
|
+
// Color
|
|
601
|
+
const colorGroup = document.createElement('div');
|
|
602
|
+
colorGroup.className = 'property-group';
|
|
603
|
+
colorGroup.innerHTML = `
|
|
604
|
+
<div class="property-label">Color</div>
|
|
605
|
+
<input type="color" id="lightColor" value="${light.color}" class="property-input" style="width:100%;height:35px;">
|
|
606
|
+
`;
|
|
607
|
+
panelEl.appendChild(colorGroup);
|
|
608
|
+
|
|
609
|
+
// Intensity
|
|
610
|
+
const intensityGroup = document.createElement('div');
|
|
611
|
+
intensityGroup.className = 'property-group';
|
|
612
|
+
intensityGroup.innerHTML = `
|
|
613
|
+
<div class="property-label">Intensity: <span id="intensityValue">${light.intensity.toFixed(2)}</span></div>
|
|
614
|
+
<input type="range" id="lightIntensity" min="0" max="2" step="0.1" value="${light.intensity}" style="width:100%;">
|
|
615
|
+
`;
|
|
616
|
+
panelEl.appendChild(intensityGroup);
|
|
617
|
+
|
|
618
|
+
// Position (if applicable)
|
|
619
|
+
if (light.position) {
|
|
620
|
+
const posGroup = document.createElement('div');
|
|
621
|
+
posGroup.className = 'property-group';
|
|
622
|
+
posGroup.innerHTML = `
|
|
623
|
+
<div class="property-label">📍 Position</div>
|
|
624
|
+
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;">
|
|
625
|
+
<input type="number" id="lightPosX" value="${light.position.x.toFixed(2)}" step="0.5" class="property-input" placeholder="X">
|
|
626
|
+
<input type="number" id="lightPosY" value="${light.position.y.toFixed(2)}" step="0.5" class="property-input" placeholder="Y">
|
|
627
|
+
<input type="number" id="lightPosZ" value="${light.position.z.toFixed(2)}" step="0.5" class="property-input" placeholder="Z">
|
|
628
|
+
</div>
|
|
629
|
+
`;
|
|
630
|
+
panelEl.appendChild(posGroup);
|
|
631
|
+
|
|
632
|
+
panelEl.querySelector('#lightPosX').addEventListener('change', (e) => {
|
|
633
|
+
light.position.x = parseFloat(e.target.value);
|
|
634
|
+
});
|
|
635
|
+
panelEl.querySelector('#lightPosY').addEventListener('change', (e) => {
|
|
636
|
+
light.position.y = parseFloat(e.target.value);
|
|
637
|
+
});
|
|
638
|
+
panelEl.querySelector('#lightPosZ').addEventListener('change', (e) => {
|
|
639
|
+
light.position.z = parseFloat(e.target.value);
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Cast Shadow (if applicable)
|
|
644
|
+
if (light.castShadow !== undefined) {
|
|
645
|
+
const shadowGroup = document.createElement('div');
|
|
646
|
+
shadowGroup.className = 'property-group';
|
|
647
|
+
shadowGroup.innerHTML = `
|
|
648
|
+
<div class="property-label">Cast Shadow</div>
|
|
649
|
+
<input type="checkbox" id="lightShadow" ${light.castShadow ? 'checked' : ''} style="width:20px;height:20px;cursor:pointer;">
|
|
650
|
+
`;
|
|
651
|
+
panelEl.appendChild(shadowGroup);
|
|
652
|
+
|
|
653
|
+
panelEl.querySelector('#lightShadow').addEventListener('change', (e) => {
|
|
654
|
+
light.castShadow = e.target.checked;
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Event listeners
|
|
659
|
+
panelEl.querySelector('#lightName').addEventListener('change', (e) => {
|
|
660
|
+
light.name = e.target.value;
|
|
661
|
+
this.updateSceneTree();
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
panelEl.querySelector('#lightColor').addEventListener('change', (e) => {
|
|
665
|
+
light.color = e.target.value;
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
panelEl.querySelector('#lightIntensity').addEventListener('input', (e) => {
|
|
669
|
+
light.intensity = parseFloat(e.target.value);
|
|
670
|
+
panelEl.querySelector('#intensityValue').textContent = light.intensity.toFixed(2);
|
|
671
|
+
});
|
|
427
672
|
}
|
|
428
673
|
|
|
429
674
|
setupEventListeners() {
|
|
430
675
|
this._getDomElement('saveBtn').addEventListener('click', () => this.saveScene());
|
|
676
|
+
this._getDomElement('addAssetBtn').addEventListener('click', () => this.onAddAssetClick());
|
|
677
|
+
this._getDomElement('addLightBtn').addEventListener('click', () => this.showLightCreationDialog());
|
|
678
|
+
this._getDomElement('fileInput').addEventListener('change', (e) => this.onFileSelected(e));
|
|
431
679
|
this._getDomElement('undoBtn').addEventListener('click', () => console.log('Undo'));
|
|
432
680
|
this._getDomElement('redoBtn').addEventListener('click', () => console.log('Redo'));
|
|
433
681
|
this._getDomElement('gridToggle').addEventListener('click', () => this.toggleGrid());
|
|
434
682
|
this._getDomElement('axesToggle').addEventListener('click', () => this.toggleAxes());
|
|
683
|
+
this._getDomElement('treeTab').addEventListener('click', () => this.switchTab('tree'));
|
|
684
|
+
this._getDomElement('assetsTab').addEventListener('click', () => this.switchTab('assets'));
|
|
435
685
|
}
|
|
436
686
|
|
|
437
687
|
async saveScene() {
|
|
@@ -469,6 +719,96 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
469
719
|
this.renderer.setSize(width, height);
|
|
470
720
|
}
|
|
471
721
|
|
|
722
|
+
onAddAssetClick() {
|
|
723
|
+
this._getDomElement('fileInput').click();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
onFileSelected(event) {
|
|
727
|
+
const file = event.target.files[0];
|
|
728
|
+
if (!file) return;
|
|
729
|
+
|
|
730
|
+
const reader = new FileReader();
|
|
731
|
+
reader.onload = (e) => {
|
|
732
|
+
const arrayBuffer = e.target.result;
|
|
733
|
+
const blob = new Blob([arrayBuffer]);
|
|
734
|
+
const url = URL.createObjectURL(blob);
|
|
735
|
+
|
|
736
|
+
// Create asset object
|
|
737
|
+
const assetId = 'asset_' + Date.now().toString(36);
|
|
738
|
+
const asset = {
|
|
739
|
+
id: assetId,
|
|
740
|
+
name: file.name,
|
|
741
|
+
type: 'model',
|
|
742
|
+
glbPath: url,
|
|
743
|
+
position: { x: 0, y: 0, z: 0 },
|
|
744
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
745
|
+
scale: { x: 1, y: 1, z: 1 },
|
|
746
|
+
visible: true
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// Add to scene data
|
|
750
|
+
if (!this.sceneData.assets) {
|
|
751
|
+
this.sceneData.assets = [];
|
|
752
|
+
}
|
|
753
|
+
this.sceneData.assets.push(asset);
|
|
754
|
+
|
|
755
|
+
// Load and add to scene
|
|
756
|
+
this.loadAsset(asset);
|
|
757
|
+
|
|
758
|
+
// Update assets list
|
|
759
|
+
this.updateAssetsList();
|
|
760
|
+
|
|
761
|
+
console.log('✅ Asset added:', file.name);
|
|
762
|
+
};
|
|
763
|
+
reader.readAsArrayBuffer(file);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
switchTab(tab) {
|
|
767
|
+
const sceneTree = this._getDomElement('sceneTree');
|
|
768
|
+
const assetsList = this._getDomElement('assetsList');
|
|
769
|
+
const treeTab = this._getDomElement('treeTab');
|
|
770
|
+
const assetsTab = this._getDomElement('assetsTab');
|
|
771
|
+
|
|
772
|
+
if (tab === 'tree') {
|
|
773
|
+
sceneTree.style.display = 'block';
|
|
774
|
+
assetsList.style.display = 'none';
|
|
775
|
+
treeTab.style.background = '#2a2a2a';
|
|
776
|
+
treeTab.style.color = '#0f0';
|
|
777
|
+
assetsTab.style.background = '#1e1e1e';
|
|
778
|
+
assetsTab.style.color = '#aaa';
|
|
779
|
+
} else {
|
|
780
|
+
sceneTree.style.display = 'none';
|
|
781
|
+
assetsList.style.display = 'block';
|
|
782
|
+
treeTab.style.background = '#1e1e1e';
|
|
783
|
+
treeTab.style.color = '#aaa';
|
|
784
|
+
assetsTab.style.background = '#2a2a2a';
|
|
785
|
+
assetsTab.style.color = '#0f0';
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
updateAssetsList() {
|
|
790
|
+
const assetsListContent = this._getDomElement('assetsListContent');
|
|
791
|
+
|
|
792
|
+
if (!this.sceneData.assets || this.sceneData.assets.length === 0) {
|
|
793
|
+
assetsListContent.innerHTML = '<div style="color:#888;">No assets loaded</div>';
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
let html = '';
|
|
798
|
+
for (const asset of this.sceneData.assets) {
|
|
799
|
+
html += `
|
|
800
|
+
<div style="padding:8px;background:#242424;margin-bottom:5px;border-radius:3px;cursor:pointer;border:1px solid #333;">
|
|
801
|
+
<div style="color:#0f0;font-weight:bold;">${asset.name}</div>
|
|
802
|
+
<div style="color:#888;font-size:11px;margin-top:3px;">
|
|
803
|
+
Type: ${asset.type}<br>
|
|
804
|
+
Pos: (${asset.position.x.toFixed(1)}, ${asset.position.y.toFixed(1)}, ${asset.position.z.toFixed(1)})
|
|
805
|
+
</div>
|
|
806
|
+
</div>
|
|
807
|
+
`;
|
|
808
|
+
}
|
|
809
|
+
assetsListContent.innerHTML = html;
|
|
810
|
+
}
|
|
811
|
+
|
|
472
812
|
disconnectedCallback() {
|
|
473
813
|
if (this.renderer) {
|
|
474
814
|
this.renderer.dispose();
|