iobroker.mywebui 1.42.2 → 1.42.4
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/io-package.json
CHANGED
package/package.json
CHANGED
|
@@ -6,7 +6,8 @@ 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;">+
|
|
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>
|
|
10
11
|
<input id="fileInput" type="file" accept=".glb,.gltf,.fbx,.obj" style="display:none;">
|
|
11
12
|
<button id="undoBtn" style="padding:5px 15px;background:#444;color:#aaa;border:none;cursor:pointer;border-radius:3px;">↶ Undo</button>
|
|
12
13
|
<button id="redoBtn" style="padding:5px 15px;background:#444;color:#aaa;border:none;cursor:pointer;border-radius:3px;">↷ Redo</button>
|
|
@@ -374,8 +375,18 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
374
375
|
|
|
375
376
|
for (const light of this.sceneData.lights) {
|
|
376
377
|
const itemDiv = document.createElement('div');
|
|
377
|
-
itemDiv.textContent = light.name
|
|
378
|
-
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
|
+
});
|
|
379
390
|
lightsDiv.appendChild(itemDiv);
|
|
380
391
|
}
|
|
381
392
|
|
|
@@ -394,15 +405,24 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
394
405
|
|
|
395
406
|
const asset = this.selectedObject.userData.assetData;
|
|
396
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
|
+
|
|
397
417
|
// Position
|
|
398
418
|
const posGroup = document.createElement('div');
|
|
399
419
|
posGroup.className = 'property-group';
|
|
400
420
|
posGroup.innerHTML = `
|
|
401
|
-
<div class="property-label"
|
|
421
|
+
<div class="property-label">📍 Position</div>
|
|
402
422
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;">
|
|
403
|
-
<input type="number" id="posX" value="${asset.position.x}" step="0.1" class="property-input" placeholder="X">
|
|
404
|
-
<input type="number" id="posY" value="${asset.position.y}" step="0.1" class="property-input" placeholder="Y">
|
|
405
|
-
<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">
|
|
406
426
|
</div>
|
|
407
427
|
`;
|
|
408
428
|
panelEl.appendChild(posGroup);
|
|
@@ -411,11 +431,11 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
411
431
|
const rotGroup = document.createElement('div');
|
|
412
432
|
rotGroup.className = 'property-group';
|
|
413
433
|
rotGroup.innerHTML = `
|
|
414
|
-
<div class="property-label"
|
|
434
|
+
<div class="property-label">🔄 Rotation (rad)</div>
|
|
415
435
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;">
|
|
416
|
-
<input type="number" id="rotX" value="${asset.rotation.x}" step="0.
|
|
417
|
-
<input type="number" id="rotY" value="${asset.rotation.y}" step="0.
|
|
418
|
-
<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">
|
|
419
439
|
</div>
|
|
420
440
|
`;
|
|
421
441
|
panelEl.appendChild(rotGroup);
|
|
@@ -424,19 +444,237 @@ export class IobrokerWebui3DScreenEditor extends BaseCustomWebComponentConstruct
|
|
|
424
444
|
const scaleGroup = document.createElement('div');
|
|
425
445
|
scaleGroup.className = 'property-group';
|
|
426
446
|
scaleGroup.innerHTML = `
|
|
427
|
-
<div class="property-label"
|
|
447
|
+
<div class="property-label">📏 Scale</div>
|
|
428
448
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;">
|
|
429
|
-
<input type="number" id="scaleX" value="${asset.scale.x}" step="0.1" class="property-input" placeholder="X">
|
|
430
|
-
<input type="number" id="scaleY" value="${asset.scale.y}" step="0.1" class="property-input" placeholder="Y">
|
|
431
|
-
<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">
|
|
432
452
|
</div>
|
|
433
453
|
`;
|
|
434
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
|
+
});
|
|
435
672
|
}
|
|
436
673
|
|
|
437
674
|
setupEventListeners() {
|
|
438
675
|
this._getDomElement('saveBtn').addEventListener('click', () => this.saveScene());
|
|
439
676
|
this._getDomElement('addAssetBtn').addEventListener('click', () => this.onAddAssetClick());
|
|
677
|
+
this._getDomElement('addLightBtn').addEventListener('click', () => this.showLightCreationDialog());
|
|
440
678
|
this._getDomElement('fileInput').addEventListener('change', (e) => this.onFileSelected(e));
|
|
441
679
|
this._getDomElement('undoBtn').addEventListener('click', () => console.log('Undo'));
|
|
442
680
|
this._getDomElement('redoBtn').addEventListener('click', () => console.log('Redo'));
|
|
@@ -8,6 +8,8 @@ import { Wunderbaum } from 'wunderbaum';
|
|
|
8
8
|
//@ts-ignore
|
|
9
9
|
import wunderbaumStyle from 'wunderbaum/dist/wunderbaum.css' with { type: 'css' };
|
|
10
10
|
import { defaultNewStyle, defaultNewControlScript } from "./IobrokerWebuiScreenEditor.js";
|
|
11
|
+
import { IobrokerWebui3DScreenEditor } from "./IobrokerWebui3DScreenEditor.js";
|
|
12
|
+
import { IobrokerWebui3DScreenViewer } from "./IobrokerWebui3DScreenViewer.js";
|
|
11
13
|
import { IobrokerWebuiIconsView } from "./IobrokerWebuiIconsView.js";
|
|
12
14
|
import { IobrokerWebuiScreensView } from "./IobrokerWebuiScreensView.js";
|
|
13
15
|
import { convertToXml, parseXml } from "../helper/XmlHelper.js";
|