@iobroker/adapter-react-v5 8.2.7 → 8.2.8

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/README.md CHANGED
@@ -690,6 +690,9 @@ You can find the migration instructions:
690
690
  -->
691
691
 
692
692
  ## Changelog
693
+ ### 8.2.8 (2026-06-11)
694
+ - (@GermanBluefox) Added object and file navigation
695
+
693
696
  ### 8.2.7 (2026-05-29)
694
697
  - (@GermanBluefox) Updated loader
695
698
 
@@ -14,6 +14,17 @@ export interface MetaACL extends ioBroker.ObjectACL {
14
14
  export interface MetaObject extends ioBroker.MetaObject {
15
15
  acl: MetaACL;
16
16
  }
17
+ /**
18
+ * A navigation target the parent can use to drive the browser (and that the browser reports back).
19
+ * Maps cleanly to a route like `#tab-files/<mode>/<encoded-id>` (the parent encodes the file path,
20
+ * whose `/` separators would otherwise clash with the hash). The browser never reads the URL.
21
+ */
22
+ export interface FileBrowserNavigation {
23
+ /** `select` = just highlight the file; `view` = open the file viewer. */
24
+ mode: 'select' | 'view';
25
+ /** The file path/ID (e.g. `email.admin/custom/assets/Components.js`). */
26
+ id: string;
27
+ }
17
28
  export interface FileBrowserProps {
18
29
  /** The key to identify this component. */
19
30
  key?: string;
@@ -61,6 +72,17 @@ export interface FileBrowserProps {
61
72
  filterByType?: 'images' | 'code' | 'txt';
62
73
  /** Callback for file selection. */
63
74
  onSelect?: (id: string | string[], isDoubleClick?: boolean, isFolder?: boolean) => void;
75
+ /**
76
+ * Drive selection and the file viewer from the parent (e.g. from the URL). When this prop
77
+ * changes the browser selects the file and opens the requested viewer. The browser does NOT
78
+ * read the URL itself — all URL parsing/writing lives in the parent component.
79
+ */
80
+ navigateTo?: FileBrowserNavigation | null;
81
+ /**
82
+ * Called when the user navigates inside the browser (selects a file or opens/closes the viewer)
83
+ * so the parent can reflect it in the URL. The browser never touches the URL itself.
84
+ */
85
+ onNavigateTo?: (navigation: FileBrowserNavigation | null) => void;
64
86
  /** Theme name */
65
87
  themeName?: ThemeName;
66
88
  /** Theme type. */
@@ -132,6 +154,10 @@ export declare class FileBrowserClass extends Component<FileBrowserProps, FileBr
132
154
  private readonly limitToObjectID;
133
155
  private readonly limitToPath;
134
156
  private lastSelect;
157
+ /** Last navigation applied from `navigateTo` or reported via `onNavigateTo` (loop guard). */
158
+ private lastNav;
159
+ /** True while applying `navigateTo`, so the derived-state watcher does not echo it back. */
160
+ private applyingNav;
135
161
  private setOpacityTimer;
136
162
  private cacheFoldersTimeout;
137
163
  private foldersLoading;
@@ -168,7 +194,18 @@ export declare class FileBrowserClass extends Component<FileBrowserProps, FileBr
168
194
  renderToolbar(): JSX.Element;
169
195
  findItem(id: string, folders?: Folders | null): null | FolderOrFileItem;
170
196
  renderInputDialog(): JSX.Element | null;
171
- componentDidUpdate(): void;
197
+ /** Strip the image prefix from a viewer href to get back the raw file path. */
198
+ private viewerToId;
199
+ /** Derive the current navigation target from the viewer/selection state. */
200
+ private getStateNav;
201
+ private static navEqual;
202
+ /** Apply a navigation target coming from the parent (`navigateTo`): select + open the viewer. */
203
+ private applyNavigateTo;
204
+ /** Apply the initial `navigateTo` once the browser is ready (called from componentDidMount). */
205
+ applyInitialNavigateTo(): void;
206
+ /** Reconcile `navigateTo` (parent/URL) with the browser's selection/viewer state. */
207
+ private reconcileNavigation;
208
+ componentDidUpdate(prevProps: FileBrowserProps): void;
172
209
  findFirstFolder(id: string): string | null;
173
210
  uploadFile(fileName: string, data: string): Promise<void>;
174
211
  renderUpload(): JSX.Element[] | null;
@@ -394,6 +394,10 @@ export class FileBrowserClass extends Component {
394
394
  limitToObjectID = null;
395
395
  limitToPath = null;
396
396
  lastSelect = null;
397
+ /** Last navigation applied from `navigateTo` or reported via `onNavigateTo` (loop guard). */
398
+ lastNav = null;
399
+ /** True while applying `navigateTo`, so the derived-state watcher does not echo it back. */
400
+ applyingNav = false;
397
401
  setOpacityTimer = null;
398
402
  cacheFoldersTimeout = null;
399
403
  foldersLoading = null;
@@ -543,7 +547,9 @@ export class FileBrowserClass extends Component {
543
547
  }
544
548
  async componentDidMount() {
545
549
  this.mounted = true;
546
- this.loadFolders().catch(error => console.error(`Cannot load folders: ${error}`));
550
+ this.loadFolders()
551
+ .then(() => this.applyInitialNavigateTo())
552
+ .catch(error => console.error(`Cannot load folders: ${error}`));
547
553
  this.browseList = [];
548
554
  this.browseListRunning = false;
549
555
  this.supportSubscribes = await this.props.socket.checkFeatureSupported('BINARY_STATE_EVENT');
@@ -1372,7 +1378,86 @@ export class FileBrowserClass extends Component {
1372
1378
  }
1373
1379
  return null;
1374
1380
  }
1375
- componentDidUpdate( /* prevProps , prevState, snapshot */) {
1381
+ // --- Routing (navigateTo / onNavigateTo) ---
1382
+ // The browser never reads the URL; the parent drives it via `navigateTo` and is informed of user
1383
+ // navigation via `onNavigateTo`. All URL parsing/encoding lives in the parent component.
1384
+ /** Strip the image prefix from a viewer href to get back the raw file path. */
1385
+ viewerToId(viewer) {
1386
+ return viewer.startsWith(this.imagePrefix) ? viewer.substring(this.imagePrefix.length) : viewer;
1387
+ }
1388
+ /** Derive the current navigation target from the viewer/selection state. */
1389
+ getStateNav() {
1390
+ if (this.state.viewer) {
1391
+ return { mode: 'view', id: this.viewerToId(this.state.viewer) };
1392
+ }
1393
+ if (this.state.selected) {
1394
+ return { mode: 'select', id: this.state.selected };
1395
+ }
1396
+ return null;
1397
+ }
1398
+ static navEqual(a, b) {
1399
+ if (!a || !b) {
1400
+ return !a && !b;
1401
+ }
1402
+ return a.mode === b.mode && a.id === b.id;
1403
+ }
1404
+ /** Apply a navigation target coming from the parent (`navigateTo`): select + open the viewer. */
1405
+ applyNavigateTo(nav) {
1406
+ this.applyingNav = true;
1407
+ const done = () => {
1408
+ this.applyingNav = false;
1409
+ };
1410
+ if (!nav?.id) {
1411
+ this.setState({ viewer: '', formatEditFile: '' }, done);
1412
+ return;
1413
+ }
1414
+ const { id } = nav;
1415
+ this.select(id, null, () => {
1416
+ this.scrollToSelected();
1417
+ if (nav.mode === 'view') {
1418
+ this.setState({ viewer: this.imagePrefix + id, formatEditFile: Utils.getFileExtension(id) }, done);
1419
+ }
1420
+ else {
1421
+ this.setState({ viewer: '', formatEditFile: '' }, done);
1422
+ }
1423
+ });
1424
+ }
1425
+ /** Apply the initial `navigateTo` once the browser is ready (called from componentDidMount). */
1426
+ applyInitialNavigateTo() {
1427
+ const nav = this.props.navigateTo ?? null;
1428
+ if (nav?.id) {
1429
+ this.lastNav = nav;
1430
+ this.applyNavigateTo(nav);
1431
+ }
1432
+ else {
1433
+ // Don't push the restored-from-localStorage selection into the URL on load.
1434
+ this.lastNav = this.getStateNav();
1435
+ }
1436
+ }
1437
+ /** Reconcile `navigateTo` (parent/URL) with the browser's selection/viewer state. */
1438
+ reconcileNavigation(prevProps) {
1439
+ if (this.props.navigateTo === undefined && !this.props.onNavigateTo) {
1440
+ return; // routing not used by this consumer
1441
+ }
1442
+ if (this.applyingNav) {
1443
+ return;
1444
+ }
1445
+ const propNav = this.props.navigateTo ?? null;
1446
+ const stateNav = this.getStateNav();
1447
+ if (FileBrowserClass.navEqual(propNav, stateNav)) {
1448
+ this.lastNav = stateNav;
1449
+ return;
1450
+ }
1451
+ if (!FileBrowserClass.navEqual(propNav, prevProps.navigateTo ?? null)) {
1452
+ this.lastNav = propNav;
1453
+ this.applyNavigateTo(propNav);
1454
+ }
1455
+ else if (!FileBrowserClass.navEqual(stateNav, this.lastNav)) {
1456
+ this.lastNav = stateNav;
1457
+ this.props.onNavigateTo?.(stateNav);
1458
+ }
1459
+ }
1460
+ componentDidUpdate(prevProps /* , prevState, snapshot */) {
1376
1461
  if (this.setOpacityTimer) {
1377
1462
  clearTimeout(this.setOpacityTimer);
1378
1463
  }
@@ -1383,6 +1468,7 @@ export class FileBrowserClass extends Component {
1383
1468
  items[i].style.opacity = '1';
1384
1469
  }
1385
1470
  }, 100);
1471
+ this.reconcileNavigation(prevProps);
1386
1472
  }
1387
1473
  findFirstFolder(id) {
1388
1474
  let parentFolder = id;