iobroker.mywebui 1.42.43 → 1.42.44

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.44",
5
5
  "titleLang": {
6
6
  "en": "mywebui",
7
7
  "de": "mywebui",
@@ -29,6 +29,13 @@
29
29
  "zh-cn": "使用万维网传送器的高锰用户接口"
30
30
  },
31
31
  "news": {
32
+ "1.42.44": {
33
+ "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)",
34
+ "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)",
35
+ "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)",
36
+ "ru": "новое: поддержка нескольких проектов - селектор проектов с созданием/удалением в solution explorer; существующие данные остаются проектом 'default', новые проекты в projects/<имя>/; предпросмотр runtime передаёт ?project=, runtime.html без параметра всегда показывает default (безопасно для киосков)",
37
+ "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)"
38
+ },
32
39
  "1.42.43": {
33
40
  "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
41
  "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.44",
4
4
  "description": "ioBroker mywebui - Custom edited mywebui by gokturk413 with 3D Editor",
5
5
  "type": "module",
6
6
  "main": "dist/backend/main.js",
@@ -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,7 +26,14 @@ 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 `
32
39
  * {
@@ -34,6 +41,36 @@ export class IobrokerWebuiSolutionExplorer extends BaseCustomWebComponentConstru
34
41
  -webkit-user-select: none;
35
42
  cursor: pointer;
36
43
  }
44
+
45
+ #projectBar {
46
+ display: flex;
47
+ gap: 3px;
48
+ padding: 3px 4px;
49
+ align-items: center;
50
+ border-bottom: 1px solid #d0d0d0;
51
+ background: #f2f2f2;
52
+ flex-shrink: 0;
53
+ }
54
+
55
+ #projectBar select {
56
+ flex: 1;
57
+ min-width: 0;
58
+ font-size: 12px;
59
+ }
60
+
61
+ #projectBar button {
62
+ border: 1px solid #b0b0b0;
63
+ background: white;
64
+ border-radius: 3px;
65
+ font-size: 12px;
66
+ line-height: 16px;
67
+ width: 22px;
68
+ padding: 0;
69
+ }
70
+
71
+ #projectBar button:hover {
72
+ background: #e0e8f0;
73
+ }
37
74
 
38
75
  div.wunderbaum span.wb-node i.wb-indent::before {
39
76
  content: "";
@@ -85,9 +122,64 @@ export class IobrokerWebuiSolutionExplorer extends BaseCustomWebComponentConstru
85
122
  constructor() {
86
123
  super();
87
124
  this._treeDiv = this._getDomElement('treeDiv');
125
+ this._projectSelect = this._getDomElement('projectSelect');
126
+ this._projectAdd = this._getDomElement('projectAdd');
127
+ this._projectDel = this._getDomElement('projectDel');
88
128
  this.shadowRoot.adoptedStyleSheets = [wunderbaumStyle, defaultStyle, IobrokerWebuiSolutionExplorer.style];
89
129
  this._createThumbnailTooltip();
90
130
  }
131
+ async _initProjectBar() {
132
+ const fillSelect = async () => {
133
+ const projects = await iobrokerHandler.getProjects();
134
+ this._projectSelect.innerHTML = '';
135
+ for (const p of projects) {
136
+ const opt = document.createElement('option');
137
+ opt.value = p;
138
+ opt.textContent = p;
139
+ if (p === iobrokerHandler.currentProject)
140
+ opt.selected = true;
141
+ this._projectSelect.appendChild(opt);
142
+ }
143
+ // if the stored project does not exist anymore, fall back to default
144
+ if (!projects.includes(iobrokerHandler.currentProject))
145
+ iobrokerHandler.setCurrentProject('default');
146
+ };
147
+ await fillSelect();
148
+ this._projectSelect.onchange = () => {
149
+ if (this._projectSelect.value !== iobrokerHandler.currentProject)
150
+ iobrokerHandler.setCurrentProject(this._projectSelect.value);
151
+ };
152
+ this._projectAdd.onclick = async () => {
153
+ const name = prompt('New project name (a-z, 0-9, -, _):');
154
+ if (!name)
155
+ return;
156
+ try {
157
+ const created = await iobrokerHandler.createProject(name);
158
+ if (confirm("Project '" + created + "' created. Switch to it now?"))
159
+ iobrokerHandler.setCurrentProject(created);
160
+ else
161
+ await fillSelect();
162
+ }
163
+ catch (err) {
164
+ alert(err.message ?? err);
165
+ }
166
+ };
167
+ this._projectDel.onclick = async () => {
168
+ const current = iobrokerHandler.currentProject;
169
+ if (current === 'default') {
170
+ alert("The 'default' project cannot be deleted.");
171
+ return;
172
+ }
173
+ if (!confirm("Delete project '" + current + "' with ALL its screens, controls and files? This cannot be undone!"))
174
+ return;
175
+ try {
176
+ await iobrokerHandler.removeProject(current);
177
+ }
178
+ catch (err) {
179
+ alert(err.message ?? err);
180
+ }
181
+ };
182
+ }
91
183
  _createThumbnailTooltip() {
92
184
  this._thumbnailTooltip = document.createElement('div');
93
185
  this._thumbnailTooltip.className = 'control-thumbnail-tooltip';
@@ -102,6 +194,7 @@ export class IobrokerWebuiSolutionExplorer extends BaseCustomWebComponentConstru
102
194
  });
103
195
  iobrokerHandler.imagesChanged.on(() => this._refreshNode('images'));
104
196
  iobrokerHandler.additionalFilesChanged.on(() => this._refreshAdditionalFilesNode());
197
+ this._initProjectBar();
105
198
  await sleep(100);
106
199
  this._loadTree();
107
200
  //Preload after a timeout