From fa3304fe0bfa4be6521d1fb2786114225a33afe0 Mon Sep 17 00:00:00 2001 From: Babayaga Date: Fri, 20 Feb 2026 19:37:39 +0100 Subject: [PATCH] filebrowser ole :) --- packages/ui/shared/src/ui/schemas.ts | 5 --- .../widgets/WidgetPropertiesForm.tsx | 20 ++++++------ packages/ui/src/lib/registerWidgets.ts | 7 +++++ .../src/modules/storage/FileBrowserWidget.tsx | 31 ++++++++++++++++--- 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/packages/ui/shared/src/ui/schemas.ts b/packages/ui/shared/src/ui/schemas.ts index 6c2aec5b..26995fdc 100644 --- a/packages/ui/shared/src/ui/schemas.ts +++ b/packages/ui/shared/src/ui/schemas.ts @@ -167,11 +167,6 @@ export const FileBrowserWidgetSchema = z.object({ viewMode: z.enum(['list', 'thumbs']).default('list'), sortBy: z.enum(['name', 'ext', 'date', 'type']).default('name'), showToolbar: z.boolean().default(true), - canChangeMount: z.boolean().default(true), - allowFileViewer: z.boolean().default(true), - allowLightbox: z.boolean().default(true), - allowDownload: z.boolean().default(true), - jail: z.boolean().default(false), variables: WidgetVariablesSchema, }); diff --git a/packages/ui/src/components/widgets/WidgetPropertiesForm.tsx b/packages/ui/src/components/widgets/WidgetPropertiesForm.tsx index 96fd8c7e..c59e2754 100644 --- a/packages/ui/src/components/widgets/WidgetPropertiesForm.tsx +++ b/packages/ui/src/components/widgets/WidgetPropertiesForm.tsx @@ -67,9 +67,11 @@ export const WidgetPropertiesForm: React.FC = ({ }, [currentProps]); const updateSetting = (key: string, value: any) => { - const newSettings = { ...settings, [key]: value }; - setSettings(newSettings); - onSettingsChange(newSettings); + setSettings(prev => { + const newSettings = { ...prev, [key]: value }; + onSettingsChange(newSettings); + return newSettings; + }); }; const renderField = (key: string, config: any) => { @@ -422,6 +424,8 @@ export const WidgetPropertiesForm: React.FC = ({ setVfsPickerField(key); setVfsPickerMountKey(mountKey); setVfsPickerPathKey(pathKey); + setVfsBrowseMount(settings[mountKey] || config.defaultMount || 'home'); + setVfsBrowsePath(settings[pathKey] || '/'); setVfsPickerOpen(true); }} > @@ -624,10 +628,6 @@ export const WidgetPropertiesForm: React.FC = ({ {/* VFS Browse Dialog */} { if (!open) setVfsPickerOpen(false); - else { - setVfsBrowseMount(settings[vfsPickerMountKey] || 'home'); - setVfsBrowsePath(settings[vfsPickerPathKey] || '/'); - } }}> @@ -638,12 +638,10 @@ export const WidgetPropertiesForm: React.FC = ({
{ - setVfsBrowseMount(m); - setVfsBrowsePath('/'); - }} + onMountChange={(m: string) => setVfsBrowseMount(m)} onPathChange={(p: string) => setVfsBrowsePath(p)} viewMode="list" mode="simple" diff --git a/packages/ui/src/lib/registerWidgets.ts b/packages/ui/src/lib/registerWidgets.ts index 83b07e1c..d593d9cb 100644 --- a/packages/ui/src/lib/registerWidgets.ts +++ b/packages/ui/src/lib/registerWidgets.ts @@ -504,6 +504,7 @@ export function registerAllWidgets() { allowFileViewer: true, allowLightbox: true, allowDownload: true, + jail: false, variables: {} }, configSchema: { @@ -588,6 +589,12 @@ export function registerAllWidgets() { label: 'Allow Download', description: 'Show download button for files', default: true + }, + jail: { + type: 'boolean', + label: 'Jail Mode', + description: 'Prevent navigating above the configured mount and path', + default: false } }, minSize: { width: 300, height: 300 }, diff --git a/packages/ui/src/modules/storage/FileBrowserWidget.tsx b/packages/ui/src/modules/storage/FileBrowserWidget.tsx index bf0360f2..9cc6af30 100644 --- a/packages/ui/src/modules/storage/FileBrowserWidget.tsx +++ b/packages/ui/src/modules/storage/FileBrowserWidget.tsx @@ -27,6 +27,7 @@ const FileBrowserWidget: React.FC = (props) => { allowFileViewer = true, allowLightbox = true, allowDownload = true, + jail = false, onPathChange, onMountChange, onSelect, @@ -43,10 +44,19 @@ const FileBrowserWidget: React.FC = (props) => { const mount = onMountChange ? mountProp : internalMount; const currentPath = isControlled ? pathProp : internalPath; + // Jail: normalize the root path for comparison + const jailRoot = pathProp.replace(/\/+$/, '') || '/'; + const updatePath = useCallback((newPath: string) => { + // Jail guard: prevent navigating above the jail root + if (jail) { + const norm = newPath.replace(/\/+$/, '') || '/'; + const root = pathProp.replace(/\/+$/, '') || '/'; + if (root !== '/' && !norm.startsWith(root) && norm !== root) return; + } if (isControlled) onPathChange!(newPath); else setInternalPath(newPath); - }, [isControlled, onPathChange]); + }, [isControlled, onPathChange, jail, pathProp]); const updateMount = useCallback((newMount: string) => { if (onMountChange) onMountChange(newMount); @@ -140,7 +150,14 @@ const FileBrowserWidget: React.FC = (props) => { // ── Sorted items ───────────────────────────────────────────── - const canGoUp = currentPath !== '/' && currentPath !== ''; + const canGoUp = (() => { + if (currentPath === '/' || currentPath === '') return false; + if (jail) { + const normalized = currentPath.replace(/\/+$/, '') || '/'; + return normalized !== jailRoot && normalized !== jailRoot.replace(/\/+$/, ''); + } + return true; + })(); const sorted = useMemo(() => sortNodes(nodes, sortBy, sortAsc), [nodes, sortBy, sortAsc]); const itemCount = sorted.length + (canGoUp ? 1 : 0); const getNode = (idx: number): INode | null => { @@ -189,8 +206,14 @@ const FileBrowserWidget: React.FC = (props) => { const crumbs = [{ label: '/', path: '/' }]; let acc = ''; for (const p of parts) { acc += (acc ? '/' : '') + p; crumbs.push({ label: p, path: acc }); } + // In jail mode, only show crumbs at or below the jail root + if (jail) { + const root = jailRoot === '/' ? '/' : jailRoot; + const rootParts = root === '/' ? 0 : root.split('/').filter(Boolean).length; + return crumbs.slice(rootParts); + } return crumbs; - }, [currentPath, mount]); + }, [currentPath, mount, jail, jailRoot]); // Return-to-sender focus useEffect(() => { @@ -369,7 +392,7 @@ const FileBrowserWidget: React.FC = (props) => {