From 460f2477283207c68fdbc63471280e4ef80bf1a1 Mon Sep 17 00:00:00 2001 From: Joseph Abbey Date: Fri, 18 Jul 2025 22:18:26 +0100 Subject: [PATCH] Improve web: dim disabled, icons, pin --- .vscode/settings.json | 5 ++- resources/settings/properties.xml | 2 +- web/index.html | 17 +++++++- web/main.js | 70 +++++++++++++++++++++++++------ web/package.json | 7 ++-- web/pnpm-lock.yaml | 25 +++++++---- 6 files changed, 99 insertions(+), 27 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9c38135..edb439a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "cSpell.words": [ "usbs", "Venu" - ] + ], + "files.exclude": { + "resources-*": true + } } \ No newline at end of file diff --git a/resources/settings/properties.xml b/resources/settings/properties.xml index a108e2c..4dc885c 100644 --- a/resources/settings/properties.xml +++ b/resources/settings/properties.xml @@ -53,7 +53,7 @@ Poll delay adds a user configurable delay (in seconds) to each round of status updates of all item in the device's menu that might be amended externally from the watch. A user has requested that it is possible to add - this delayfor an "always open" mode of operation, which then drains the + this delay for an "always open" mode of operation, which then drains the watch battery from the additional API access activity. --> 5 diff --git a/web/index.html b/web/index.html index 443b9ba..65434d1 100644 --- a/web/index.html +++ b/web/index.html @@ -37,7 +37,8 @@ margin-left: 0.5em; filter: grayscale() invert(); } - .template, .info { + .template, + .info { background-image: url(../resources-icons-48/info_type.svg); background-size: contain; margin-left: 0.5em; @@ -63,6 +64,13 @@ margin-left: 0.5em; filter: grayscale() invert(); } + .mdi { + margin-left: 0.5em; + } + + .disabled { + opacity: 0.5; + } :root { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; @@ -285,6 +293,10 @@ background-color: var(--ctp-mocha-overlay1); } +
@@ -441,9 +453,10 @@ http:
- + + diff --git a/web/main.js b/web/main.js index 9d7915f..5caed32 100644 --- a/web/main.js +++ b/web/main.js @@ -11,7 +11,7 @@ let api_token = localStorage.getItem('api_token') ?? ''; /** * Get all entities in HomeAssistant. - * @returns {Promise>} [id, name] + * @returns {Promise>} [id, name] */ async function get_entities() { try { @@ -21,7 +21,7 @@ async function get_entities() { Authorization: `Bearer ${api_token}`, }, mode: 'cors', - body: `{"template":"[{% for entity in states %}[\\"{{ entity.entity_id }}\\",\\"{{ entity.name }}\\"]{% if not loop.last %},{% endif %}{% endfor %}]"}`, + body: `{"template":"[{% for entity in states %}[\\"{{ entity.entity_id }}\\",\\"{{ entity.name }}\\",\\"{{ entity.attributes.icon }}\\"]{% if not loop.last %},{% endif %}{% endfor %}]"}`, }); if (res.status == 401 || res.status == 403) { document.querySelector('#api_token').classList.add('invalid'); @@ -29,8 +29,16 @@ async function get_entities() { } document.querySelector('#api_url').classList.remove('invalid'); document.querySelector('#api_token').classList.remove('invalid'); - return Object.fromEntries(await res.json()); - } catch { + const data = {}; + for (const [id, name, icon] of await res.json()) { + data[id] = { name }; + if (icon !== '') { + data[id].icon = icon; + } + } + return data; + } catch (e) { + console.error('Error fetching entities:', e); document.querySelector('#api_url').classList.add('invalid'); return {}; } @@ -57,7 +65,8 @@ async function get_devices() { document.querySelector('#api_url').classList.remove('invalid'); document.querySelector('#api_token').classList.remove('invalid'); return Object.fromEntries(await res.json()); - } catch { + } catch (e) { + console.error('Error fetching devices:', e); document.querySelector('#api_url').classList.add('invalid'); return {}; } @@ -84,7 +93,8 @@ async function get_areas() { document.querySelector('#api_url').classList.remove('invalid'); document.querySelector('#api_token').classList.remove('invalid'); return Object.fromEntries(await res.json()); - } catch { + } catch (e) { + console.error('Error fetching areas:', e); document.querySelector('#api_url').classList.add('invalid'); return {}; } @@ -119,7 +129,8 @@ async function get_services() { } } return services; - } catch { + } catch (e) { + console.error('Error fetching services:', e); document.querySelector('#api_url').classList.add('invalid'); return []; } @@ -370,6 +381,9 @@ async function generate_schema(entities, devices, areas, services, schema) { confirm: { $ref: '#/$defs/confirm', }, + pin: { + $ref: '#/$defs/pin', + }, data: { type: 'object', properties: {}, @@ -724,6 +738,7 @@ require(['vs/editor/editor.main'], async () => { var decorations = editor.createDecorationsCollection([]); + /** @type {monaco.editor.IMarkerData[]} */ let markers = []; const renderTemplate = editor.addCommand( @@ -905,6 +920,7 @@ require(['vs/editor/editor.main'], async () => { const ast = json.parse(model.getValue()); const data = JSON.parse(model.getValue()); markers = []; + /** @type {monaco.editor.IModelDeltaDecoration[]} */ const glyphs = []; async function testToggle(range, entity) { const res = await fetch(api_url + '/states/' + entity, { @@ -983,8 +999,10 @@ require(['vs/editor/editor.main'], async () => { * @param {import('json-ast-comments').JsonAst | * import('json-ast-comments').JsonProperty} node * @param {string[]} path + * @param {import('json-ast-comments').JsonAst | + * import('json-ast-comments').JsonProperty | null} parent */ - function recurse(node, path) { + function recurse(node, path, parent = null) { if (node.type === 'property') { if (node.key[0].value === 'content') { templates.push([ @@ -1010,11 +1028,39 @@ require(['vs/editor/editor.main'], async () => { } trim++; markers.push({ - message: entities[node.value[0].value] ?? 'Entity not found', + message: entities[node.value[0].value].name ?? 'Entity not found', severity: monaco.MarkerSeverity.Hint, ...range, startColumn: trim, }); + glyphs.push({ + range, + options: { + isWholeLine: true, + glyphMarginClassName: + 'mdi ' + + entities[node.value[0].value]?.icon?.replace(':', '-'), + }, + }); + } else if ( + node.key[0].value === 'enabled' && + node.value[0].type === 'boolean' && + !node.value[0].value + ) { + glyphs.push({ + range: { + startLineNumber: parent.members[0].key[0].range.start.line + 1, + startColumn: 0, + endLineNumber: + parent.members[parent.members.length - 1].value[0].range.end + .line + 1, + endColumn: 10000, + }, + options: { + isWholeLine: true, + inlineClassName: 'disabled', + }, + }); } else if (node.key[0].value === 'type') { if (node.value[0].value === 'toggle') { toggles.push([ @@ -1041,15 +1087,15 @@ require(['vs/editor/editor.main'], async () => { }); } } else { - recurse(node.value[0], [...path, node.key[0].value]); + recurse(node.value[0], [...path, node.key[0].value], node); } } else if (node.type === 'array') { for (let i = 0; i < node.members.length; i++) { - recurse(node.members[i], [...path, i]); + recurse(node.members[i], [...path, i], node); } } else if (node.type === 'object') { for (let member of node.members) { - recurse(member, path); + recurse(member, path, node); } } } diff --git a/web/package.json b/web/package.json index 575a5cf..a3b5e4b 100644 --- a/web/package.json +++ b/web/package.json @@ -10,10 +10,11 @@ "author": "", "license": "ISC", "devDependencies": { - "@types/toastify-js": "^1.12.3", + "@types/toastify-js": "^1.12.4", "@vscode/webview-ui-toolkit": "1.4.0", "json-ast-comments": "1.1.1", - "monaco-editor": "0.45.0", + "monaco-editor": "0.52.2", + "prettier": "^3.6.2", "serve": "^14.2.1" } -} +} \ No newline at end of file diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index ec6251d..5d56eed 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: devDependencies: '@types/toastify-js': - specifier: ^1.12.3 - version: 1.12.3 + specifier: ^1.12.4 + version: 1.12.4 '@vscode/webview-ui-toolkit': specifier: 1.4.0 version: 1.4.0(react@18.2.0) @@ -15,8 +15,11 @@ devDependencies: specifier: 1.1.1 version: 1.1.1 monaco-editor: - specifier: 0.45.0 - version: 0.45.0 + specifier: 0.52.2 + version: 0.52.2 + prettier: + specifier: ^3.6.2 + version: 3.6.2 serve: specifier: ^14.2.1 version: 14.2.1 @@ -52,8 +55,8 @@ packages: exenv-es6: 1.1.1 dev: true - /@types/toastify-js@1.12.3: - resolution: {integrity: sha512-9RjLlbAHMSaae/KZNHGv19VG4gcLIm3YjvacCXBtfMfYn26h76YP5oxXI8k26q4iKXCB9LNfv18lsoS0JnFPTg==} + /@types/toastify-js@1.12.4: + resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==} dev: true /@vscode/webview-ui-toolkit@1.4.0(react@18.2.0): @@ -415,8 +418,8 @@ packages: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true - /monaco-editor@0.45.0: - resolution: {integrity: sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==} + /monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} dev: true /ms@2.0.0: @@ -460,6 +463,12 @@ packages: resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==} dev: true + /prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + dev: true + /punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} dev: true