From 21122e058eace154323ddfe05c70ec2fac898d0c Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 19 Aug 2026 18:47:45 +0200 Subject: [PATCH] feat(webui): add bounded file quick access --- src/govoplan_files/backend/manifest.py | 15 +++- webui/src/features/files/FileQuickAccess.tsx | 81 ++++++++++++++++++++ webui/src/module.ts | 10 +-- 3 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 webui/src/features/files/FileQuickAccess.tsx diff --git a/src/govoplan_files/backend/manifest.py b/src/govoplan_files/backend/manifest.py index e0ba82d..9183ecb 100644 --- a/src/govoplan_files/backend/manifest.py +++ b/src/govoplan_files/backend/manifest.py @@ -578,6 +578,8 @@ manifest = ModuleManifest( required_any=("files:file:read",), order=10, modes=("browse", "select", "upload"), + returned_reference_kinds=("files.file-version",), + help_context_id="files.quick_access.files", ), ), ), @@ -684,8 +686,10 @@ manifest = ModuleManifest( summary="Use managed files in the Records and documents area and keep a compact file surface beside current work.", body=( "Files contributes its authorized workspace to Records and documents. When Quick Access is enabled, the owner-rendered " - "file-space summary appears in Files on the right rail and links back to the complete workspace. View and rail settings " - "do not bypass file ownership, shares, connector policy, integrity gates, or purpose-aware access." + "recent-file selector appears in Files on the right rail and can return one exact file-version reference to the current task. " + "Opening the selector causes Files to re-run its own authorization; completion and cancellation are explicit, and the full Files " + "workspace remains available as a deep-link fallback. View and rail settings may recommend or focus this tool, but do not bypass " + "file ownership, shares, connector policy, integrity gates, or purpose-aware access." ), layer="configured", documentation_types=("user", "admin"), @@ -711,8 +715,11 @@ manifest = ModuleManifest( "summary": "Verwaltete Dateien im Produktbereich Akten und Dokumente und optional neben der aktuellen Arbeit verwenden.", "body": ( "Files ordnet seinen berechtigten Arbeitsbereich Akten und Dokumente zu. Ist der Schnellzugriff aktiviert, erscheint " - "die vom Modul gerenderte Übersicht der Dateibereiche rechts neben der aktuellen Seite. Ansichts- und Leistenkonfiguration " - "umgehen weder Eigentum, Freigaben, Connector-Richtlinien, Integritätsprüfungen noch zweckgebundenen Zugriff." + "die vom Modul gerenderte Auswahl zuletzt berechtigter Dateien rechts neben der aktuellen Seite und kann einen exakten " + "Dateiversionsverweis an die aktuelle Aufgabe zurückgeben. Files prüft die Berechtigung beim Öffnen erneut; Abschluss und " + "Abbruch sind ausdrücklich, und der vollständige Files-Arbeitsbereich bleibt als Ausweichziel erreichbar. Ansichts- und " + "Leistenkonfiguration dürfen das Werkzeug empfehlen oder fokussieren, umgehen aber weder Eigentum, Freigaben, Connector-" + "Richtlinien, Integritätsprüfungen noch zweckgebundenen Zugriff." ), } }, diff --git a/webui/src/features/files/FileQuickAccess.tsx b/webui/src/features/files/FileQuickAccess.tsx new file mode 100644 index 0000000..308ca46 --- /dev/null +++ b/webui/src/features/files/FileQuickAccess.tsx @@ -0,0 +1,81 @@ +import { FileText, X } from "lucide-react"; +import { useCallback } from "react"; +import { + Button, + DismissibleAlert, + LoadingFrame, + SelectionList, + SelectionListItem, + SelectionListItemContent, + useDashboardWidgetData, + type QuickAccessToolRenderContext +} from "@govoplan/core-webui"; +import { listFiles } from "../../api/files"; + +type Props = Pick< + QuickAccessToolRenderContext, + "settings" | "launchContext" | "complete" | "cancel" +>; + +/** + * Bounded file selection. listFiles remains the owner-side authorization + * check; the host receives only a versioned reference after explicit choice. + */ +export default function FileQuickAccess({ + settings, + launchContext, + complete, + cancel +}: Props) { + const load = useCallback( + async () => (await listFiles(settings, { page_size: 7 })).files, + [settings] + ); + const { data: files, loading, error } = useDashboardWidgetData(load, 0); + + return ( + + {launchContext.activeObject ? ( +

+ Select a file for {launchContext.activeObject.label}. Files checks + your current access again before showing this list. +

+ ) : null} + {error ? {error} : null} + + {(files ?? []).map((file) => ( + complete({ + contractVersion: "1", + outcome: "completed", + action: "selected", + reference: { + ownerModule: "files", + kind: "file-version", + objectId: file.version_id, + tenantId: file.tenant_id, + label: file.filename, + version: file.version_id, + path: `/files?fileId=${encodeURIComponent(file.id)}&versionId=${encodeURIComponent(file.version_id)}` + } + })} + > + + ))} + + {!loading && !error && !(files ?? []).length ? ( +

No authorized files are available.

+ ) : null} +
+ +
+
+ ); +} diff --git a/webui/src/module.ts b/webui/src/module.ts index cacdfeb..da9f902 100644 --- a/webui/src/module.ts +++ b/webui/src/module.ts @@ -12,6 +12,7 @@ import { FolderTree } from "./features/files/components/FileManagerComponents"; import FileConnectorSettingsPanel from "./features/files/FileConnectorSettingsPanel"; import ManagedFileChooser from "./features/files/components/ManagedFileChooser"; import FileSpacesWidget from "./features/files/FileSpacesWidget"; +import FileQuickAccess from "./features/files/FileQuickAccess"; import { listFileSpaces, listFiles, listFilesDelta, listFolders, resolveFilePatterns, shareFilesWithTarget } from "./api/files"; import { generatedTranslations } from "./i18n/generatedTranslations"; import "./styles/file-manager.css"; @@ -72,14 +73,7 @@ const fileQuickAccessTools: QuickAccessToolsUiCapability = { tools: [ { id: "files.recent", - render: ({ settings }) => createElement(FileSpacesWidget, { - settings, - refreshKey: 0, - configuration: { - maxItems: 7, - includeConnectors: true - } - }) + render: (context) => createElement(FileQuickAccess, context) } ] };