From e756007551abd4a64f35edfc4527fb61d91dc612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 29 Aug 2026 20:30:22 +0100 Subject: [PATCH] the file browser can be a file picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A prop, not a second component. `onPickFile` turns opening a file into handing it back rather than opening a viewer; folders still navigate, because choosing a file means getting to it. Building a picker separately would mean reimplementing a listing, a breadcrumb, sorting, thumbnails, upload and keyboard handling — all of it free to drift from the real browser, and none of it better. fofos-admin needs one to choose media and it is the first of several, so this belongs here rather than in the plugin. Co-Authored-By: Claude Opus 5 --- .../FileBrowserApp/FileBrowserApp.tsx | 13 ++++++++++-- .../FileBrowserApp/useFileBrowserApp.ts | 20 +++++++++++++++++++ .../officerdev/src/apps/FileBrowser/index.ts | 1 + src/workspaces/officerdev/src/index.ts | 1 + 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserApp.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserApp.tsx index e4392a25..cf53d1b9 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserApp.tsx +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserApp.tsx @@ -6,7 +6,7 @@ import { TaskRunnerDialog } from './components/TaskRunnerDialog'; import { AgentRunnerDialog } from './components/AgentRunnerDialog'; import { VideoDownloadDialog } from './components/VideoDownloadDialog'; import { DictateDialog } from './components/DictateDialog'; -import { useFileBrowserApp } from './useFileBrowserApp'; +import { useFileBrowserApp, type PickedFile } from './useFileBrowserApp'; type FileBrowserAppProps = { basePath?: string; @@ -27,6 +27,14 @@ type FileBrowserAppProps = { * where the folder is the URL and already does. */ panelId?: string; + /** + * Turn this into a CHOOSER: opening a file hands it back instead of opening a viewer. + * + * Folders still navigate, because choosing a file means getting to it. A prop rather than a second + * component — a picker built separately is a listing, a breadcrumb, a sort, thumbnails and keyboard + * handling reimplemented, all of it free to drift from the real browser. + */ + onPickFile?: (file: PickedFile) => void; }; export const FileBrowserApp = ({ @@ -36,8 +44,9 @@ export const FileBrowserApp = ({ urlPath, panelId, pathParam, + onPickFile, }: FileBrowserAppProps) => { - const fileBrowserManager = useFileBrowserApp(basePath, rootOverride, urlPath, panelId, pathParam); + const fileBrowserManager = useFileBrowserApp(basePath, rootOverride, urlPath, panelId, pathParam, onPickFile); const { handleNavigate } = fileBrowserManager; return ( diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts index 657df1f1..ed6edfd2 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts @@ -36,12 +36,17 @@ export const FILES_PATH_PARAM = 'path'; * not fill the back stack, and no pathname involved at all. A reload lands where you were and the link * can be sent to somebody, which is what was actually wanted. */ +/** What a picker is handed when somebody opens a file in pick mode. */ +export type PickedFile = { name: string; path: string; type: string }; + export const useFileBrowserApp = ( basePath: string, rootOverride?: string, urlPath = false, panelId?: string, pathParam?: string, + /** Set to turn this into a chooser: opening a file calls this instead of a viewer. */ + onPickFile?: (file: PickedFile) => void, ) => { const { t } = useTranslation(); const { user } = useAuth(); @@ -475,6 +480,21 @@ export const useFileBrowserApp = ( setCurrentPath(next); return; } + /* + PICK MODE. + + When a caller is using this browser to CHOOSE a file rather than to work in a folder, opening a + file means handing it back — not opening a viewer the caller would then have to close. Folders + still navigate above, because choosing a file requires getting to it. + + A prop rather than a second browser: a picker built separately would be a listing, a breadcrumb, + a sort, thumbnails and keyboard handling reimplemented, all of it drifting from the real one. + This is the same component with one branch. + */ + if (onPickFile) { + onPickFile({ name: entry.name, path: entryPath(entry.name), type: entry.type }); + return; + } // An archive extracts rather than opening a pane. The viewer has nothing to show for one — it renders // a placeholder reading "Archive file" and the name you can already see in the listing — so a pane // was a step on the way to the only thing anyone opens a zip for. Extract resolves collisions, so a diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/index.ts b/src/workspaces/officerdev/src/apps/FileBrowser/index.ts index a6bbf219..2e07d6e5 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/index.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/index.ts @@ -9,6 +9,7 @@ export { useTasks, type TaskSummary } from './useTasks'; export { useRecentFiles } from './useRecentFiles'; export { usePinnedFiles } from './usePinnedFiles'; export { FileBrowserApp, FileBrowserPanelWrapper } from './FileBrowserApp'; +export type { PickedFile } from './FileBrowserApp/useFileBrowserApp'; export { FileBrowserWidget } from './FileBrowserWidget'; export { TaskRunnerModal } from './FileBrowserApp/components/TaskRunnerModal'; diff --git a/src/workspaces/officerdev/src/index.ts b/src/workspaces/officerdev/src/index.ts index dac96706..828baf21 100644 --- a/src/workspaces/officerdev/src/index.ts +++ b/src/workspaces/officerdev/src/index.ts @@ -59,6 +59,7 @@ export { useRecentFiles, usePinnedFiles, FileBrowserApp, + type PickedFile, FileBrowserPanelWrapper, FileBrowserWidget, TaskRunnerModal,