@iobroker/gui-components 10.3.4 → 10.3.6

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.
@@ -5,6 +5,20 @@ export declare class Router<P = {}, S = {}> extends Component<P, S> {
5
5
  componentDidMount(): void;
6
6
  componentWillUnmount(): void;
7
7
  onHashChanged(): void;
8
+ /**
9
+ * Put a free-form part of the route into the hash.
10
+ *
11
+ * {@link Router.getLocation} splits the hash at "/" and decodes every part, so everything written
12
+ * here has to be encoded: an object ID may contain a "/" (e.g.
13
+ * `ocpp.0./TACW1142021G1543.1.meterValues.Power_Active_Import`) and a file ID always does.
14
+ * Without this, such an ID is cut off at its first slash when the route is read back, and the
15
+ * rest of it lands in `arg`.
16
+ *
17
+ * Callers pass the raw ID; they must not encode it themselves, or it is encoded twice.
18
+ *
19
+ * @param part the value to write into one segment of the hash
20
+ */
21
+ private static encodeSegment;
8
22
  /**
9
23
  * Gets the location object.
10
24
  */
@@ -16,6 +16,22 @@ export class Router extends Component {
16
16
  onHashChanged() {
17
17
  // override this function
18
18
  }
19
+ /**
20
+ * Put a free-form part of the route into the hash.
21
+ *
22
+ * {@link Router.getLocation} splits the hash at "/" and decodes every part, so everything written
23
+ * here has to be encoded: an object ID may contain a "/" (e.g.
24
+ * `ocpp.0./TACW1142021G1543.1.meterValues.Power_Active_Import`) and a file ID always does.
25
+ * Without this, such an ID is cut off at its first slash when the route is read back, and the
26
+ * rest of it lands in `arg`.
27
+ *
28
+ * Callers pass the raw ID; they must not encode it themselves, or it is encoded twice.
29
+ *
30
+ * @param part the value to write into one segment of the hash
31
+ */
32
+ static encodeSegment(part) {
33
+ return encodeURIComponent(part);
34
+ }
19
35
  /**
20
36
  * Gets the location object.
21
37
  */
@@ -62,9 +78,12 @@ export class Router extends Component {
62
78
  if (dialog) {
63
79
  hash += `/${dialog}`;
64
80
  if (id) {
65
- hash += `/${id}`;
81
+ // `id` and `arg` carry free-form values - an ID with a "/" in it would otherwise
82
+ // be read back as two segments. `tab` and `dialog` are fixed names of the
83
+ // application and are left as they are.
84
+ hash += `/${Router.encodeSegment(id)}`;
66
85
  if (arg !== undefined) {
67
- hash += `/${arg}`;
86
+ hash += `/${arg === null ? arg : Router.encodeSegment(arg)}`;
68
87
  }
69
88
  }
70
89
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Router.js","sourceRoot":"./src/","sources":["Components/Router.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component } from 'react';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport class Router<P = {}, S = {}> extends Component<P, S> {\n protected onHashChangedBound: () => void;\n\n constructor(props: P) {\n super(props);\n this.onHashChangedBound = this.onHashChanged.bind(this);\n }\n\n componentDidMount(): void {\n window.addEventListener('hashchange', this.onHashChangedBound);\n }\n\n componentWillUnmount(): void {\n window.removeEventListener('hashchange', this.onHashChangedBound);\n }\n\n // eslint-disable-next-line class-methods-use-this\n onHashChanged(): void {\n // override this function\n }\n\n /**\n * Gets the location object.\n */\n static getLocation(): { tab: string; dialog: string; id: string; arg: string } {\n let hash = window.location.hash;\n hash = hash.replace(/^#/, '');\n const parts = hash.split('/').map(item => {\n try {\n return item ? decodeURIComponent(item) : '';\n } catch {\n console.error(`Router: Cannot decode ${item}`);\n return item;\n }\n });\n // #tabName/dialogName/deviceId\n return {\n tab: parts[0] || '',\n dialog: parts[1] || '',\n id: parts[2] || '',\n arg: parts[3] || '',\n };\n }\n\n /**\n * Navigate to a new location. Any parameters that are not set will be taken from the current location.\n */\n static doNavigate(\n tab: string | undefined | null,\n dialog?: string | null,\n id?: string | null,\n arg?: string | null,\n ): void {\n let hash = '';\n const location = Router.getLocation();\n if (arg !== undefined && !id) {\n id = location.id;\n }\n if (id && !dialog) {\n dialog = location.dialog;\n }\n if (dialog && !tab) {\n tab = location.tab;\n } else if (tab === null) {\n tab = location.tab;\n }\n\n if (tab) {\n hash = `#${tab}`;\n if (dialog) {\n hash += `/${dialog}`;\n\n if (id) {\n hash += `/${id}`;\n if (arg !== undefined) {\n hash += `/${arg}`;\n }\n }\n }\n }\n if (window.location.hash !== hash) {\n window.location.hash = hash;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"Router.js","sourceRoot":"./src/","sources":["Components/Router.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component } from 'react';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport class Router<P = {}, S = {}> extends Component<P, S> {\n protected onHashChangedBound: () => void;\n\n constructor(props: P) {\n super(props);\n this.onHashChangedBound = this.onHashChanged.bind(this);\n }\n\n componentDidMount(): void {\n window.addEventListener('hashchange', this.onHashChangedBound);\n }\n\n componentWillUnmount(): void {\n window.removeEventListener('hashchange', this.onHashChangedBound);\n }\n\n // eslint-disable-next-line class-methods-use-this\n onHashChanged(): void {\n // override this function\n }\n\n /**\n * Put a free-form part of the route into the hash.\n *\n * {@link Router.getLocation} splits the hash at \"/\" and decodes every part, so everything written\n * here has to be encoded: an object ID may contain a \"/\" (e.g.\n * `ocpp.0./TACW1142021G1543.1.meterValues.Power_Active_Import`) and a file ID always does.\n * Without this, such an ID is cut off at its first slash when the route is read back, and the\n * rest of it lands in `arg`.\n *\n * Callers pass the raw ID; they must not encode it themselves, or it is encoded twice.\n *\n * @param part the value to write into one segment of the hash\n */\n private static encodeSegment(part: string): string {\n return encodeURIComponent(part);\n }\n\n /**\n * Gets the location object.\n */\n static getLocation(): { tab: string; dialog: string; id: string; arg: string } {\n let hash = window.location.hash;\n hash = hash.replace(/^#/, '');\n const parts = hash.split('/').map(item => {\n try {\n return item ? decodeURIComponent(item) : '';\n } catch {\n console.error(`Router: Cannot decode ${item}`);\n return item;\n }\n });\n // #tabName/dialogName/deviceId\n return {\n tab: parts[0] || '',\n dialog: parts[1] || '',\n id: parts[2] || '',\n arg: parts[3] || '',\n };\n }\n\n /**\n * Navigate to a new location. Any parameters that are not set will be taken from the current location.\n */\n static doNavigate(\n tab: string | undefined | null,\n dialog?: string | null,\n id?: string | null,\n arg?: string | null,\n ): void {\n let hash = '';\n const location = Router.getLocation();\n if (arg !== undefined && !id) {\n id = location.id;\n }\n if (id && !dialog) {\n dialog = location.dialog;\n }\n if (dialog && !tab) {\n tab = location.tab;\n } else if (tab === null) {\n tab = location.tab;\n }\n\n if (tab) {\n hash = `#${tab}`;\n if (dialog) {\n hash += `/${dialog}`;\n\n if (id) {\n // `id` and `arg` carry free-form values - an ID with a \"/\" in it would otherwise\n // be read back as two segments. `tab` and `dialog` are fixed names of the\n // application and are left as they are.\n hash += `/${Router.encodeSegment(id)}`;\n if (arg !== undefined) {\n hash += `/${arg === null ? arg : Router.encodeSegment(arg)}`;\n }\n }\n }\n }\n if (window.location.hash !== hash) {\n window.location.hash = hash;\n }\n }\n}\n"]}
@@ -1,11 +1,12 @@
1
1
  /**
2
- * Copyright 2018-2024 Denis Haev <dogafox@gmail.com>
2
+ * Copyright 2018-2026 Denis Haev <dogafox@gmail.com>
3
3
  *
4
4
  * MIT License
5
5
  *
6
6
  */
7
7
  import React from 'react';
8
8
  import type { Types } from '@iobroker/type-detector';
9
+ import type { Connection } from '../Connection';
9
10
  import type { IobTheme, ThemeName, ThemeType } from '../types';
10
11
  type SmartNameObject = {
11
12
  [lang in ioBroker.Languages]?: string;
@@ -105,6 +106,14 @@ export declare class Utils {
105
106
  * Get the icon for the given object.
106
107
  */
107
108
  static getObjectIcon(id: string | ioBroker.PartialObject, obj?: ioBroker.PartialObject): string | null;
109
+ /**
110
+ * Finds the icon for the given object id, searching up the hierarchy if necessary.
111
+ *
112
+ * @param id The object id to find the icon for.
113
+ * @param socket The socket connection to use for fetching objects.
114
+ * @param objects Optional pre-fetched objects to avoid redundant socket calls.
115
+ */
116
+ static findObjectIcon(id: string, socket: Connection, objects?: Record<string, ioBroker.Object>): Promise<string | null>;
108
117
  /**
109
118
  * Converts word1_word2 to word1Word2.
110
119
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2018-2024 Denis Haev <dogafox@gmail.com>
2
+ * Copyright 2018-2026 Denis Haev <dogafox@gmail.com>
3
3
  *
4
4
  * MIT License
5
5
  *
@@ -409,6 +409,34 @@ export class Utils {
409
409
  }
410
410
  return null;
411
411
  }
412
+ /**
413
+ * Finds the icon for the given object id, searching up the hierarchy if necessary.
414
+ *
415
+ * @param id The object id to find the icon for.
416
+ * @param socket The socket connection to use for fetching objects.
417
+ * @param objects Optional pre-fetched objects to avoid redundant socket calls.
418
+ */
419
+ static async findObjectIcon(id, socket, objects) {
420
+ objects ||= {};
421
+ if (objects[id]?.common?.icon) {
422
+ return Promise.resolve(Utils.getObjectIcon(id, objects[id]));
423
+ }
424
+ const state = await socket.getObject(id);
425
+ if (state && (state.type === 'state' || state.type === 'channel' || state.type === 'device')) {
426
+ objects[id] = state;
427
+ }
428
+ else {
429
+ return Promise.resolve(null);
430
+ }
431
+ if (state.common?.icon) {
432
+ return Promise.resolve(Utils.getObjectIcon(id, state));
433
+ }
434
+ if (state.type === 'state' || state.type === 'channel') {
435
+ const parentId = id.substring(0, id.lastIndexOf('.'));
436
+ return Utils.findObjectIcon(parentId, socket, objects);
437
+ }
438
+ return Promise.resolve(null);
439
+ }
412
440
  /**
413
441
  * Converts word1_word2 to word1Word2.
414
442
  */