/** * Copyright 2020-2026, Denis Haev * * MIT License * */ import React, { Component, type JSX } from 'react'; import type { Connection } from '@iobroker/socket-client'; import type { ThemeName, ThemeType, Translate, IobTheme } from '../types'; import { type FileViewerProps } from './FileViewer'; export interface MetaACL extends ioBroker.ObjectACL { file: number; } export interface MetaObject extends ioBroker.MetaObject { acl: MetaACL; } /** * A navigation target the parent can use to drive the browser (and that the browser reports back). * Maps cleanly to a route like `#tab-files//` (the parent encodes the file path, * whose `/` separators would otherwise clash with the hash). The browser never reads the URL. */ export interface FileBrowserNavigation { /** `select` = just highlight the file; `view` = open the file viewer. */ mode: 'select' | 'view'; /** The file path/ID (e.g. `email.admin/custom/assets/Components.js`). */ id: string; } export interface FileBrowserProps { /** The key to identify this component. */ key?: string; /** Additional styling for this component. */ style?: React.CSSProperties; /** The CSS class name. */ className?: string; /** Translation function. */ t: Translate; /** The selected language. */ lang: ioBroker.Languages; /** The socket connection. */ socket: Connection; /** Shows if the component data ready. */ ready?: boolean; /** Is expert mode enabled? (default: false) */ expertMode?: boolean; /** Show the toolbar? (default: false) */ showToolbar?: boolean; /** If defined, allow selecting only files from this folder and subfolders */ limitPath?: string; /** Allow upload of new files? (default: false) */ allowUpload?: boolean; /** Allow download of files? (default: false) */ allowDownload?: boolean; /** Allow creation of new folders? (default: false) */ allowCreateFolder?: boolean; /** Allow deleting files? (default: false) */ allowDelete?: boolean; /** Allow viewing files? (default: false) */ allowView?: boolean; /** Prefix (default: '.') */ imagePrefix?: string; /** Show the expert button? */ showExpertButton?: boolean; /** Type of view */ viewType?: 'Table' | 'Tile'; /** Show the buttons to switch the view from table to tile? (default: false) */ showViewTypeButton?: boolean; /** The ID of the selected file. */ selected?: string | string[]; /** The file extensions to show, like ['png', 'svg', 'bmp', 'jpg', 'jpeg', 'gif']. */ filterFiles?: string[]; /** The file extension categories to show. */ filterByType?: 'images' | 'code' | 'txt'; /** Callback for file selection. */ onSelect?: (id: string | string[], isDoubleClick?: boolean, isFolder?: boolean) => void; /** * Drive selection and the file viewer from the parent (e.g. from the URL). When this prop * changes the browser selects the file and opens the requested viewer. The browser does NOT * read the URL itself — all URL parsing/writing lives in the parent component. */ navigateTo?: FileBrowserNavigation | null; /** * Called when the user navigates inside the browser (selects a file or opens/closes the viewer) * so the parent can reflect it in the URL. The browser never touches the URL itself. */ onNavigateTo?: (navigation: FileBrowserNavigation | null) => void; /** Theme name */ themeName?: ThemeName; /** Theme type. */ themeType?: ThemeType; /** Theme object. */ theme: IobTheme; /** Padding in pixels for folder levels */ levelPadding?: number; restrictToFolder?: string; modalEditOfAccessControl?: (obj: FileBrowserClass) => JSX.Element | null; allowNonRestricted?: boolean; showTypeSelector?: boolean; FileViewer?: React.FC; } export interface FolderOrFileItem { id: string; level: number; name: string; folder: boolean; temp?: boolean; size?: number | undefined; ext?: string | null; modified?: number; title?: ioBroker.StringOrTranslated; meta?: boolean; from?: string; ts?: number; color?: string; icon?: string; acl?: ioBroker.EvaluatedFileACL | MetaACL; } export type Folders = Record; interface FileBrowserState { viewType: 'Table' | 'Tile'; folders: Folders; filterEmpty: boolean; expanded: string[]; currentDir: string; expertMode: boolean; addFolder: boolean; uploadFile: boolean | 'dragging'; deleteItem: string; suppressDeleteConfirm: boolean; viewer: string; formatEditFile: string | null; path: string; selected: string; errorText: string; modalEditOfAccess: boolean; backgroundImage: string | null; queueLength: number; loadAllFolders: boolean; fileErrors: string[]; filterByType: string; showTypesMenu: HTMLButtonElement | null; restrictToFolder: string; pathFocus: boolean; } export declare class FileBrowserClass extends Component { private readonly imagePrefix; private readonly levelPadding; private mounted; private suppressDeleteConfirm; private browseList; private browseListRunning; private initialReadFinished; private supportSubscribes; private _tempTimeout; private readonly limitToObjectID; private readonly limitToPath; private lastSelect; /** Last navigation applied from `navigateTo` or reported via `onNavigateTo` (loop guard). */ private lastNav; /** True while applying `navigateTo`, so the derived-state watcher does not echo it back. */ private applyingNav; private setOpacityTimer; private cacheFoldersTimeout; private foldersLoading; private cacheFolders; private readonly localStorage; private readonly scrollPositions; private readonly refFileDiv; constructor(props: FileBrowserProps); static getDerivedStateFromProps(props: FileBrowserProps, state: FileBrowserState): Partial | null; loadFolders(): Promise; scrollToSelected(): void; componentDidMount(): Promise; componentWillUnmount(): void; browseFoldersCb(foldersList: string[], newFoldersNotNull: Folders, cb: (folders: Folders) => void): void; browseFolders(foldersList: string[], _newFolders?: Folders | null): Promise; readDirSerial(adapter: string, relPath: string): Promise; processBrowseList(level?: number): void; browseFolder(folderId: string, _newFolders?: Folders | null, _checkEmpty?: boolean, force?: boolean): Promise; toggleFolder(item: FolderOrFileItem, e: React.MouseEvent): void; onFileChange: (id: string, fileName: string, size: number | null) => void; changeFolder(e: React.MouseEvent, folder?: string): void; select(id: string, e?: React.MouseEvent | null, cb?: () => void): void; getText(text?: ioBroker.StringOrTranslated | null): string | undefined; renderFolder(item: FolderOrFileItem, expanded?: boolean): JSX.Element | null; renderBackFolder(): JSX.Element; formatSize(size: number | null | undefined): JSX.Element; formatAcl(acl: ioBroker.EvaluatedFileACL | MetaACL | undefined): JSX.Element; getFileIcon(ext: string | null): JSX.Element; static getEditFile(ext: string | null): boolean; setStateBackgroundImage: () => void; getStyleBackgroundImage: () => React.CSSProperties | null; renderFile(item: FolderOrFileItem): JSX.Element; renderItems(folderId: string): JSX.Element | (JSX.Element | null)[]; renderToolbar(): JSX.Element; findItem(id: string, folders?: Folders | null): null | FolderOrFileItem; renderInputDialog(): JSX.Element | null; /** Strip the image prefix from a viewer href to get back the raw file path. */ private viewerToId; /** Derive the current navigation target from the viewer/selection state. */ private getStateNav; private static navEqual; /** Apply a navigation target coming from the parent (`navigateTo`): select + open the viewer. */ private applyNavigateTo; /** Apply the initial `navigateTo` once the browser is ready (called from componentDidMount). */ applyInitialNavigateTo(): void; /** Reconcile `navigateTo` (parent/URL) with the browser's selection/viewer state. */ private reconcileNavigation; componentDidUpdate(prevProps: FileBrowserProps): void; findFirstFolder(id: string): string | null; uploadFile(fileName: string, data: string): Promise; renderUpload(): JSX.Element[] | null; deleteRecursive(id: string): Promise; deleteItem(deleteItem: string): void; renderDeleteDialog(): JSX.Element | null; renderViewDialog(): JSX.Element | null; renderError(): JSX.Element | null; updateItemsAcl(info: FolderOrFileItem[]): void; changeToPath(): void; renderBreadcrumb(): JSX.Element; renderPath(): JSX.Element; render(): JSX.Element; } export declare const FileBrowser: (props: Record) => JSX.Element; export {};