iobroker.mywebui 1.42.43 → 1.42.45

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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "mywebui",
4
- "version": "1.42.43",
4
+ "version": "1.42.45",
5
5
  "titleLang": {
6
6
  "en": "mywebui",
7
7
  "de": "mywebui",
@@ -29,6 +29,20 @@
29
29
  "zh-cn": "使用万维网传送器的高锰用户接口"
30
30
  },
31
31
  "news": {
32
+ "1.42.45": {
33
+ "en": "design: neon glassmorphic theme for the solution explorer - dark gradient panel, glass project bar, glowing hover/selection, cyan icon set (wunderbaum CSS variables overridden, original icons untouched)",
34
+ "az": "dizayn: solution explorer üçün neon glassmorphic tema - tünd qradiyent panel, şüşə proyekt paneli, parlayan hover/seçim, cyan ikon dəsti (wunderbaum CSS dəyişənləri override edildi, orijinal ikonlara toxunulmadı)",
35
+ "tr": "tasarım: solution explorer için neon glassmorphic tema - koyu gradyan panel, cam proje çubuğu, parlayan hover/seçim, cyan ikon seti (wunderbaum CSS değişkenleri override edildi, orijinal ikonlara dokunulmadı)",
36
+ "ru": "дизайн: неоновая glassmorphic тема для solution explorer - тёмная градиентная панель, стеклянная панель проектов, светящиеся hover/выделение, голубой набор иконок (переопределены CSS-переменные wunderbaum, оригинальные иконки не тронуты)",
37
+ "de": "Design: Neon-Glassmorphic-Theme für den Solution Explorer - dunkles Gradient-Panel, Glas-Projektleiste, leuchtende Hover/Auswahl, Cyan-Icon-Set (wunderbaum-CSS-Variablen überschrieben, Original-Icons unverändert)"
38
+ },
39
+ "1.42.44": {
40
+ "en": "feature: multi-project support - project selector with create/delete in solution explorer; existing data stays as 'default' project, new projects under projects/<name>/; runtime preview passes ?project=, plain runtime.html always shows default (kiosk safe)",
41
+ "az": "yenilik: multi-proyekt dəstəyi - solution explorer-də yarat/sil düymələri ilə proyekt seçici; mövcud data 'default' proyekt kimi qalır, yeni proyektlər projects/<ad>/ altında; runtime önizləmə ?project= ötürür, parametrsiz runtime.html həmişə default göstərir (kiosk təhlükəsiz)",
42
+ "tr": "yenilik: çoklu proje desteği - solution explorer'da oluştur/sil düğmeleriyle proje seçici; mevcut veri 'default' proje olarak kalır, yeni projeler projects/<ad>/ altında; runtime önizleme ?project= geçirir, parametresiz runtime.html her zaman default gösterir (kiosk güvenli)",
43
+ "ru": "новое: поддержка нескольких проектов - селектор проектов с созданием/удалением в solution explorer; существующие данные остаются проектом 'default', новые проекты в projects/<имя>/; предпросмотр runtime передаёт ?project=, runtime.html без параметра всегда показывает default (безопасно для киосков)",
44
+ "de": "Neu: Multi-Projekt-Unterstützung - Projektauswahl mit Erstellen/Löschen im Solution Explorer; bestehende Daten bleiben als 'default'-Projekt, neue Projekte unter projects/<name>/; Runtime-Vorschau übergibt ?project=, runtime.html ohne Parameter zeigt immer default (kiosksicher)"
45
+ },
32
46
  "1.42.43": {
33
47
  "en": "refactor: all @gokturk413/@node-projects fork packages vendored into www/libs (edited directly in repo, removed from npm dependencies); build script made safe (gulp delAll no longer deletes www frontend sources)",
34
48
  "az": "refaktor: bütün @gokturk413/@node-projects fork paketləri www/libs-ə vendorlandı (birbaşa repo-da redaktə olunur, npm asılılıqlarından çıxarıldı); build scripti təhlükəsiz edildi (gulp delAll artıq www frontend mənbələrini silmir)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.mywebui",
3
- "version": "1.42.43",
3
+ "version": "1.42.45",
4
4
  "description": "ioBroker mywebui - Custom edited mywebui by gokturk413 with 3D Editor",
5
5
  "type": "module",
6
6
  "main": "dist/backend/main.js",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="298" height="512" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 298 511.93"><path fill="#2ed6ff" fill-rule="nonzero" d="M70.77 499.85c-16.24 16.17-42.53 16.09-58.69-.15-16.17-16.25-16.09-42.54.15-58.7l185.5-185.03L12.23 70.93c-16.24-16.16-16.32-42.45-.15-58.7 16.16-16.24 42.45-16.32 58.69-.15l215.15 214.61c16.17 16.25 16.09 42.54-.15 58.7l-215 214.46z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="512" height="298" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 298.04"><path fill="#2ed6ff" fill-rule="nonzero" d="M70.94 285.81c-16.17 16.24-42.46 16.32-58.71.15-16.24-16.16-16.32-42.46-.15-58.7L226.57 12.23c16.16-16.24 42.46-16.32 58.7-.15l214.65 215.18c16.17 16.24 16.09 42.54-.15 58.7-16.25 16.17-42.54 16.09-58.71-.15L256 100.29 70.94 285.81z"/></svg>
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 102.55 122.88" style="enable-background:new 0 0 102.55 122.88" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#2ed6ff;}</style><g><path fill="#2ed6ff" class="st0" d="M102.55,122.88H0V0h77.66l24.89,26.44L102.55,122.88L102.55,122.88z M96.13,115.98V30.44H73.44V5.91H6.51 v110.07H96.13L96.13,115.98z"/></g></svg>
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 95.21" style="enable-background:new 0 0 122.88 95.21" xml:space="preserve"><g><path fill="#2ed6ff" d="M2.48,20.74h4.5v-9.86c0-1.37,1.11-2.48,2.48-2.48h4.41V2.48c0-1.37,1.11-2.48,2.48-2.48h40.26c1.37,0,2.48,1.11,2.48,2.48 V8.4h54.3c1.37,0,2.48,1.11,2.48,2.48v9.86h4.53c1.37,0,2.48,1.11,2.48,2.48c0,0.18-0.02,0.36-0.06,0.52l-8.68,63.81 c-0.28,2.08-1.19,4.01-2.59,5.41c-1.38,1.38-3.21,2.24-5.36,2.24H14.7c-2.16,0-4.03-0.87-5.43-2.26c-1.41-1.41-2.31-3.35-2.54-5.46 l-6.72-64c-0.14-1.36,0.85-2.58,2.21-2.72C2.31,20.75,2.39,20.75,2.48,20.74L2.48,20.74L2.48,20.74z M9.46,25.71H5.23l6.43,61.27 c0.1,0.98,0.5,1.85,1.1,2.46c0.5,0.5,1.17,0.81,1.93,0.81h91.5c0.75,0,1.38-0.3,1.87-0.79c0.62-0.62,1.03-1.53,1.17-2.55 l8.32-61.19H9.46L9.46,25.71z M11.94,13.37v7.36l98.97-1.05v-6.31h-54.3c-1.37,0-2.48-1.11-2.48-2.48V4.97h-35.3v5.92 c0,1.37-1.11,2.48-2.48,2.48H11.94L11.94,13.37z"/></g></svg>
@@ -7,12 +7,24 @@ export class IobrokerHandler {
7
7
  host;
8
8
  connection;
9
9
  adapterName = "mywebui";
10
- configPath = "config/";
11
10
  namespace = "mywebui.0";
12
11
  namespaceFiles = this.namespace + '.data';
13
12
  namespaceWidgets = this.namespace + '.widgets';
14
- imagePrefix = '/' + this.namespaceFiles + '/config/images/';
15
- additionalFilePrefix = '/' + this.namespaceFiles + '/config/additionalfiles/';
13
+ // multi project support: the 'default' project is the legacy flat 'config/' folder,
14
+ // every other project lives under 'projects/<name>/config/' with the same structure
15
+ #currentProject = 'default';
16
+ get currentProject() {
17
+ return this.#currentProject;
18
+ }
19
+ get configPath() {
20
+ return this.#currentProject === 'default' ? 'config/' : 'projects/' + this.#currentProject + '/config/';
21
+ }
22
+ get imagePrefix() {
23
+ return '/' + this.namespaceFiles + '/' + this.configPath + 'images/';
24
+ }
25
+ get additionalFilePrefix() {
26
+ return '/' + this.namespaceFiles + '/' + this.configPath + 'additionalfiles/';
27
+ }
16
28
  config;
17
29
  globalStylesheet;
18
30
  fontDeclarationsStylesheet;
@@ -38,6 +50,75 @@ export class IobrokerHandler {
38
50
  this.#cache.set('control', new Map());
39
51
  this.#cache.set('3dscreen', new Map());
40
52
  this.#cache.set('3dcontrol', new Map());
53
+ // active project: explicit ?project= url parameter wins (used by runtime/kiosk links),
54
+ // otherwise the config editor remembers the last selection per browser.
55
+ // plain runtime.html without parameter always shows the 'default' project,
56
+ // so kiosk displays are not affected by what is edited in the designer.
57
+ try {
58
+ const urlProject = new URLSearchParams(window.location.search).get('project');
59
+ if (urlProject)
60
+ this.#currentProject = this._cleanProjectName(urlProject);
61
+ else if (!window.location.pathname.endsWith('runtime.html'))
62
+ this.#currentProject = this._cleanProjectName(localStorage.getItem('webui-current-project') ?? 'default');
63
+ }
64
+ catch (e) {
65
+ console.warn('could not determine current project', e);
66
+ }
67
+ }
68
+ _cleanProjectName(name) {
69
+ name = (name ?? '').toString().trim().toLowerCase().replaceAll(' ', '-');
70
+ if (!/^[a-z0-9_-]+$/.test(name))
71
+ return 'default';
72
+ return name;
73
+ }
74
+ async getProjects() {
75
+ if (this._readyPromises)
76
+ await this.waitForReady();
77
+ try {
78
+ const list = await this._getObjectFromFile('projects.json');
79
+ if (Array.isArray(list?.projects)) {
80
+ const projects = list.projects.filter(x => /^[a-z0-9_-]+$/.test(x));
81
+ if (!projects.includes('default'))
82
+ projects.unshift('default');
83
+ return projects;
84
+ }
85
+ }
86
+ catch (e) { }
87
+ return ['default'];
88
+ }
89
+ async createProject(name) {
90
+ name = (name ?? '').toString().trim().toLowerCase().replaceAll(' ', '-');
91
+ if (!/^[a-z0-9_-]+$/.test(name))
92
+ throw new Error('invalid project name (use a-z, 0-9, -, _): ' + name);
93
+ const projects = await this.getProjects();
94
+ if (projects.includes(name))
95
+ throw new Error('project already exists: ' + name);
96
+ projects.push(name);
97
+ await this._saveObjectToFile({ projects: projects.filter(x => x !== 'default') }, 'projects.json');
98
+ await this._saveObjectToFile({ globalStyle: null, globalScript: null, globalConfig: null, fontDeclarations: null }, 'projects/' + name + '/config/config.json');
99
+ return name;
100
+ }
101
+ async removeProject(name) {
102
+ if (name === 'default')
103
+ throw new Error("the 'default' project cannot be deleted");
104
+ const projects = (await this.getProjects()).filter(x => x !== name && x !== 'default');
105
+ await this._saveObjectToFile({ projects }, 'projects.json');
106
+ try {
107
+ await this.connection.deleteFolder(this.namespaceFiles, '/projects/' + name);
108
+ }
109
+ catch (e) {
110
+ console.warn('error deleting project folder', e);
111
+ }
112
+ if (this.#currentProject === name)
113
+ this.setCurrentProject('default');
114
+ }
115
+ // switching reloads the page: global style/script/translations/custom controls
116
+ // are all project scoped, a clean reload is the only safe way to swap them
117
+ setCurrentProject(name) {
118
+ localStorage.setItem('webui-current-project', this._cleanProjectName(name));
119
+ const url = new URL(window.location.href);
120
+ url.searchParams.delete('project');
121
+ window.location.href = url.toString();
41
122
  }
42
123
  getNormalizedSignalName(id, relativeSignalPath, element) {
43
124
  return (relativeSignalPath ?? '') + id;
@@ -14,16 +14,18 @@ export class CommandHandling {
14
14
  let commandName = button.dataset['command'];
15
15
  let commandParameter = button.dataset['commandParameter'];
16
16
  if (commandName === 'runtime') {
17
+ // preview always opens the project currently active in the editor
18
+ const projectParam = iobrokerHandler.currentProject !== 'default' ? '?project=' + iobrokerHandler.currentProject : '';
17
19
  let target = this.dockManager?.activeDocument?.resolvedElementContent;
18
20
  if (target instanceof IobrokerWebuiScreenEditor) {
19
- window.open("runtime.html#screenName=" + target.name);
21
+ window.open("runtime.html" + projectParam + "#screenName=" + target.name);
20
22
  }
21
23
  else if (target instanceof IobrokerWebui3DScreenEditor) {
22
24
  const scName = target.sceneData?.name || target.getAttribute?.('scene-name') || '';
23
- window.open("runtime.html#3dscreen=" + encodeURIComponent(scName));
25
+ window.open("runtime.html" + projectParam + "#3dscreen=" + encodeURIComponent(scName));
24
26
  }
25
27
  else {
26
- window.open("runtime.html");
28
+ window.open("runtime.html" + projectParam);
27
29
  }
28
30
  }
29
31
  else if (commandName === 'new') {
@@ -26,28 +26,201 @@ import { IobrokerWebuiScreensView } from "./IobrokerWebuiScreensView.js";
26
26
  import { convertToXml, parseXml } from "../helper/XmlHelper.js";
27
27
  export class IobrokerWebuiSolutionExplorer extends BaseCustomWebComponentConstructorAppend {
28
28
  static template = html `
29
- <div id="treeDiv" class="" style="overflow: auto; width:100%; height: 100%;">
29
+ <div style="display: flex; flex-direction: column; width: 100%; height: 100%;">
30
+ <div id="projectBar">
31
+ <select id="projectSelect" title="active project"></select>
32
+ <button id="projectAdd" title="create new project">+</button>
33
+ <button id="projectDel" title="delete current project">✕</button>
34
+ </div>
35
+ <div id="treeDiv" class="" style="overflow: auto; width:100%; flex: 1;">
36
+ </div>
30
37
  </div>`;
31
38
  static style = css `
39
+ :host {
40
+ display: block;
41
+ height: 100%;
42
+ box-sizing: border-box;
43
+ --neo-cyan: #2ed6ff;
44
+ --neo-purple: #a06bff;
45
+ --neo-pink: #ff4fa3;
46
+ --neo-text: #dce7ff;
47
+ --neo-text-dim: #8fa3cc;
48
+ --neo-glass: rgba(22, 34, 76, 0.55);
49
+ --neo-glass-border: rgba(80, 190, 255, 0.35);
50
+ background:
51
+ radial-gradient(ellipse 120% 60% at 80% -10%, rgba(110, 70, 220, 0.25), transparent 60%),
52
+ radial-gradient(ellipse 100% 50% at 0% 110%, rgba(0, 150, 255, 0.18), transparent 60%),
53
+ linear-gradient(165deg, #0b1228 0%, #111a3a 55%, #151d45 100%);
54
+ color: var(--neo-text);
55
+
56
+ /* wunderbaum theme variables (override its :host defaults) */
57
+ --wb-font-stack: 'Segoe UI', Roboto, sans-serif;
58
+ --wb-background-color: transparent;
59
+ --wb-node-text-color: var(--neo-text);
60
+ --wb-dim-color: var(--neo-text-dim);
61
+ --wb-filter-dim-color: var(--neo-text-dim);
62
+ --wb-filter-submatch-color: var(--neo-text-dim);
63
+ --wb-hover-color: rgba(90, 160, 255, 0.12);
64
+ --wb-hover-border-color: rgba(80, 190, 255, 0.3);
65
+ --wb-active-color: rgba(46, 214, 255, 0.2);
66
+ --wb-active-border-color: rgba(46, 214, 255, 0.55);
67
+ --wb-active-hover-color: rgba(46, 214, 255, 0.28);
68
+ --wb-active-hover-border-color: var(--neo-cyan);
69
+ --wb-active-color-grayscale: rgba(46, 214, 255, 0.14);
70
+ --wb-active-hover-color-grayscale: rgba(46, 214, 255, 0.2);
71
+ --wb-active-border-color-grayscale: rgba(80, 190, 255, 0.4);
72
+ --wb-alternate-row-color: transparent;
73
+ --wb-alternate-row-color-hover: rgba(90, 160, 255, 0.08);
74
+ --wb-focus-border-color: var(--neo-cyan);
75
+ --wb-border-color: rgba(80, 190, 255, 0.35);
76
+ --wb-drop-target-color: rgba(46, 214, 255, 0.18);
77
+ --wb-drop-source-color: rgba(160, 107, 255, 0.15);
78
+ }
79
+
32
80
  * {
33
81
  user-select: none;
34
82
  -webkit-user-select: none;
35
83
  cursor: pointer;
36
84
  }
37
-
85
+
86
+ #projectBar {
87
+ display: flex;
88
+ gap: 5px;
89
+ padding: 6px 7px;
90
+ margin: 7px 7px 5px 7px;
91
+ align-items: center;
92
+ flex-shrink: 0;
93
+ background: var(--neo-glass);
94
+ border: 1px solid var(--neo-glass-border);
95
+ border-radius: 11px;
96
+ box-shadow: 0 0 12px rgba(46, 214, 255, 0.12), inset 0 0 18px rgba(46, 214, 255, 0.05);
97
+ backdrop-filter: blur(8px);
98
+ }
99
+
100
+ #projectBar select {
101
+ flex: 1;
102
+ min-width: 0;
103
+ font-size: 13px;
104
+ color: var(--neo-text);
105
+ background: rgba(10, 18, 45, 0.6);
106
+ border: 1px solid rgba(80, 190, 255, 0.25);
107
+ border-radius: 7px;
108
+ padding: 3px 6px;
109
+ outline: none;
110
+ }
111
+
112
+ #projectBar select option {
113
+ background: #101a3d;
114
+ color: var(--neo-text);
115
+ }
116
+
117
+ #projectBar button {
118
+ border: 1px solid rgba(80, 190, 255, 0.35);
119
+ background: rgba(10, 18, 45, 0.6);
120
+ color: var(--neo-cyan);
121
+ border-radius: 7px;
122
+ font-size: 13px;
123
+ line-height: 18px;
124
+ width: 26px;
125
+ height: 26px;
126
+ padding: 0;
127
+ text-shadow: 0 0 6px rgba(46, 214, 255, 0.8);
128
+ transition: box-shadow 0.15s, background 0.15s;
129
+ }
130
+
131
+ #projectBar button:hover {
132
+ background: rgba(46, 214, 255, 0.15);
133
+ box-shadow: 0 0 10px rgba(46, 214, 255, 0.45);
134
+ }
135
+
136
+ #treeDiv {
137
+ margin: 0 4px 4px 4px;
138
+ }
139
+
140
+ #treeDiv::-webkit-scrollbar {
141
+ width: 8px;
142
+ height: 8px;
143
+ }
144
+
145
+ #treeDiv::-webkit-scrollbar-track {
146
+ background: transparent;
147
+ }
148
+
149
+ #treeDiv::-webkit-scrollbar-thumb {
150
+ background: rgba(80, 190, 255, 0.25);
151
+ border-radius: 4px;
152
+ }
153
+
154
+ #treeDiv::-webkit-scrollbar-thumb:hover {
155
+ background: rgba(80, 190, 255, 0.45);
156
+ }
157
+
158
+ div.wunderbaum {
159
+ background: transparent;
160
+ color: var(--neo-text);
161
+ font-family: 'Segoe UI', Roboto, sans-serif;
162
+ font-size: 13px;
163
+ }
164
+
165
+ div.wunderbaum div.wb-node-list div.wb-row {
166
+ border-radius: 7px;
167
+ transition: background 0.12s, box-shadow 0.12s;
168
+ }
169
+
170
+ div.wunderbaum div.wb-node-list div.wb-row:hover {
171
+ background: rgba(90, 160, 255, 0.12);
172
+ box-shadow: inset 0 0 0 1px rgba(80, 190, 255, 0.18);
173
+ }
174
+
175
+ div.wunderbaum div.wb-node-list div.wb-row.wb-active,
176
+ div.wunderbaum div.wb-node-list div.wb-row.wb-selected {
177
+ background: linear-gradient(90deg, rgba(46, 214, 255, 0.22), rgba(160, 107, 255, 0.16));
178
+ box-shadow: inset 0 0 0 1px rgba(46, 214, 255, 0.4), 0 0 10px rgba(46, 214, 255, 0.15);
179
+ }
180
+
181
+ div.wunderbaum div.wb-node-list div.wb-row.wb-active:hover,
182
+ div.wunderbaum div.wb-node-list div.wb-row.wb-selected:hover {
183
+ background: linear-gradient(90deg, rgba(46, 214, 255, 0.3), rgba(160, 107, 255, 0.22));
184
+ }
185
+
186
+ div.wunderbaum span.wb-node span.wb-title {
187
+ overflow-x: visible;
188
+ color: var(--neo-text);
189
+ text-shadow: 0 0 8px rgba(46, 214, 255, 0.12);
190
+ }
191
+
192
+ div.wunderbaum div.wb-row.wb-active span.wb-node span.wb-title {
193
+ color: #ffffff;
194
+ text-shadow: 0 0 8px rgba(46, 214, 255, 0.55);
195
+ }
196
+
197
+ /* neon recolor of the dark svg icons (folder/file/expander) */
198
+ div.wunderbaum i.wb-icon img,
199
+ div.wunderbaum i.wb-expander img {
200
+ filter: invert(72%) sepia(35%) saturate(1800%) hue-rotate(160deg) brightness(1.25) drop-shadow(0 0 2px rgba(46, 214, 255, 0.65));
201
+ }
202
+
203
+ div.wunderbaum i.wb-icon,
204
+ div.wunderbaum i.wb-expander {
205
+ color: var(--neo-cyan);
206
+ }
207
+
38
208
  div.wunderbaum span.wb-node i.wb-indent::before {
39
209
  content: "";
40
210
  }
41
-
211
+
42
212
  i.wb-icon > span.wb-badge {
43
- opacity: 0.4;
213
+ opacity: 1;
44
214
  font-size: 50%;
215
+ background: linear-gradient(135deg, var(--neo-cyan), var(--neo-purple));
216
+ color: #08122e;
217
+ border-radius: 8px;
218
+ padding: 1px 4px;
219
+ font-weight: bold;
220
+ text-shadow: none;
221
+ box-shadow: 0 0 6px rgba(46, 214, 255, 0.5);
45
222
  }
46
-
47
- div.wunderbaum span.wb-node span.wb-title {
48
- overflow-x: visible;
49
- }
50
-
223
+
51
224
  div.wunderbaum span.wb-col {
52
225
  overflow: visible;
53
226
  }
@@ -85,9 +258,64 @@ export class IobrokerWebuiSolutionExplorer extends BaseCustomWebComponentConstru
85
258
  constructor() {
86
259
  super();
87
260
  this._treeDiv = this._getDomElement('treeDiv');
261
+ this._projectSelect = this._getDomElement('projectSelect');
262
+ this._projectAdd = this._getDomElement('projectAdd');
263
+ this._projectDel = this._getDomElement('projectDel');
88
264
  this.shadowRoot.adoptedStyleSheets = [wunderbaumStyle, defaultStyle, IobrokerWebuiSolutionExplorer.style];
89
265
  this._createThumbnailTooltip();
90
266
  }
267
+ async _initProjectBar() {
268
+ const fillSelect = async () => {
269
+ const projects = await iobrokerHandler.getProjects();
270
+ this._projectSelect.innerHTML = '';
271
+ for (const p of projects) {
272
+ const opt = document.createElement('option');
273
+ opt.value = p;
274
+ opt.textContent = p;
275
+ if (p === iobrokerHandler.currentProject)
276
+ opt.selected = true;
277
+ this._projectSelect.appendChild(opt);
278
+ }
279
+ // if the stored project does not exist anymore, fall back to default
280
+ if (!projects.includes(iobrokerHandler.currentProject))
281
+ iobrokerHandler.setCurrentProject('default');
282
+ };
283
+ await fillSelect();
284
+ this._projectSelect.onchange = () => {
285
+ if (this._projectSelect.value !== iobrokerHandler.currentProject)
286
+ iobrokerHandler.setCurrentProject(this._projectSelect.value);
287
+ };
288
+ this._projectAdd.onclick = async () => {
289
+ const name = prompt('New project name (a-z, 0-9, -, _):');
290
+ if (!name)
291
+ return;
292
+ try {
293
+ const created = await iobrokerHandler.createProject(name);
294
+ if (confirm("Project '" + created + "' created. Switch to it now?"))
295
+ iobrokerHandler.setCurrentProject(created);
296
+ else
297
+ await fillSelect();
298
+ }
299
+ catch (err) {
300
+ alert(err.message ?? err);
301
+ }
302
+ };
303
+ this._projectDel.onclick = async () => {
304
+ const current = iobrokerHandler.currentProject;
305
+ if (current === 'default') {
306
+ alert("The 'default' project cannot be deleted.");
307
+ return;
308
+ }
309
+ if (!confirm("Delete project '" + current + "' with ALL its screens, controls and files? This cannot be undone!"))
310
+ return;
311
+ try {
312
+ await iobrokerHandler.removeProject(current);
313
+ }
314
+ catch (err) {
315
+ alert(err.message ?? err);
316
+ }
317
+ };
318
+ }
91
319
  _createThumbnailTooltip() {
92
320
  this._thumbnailTooltip = document.createElement('div');
93
321
  this._thumbnailTooltip.className = 'control-thumbnail-tooltip';
@@ -102,6 +330,7 @@ export class IobrokerWebuiSolutionExplorer extends BaseCustomWebComponentConstru
102
330
  });
103
331
  iobrokerHandler.imagesChanged.on(() => this._refreshNode('images'));
104
332
  iobrokerHandler.additionalFilesChanged.on(() => this._refreshAdditionalFilesNode());
333
+ this._initProjectBar();
105
334
  await sleep(100);
106
335
  this._loadTree();
107
336
  //Preload after a timeout
@@ -998,6 +1227,15 @@ export class IobrokerWebuiSolutionExplorer extends BaseCustomWebComponentConstru
998
1227
  if (!this._tree) {
999
1228
  this._tree = new Wunderbaum({
1000
1229
  ...defaultOptions,
1230
+ // neon themed copies of the default icons (originals are shared with the outline tree)
1231
+ iconMap: {
1232
+ expanderCollapsed: './assets/icons/neon/expander.svg',
1233
+ expanderExpanded: './assets/icons/neon/expanderClose.svg',
1234
+ folder: './assets/icons/neon/folder.svg',
1235
+ folderLazy: './assets/icons/neon/folder.svg',
1236
+ folderOpen: './assets/icons/neon/folder.svg',
1237
+ doc: './assets/icons/neon/file.svg',
1238
+ },
1001
1239
  element: this._treeDiv,
1002
1240
  source: await this.createTreeNodes(),
1003
1241
  lazyLoad: async (e) => {