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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.mywebui",
3
- "version": "1.42.1",
3
+ "version": "1.42.3",
4
4
  "description": "ioBroker mywebui - Custom edited mywebui by gokturk413 with 3D Editor",
5
5
  "type": "module",
6
6
  "main": "dist/backend/main.js",
@@ -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:250px;background:#1e1e1e;border-right:1px solid #444;overflow:auto;">
17
- <div style="padding:10px;font-weight:bold;color:#aaa;border-bottom:1px solid #444;">Scene Tree</div>
18
- <div id="sceneTree" style="padding:10px;"></div>
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:#aaa;margin-left:10px;';
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">Position</div>
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">Rotation</div>
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.1" class="property-input" placeholder="X">
409
- <input type="number" id="rotY" value="${asset.rotation.y}" step="0.1" class="property-input" placeholder="Y">
410
- <input type="number" id="rotZ" value="${asset.rotation.z}" step="0.1" class="property-input" placeholder="Z">
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">Scale</div>
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();