This commit is contained in:
2025-12-05 00:32:45 +01:00
parent 65ad797378
commit b8a23c7ec4
4 changed files with 49 additions and 51 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ const useDocumentsShell = (): DocumentsShellView => {
openDetailPanel: shell.openDetailPanel as WorkspaceSurfaceConfig['openDetailPanel'], openDetailPanel: shell.openDetailPanel as WorkspaceSurfaceConfig['openDetailPanel'],
closeDetailPanel: shell.closeDetailPanel as WorkspaceSurfaceConfig['closeDetailPanel'], closeDetailPanel: shell.closeDetailPanel as WorkspaceSurfaceConfig['closeDetailPanel'],
previewWorkspaceDocument: shell.previewWorkspaceDocument, previewWorkspaceDocument: shell.previewWorkspaceDocument,
previewDocumentId: (shell.previewDocumentId as Identifier | null | undefined) ?? null, previewDocumentId: (shell.previewDocumentId as Identifier) ?? null,
closeDocumentPreview: shell.closeDocumentPreview as WorkspaceSurfaceConfig['closeDocumentPreview'], closeDocumentPreview: shell.closeDocumentPreview as WorkspaceSurfaceConfig['closeDocumentPreview'],
ensurePreviewData: shell.ensurePreviewData as WorkspaceSurfaceConfig['ensurePreviewData'], ensurePreviewData: shell.ensurePreviewData as WorkspaceSurfaceConfig['ensurePreviewData'],
ensureAssetUrl: shell.ensureAssetUrl as WorkspaceSurfaceConfig['ensureAssetUrl'], ensureAssetUrl: shell.ensureAssetUrl as WorkspaceSurfaceConfig['ensureAssetUrl'],
@@ -51,7 +51,7 @@ import useDetailWorkspace from '../../viewer/logic/useDetailWorkspace';
import useWorkspaceTaxonomies from './useWorkspaceTaxonomies'; import useWorkspaceTaxonomies from './useWorkspaceTaxonomies';
import useWorkspaceBreadcrumbs from '../logic/useWorkspaceBreadcrumbs'; import useWorkspaceBreadcrumbs from '../logic/useWorkspaceBreadcrumbs';
import useWorkspaceSelectionSync from '../features/selection/useWorkspaceSelectionSync'; import useWorkspaceSelectionSync from '../features/selection/useWorkspaceSelectionSync';
import type { DocumentId, FolderId as FolderIdentifier, Identifier } from '../../types/identifiers'; import type { DocumentId, FolderNodeId, Identifier } from '../../types/identifiers';
const EntryType = Object.freeze({ const EntryType = Object.freeze({
document: 'document', document: 'document',
@@ -60,14 +60,13 @@ const EntryType = Object.freeze({
const noop = () => { }; const noop = () => { };
type FolderId = FolderIdentifier | 'root';
import type { Document } from '../../types/documents'; import type { Document } from '../../types/documents';
interface FolderContentsEntry { interface FolderContentsEntry {
folder?: { id?: FolderId; name?: string | null } | null; folder?: { id?: FolderNodeId; name?: string | null } | null;
documents?: Document[]; documents?: Document[];
subfolders?: Array<{ id?: FolderId; name?: string | null;[key: string]: unknown }>; subfolders?: Array<{ id?: FolderNodeId; name?: string | null;[key: string]: unknown }>;
__includesDocuments?: boolean; __includesDocuments?: boolean;
__sortField?: string | null; __sortField?: string | null;
__sortDirection?: string | null; __sortDirection?: string | null;
@@ -210,7 +209,7 @@ const useDocumentsWorkspace = ({
); );
const [draggedDocumentIds, setDraggedDocumentIds] = useState<DocumentId[]>([]); const [draggedDocumentIds, setDraggedDocumentIds] = useState<DocumentId[]>([]);
const [draggedFolderId, setDraggedFolderId] = useState<FolderId | null>(null); const [draggedFolderId, setDraggedFolderId] = useState<FolderNodeId | null>(null);
const [activePreviewId, setActivePreviewId] = useState<DocumentId | null>(routeDocumentId || null); const [activePreviewId, setActivePreviewId] = useState<DocumentId | null>(routeDocumentId || null);
const shellRef = useRef(null); const shellRef = useRef(null);
const assetManagerRef = useRef(null); const assetManagerRef = useRef(null);
@@ -293,7 +292,7 @@ const useDocumentsWorkspace = ({
], ],
); );
const [folderContents, setFolderContents] = useState<Map<FolderId, FolderContentsEntry>>( const [folderContents, setFolderContents] = useState<Map<FolderNodeId, FolderContentsEntry>>(
() => new Map(), () => new Map(),
); );
const folderContentsRef = useRef(folderContents); const folderContentsRef = useRef(folderContents);
@@ -687,12 +686,12 @@ const useDocumentsWorkspace = ({
return filtered.length === prev.length ? prev : filtered; return filtered.length === prev.length ? prev : filtered;
}); });
setFolderContents((prev: Map<FolderId, FolderContentsEntry>) => { setFolderContents((prev: Map<FolderNodeId, FolderContentsEntry>) => {
if (!prev.size) { if (!prev.size) {
return prev; return prev;
} }
let changed = false; let changed = false;
const next = new Map<FolderId, FolderContentsEntry>(); const next = new Map<FolderNodeId, FolderContentsEntry>();
prev.forEach((contents, key) => { prev.forEach((contents, key) => {
const docs = Array.isArray(contents?.documents) ? contents.documents : null; const docs = Array.isArray(contents?.documents) ? contents.documents : null;
if (!docs || docs.length === 0) { if (!docs || docs.length === 0) {
@@ -9,17 +9,15 @@ import {
createDocumentEntryKey, createDocumentEntryKey,
createFolderEntryKey, createFolderEntryKey,
} from '../../../app/entryKey'; } from '../../../app/entryKey';
import type { FolderId as FolderIdentifier, Identifier } from '../../../types/identifiers'; import type { FolderId, FolderNodeId, Identifier } from '../../../types/identifiers';
import type { Document } from '../../../types/documents'; import type { Document } from '../../../types/documents';
type FolderId = FolderIdentifier | 'root';
interface FolderSummary { interface FolderSummary {
id?: FolderId; id?: FolderNodeId;
name?: string; name?: string;
parent_id?: FolderId | null; parent_id?: FolderNodeId | null;
parentId?: FolderId | null; parentId?: FolderNodeId | null;
children?: FolderId[]; children?: FolderNodeId[];
subfolders?: FolderSummary[]; subfolders?: FolderSummary[];
has_children?: boolean; has_children?: boolean;
hasChildren?: boolean; hasChildren?: boolean;
@@ -37,8 +35,8 @@ interface FolderContentsEntry {
} }
interface FolderTreeNode extends FolderSummary { interface FolderTreeNode extends FolderSummary {
id: FolderId; id: FolderNodeId;
children: FolderId[]; children: FolderNodeId[];
expanded?: boolean; expanded?: boolean;
loaded?: boolean; loaded?: boolean;
hasChildren?: boolean; hasChildren?: boolean;
@@ -54,18 +52,18 @@ interface SelectionHelpers {
} }
interface UseFolderTreeOptions { interface UseFolderTreeOptions {
initialSelectedFolder?: FolderId; initialSelectedFolder?: FolderNodeId;
tenantIdRef: MutableRefObject<Identifier | null>; tenantIdRef: MutableRefObject<Identifier | null>;
documentsSortFieldRef: MutableRefObject<string>; documentsSortFieldRef: MutableRefObject<string>;
documentsSortDirectionRef: MutableRefObject<string>; documentsSortDirectionRef: MutableRefObject<string>;
selectionHelpers: SelectionHelpers; selectionHelpers: SelectionHelpers;
setDocuments: Dispatch<SetStateAction<Document[]>>; setDocuments: Dispatch<SetStateAction<Document[]>>;
setFolderContents: Dispatch<SetStateAction<Map<FolderId, FolderContentsEntry>>>; setFolderContents: Dispatch<SetStateAction<Map<FolderNodeId, FolderContentsEntry>>>;
folderContentsRef: MutableRefObject<Map<FolderId, FolderContentsEntry>>; folderContentsRef: MutableRefObject<Map<FolderNodeId, FolderContentsEntry>>;
} }
interface FolderOption { interface FolderOption {
id: FolderId; id: FolderNodeId;
label: string; label: string;
} }
@@ -79,7 +77,7 @@ const useFolderTree = ({
setFolderContents, setFolderContents,
folderContentsRef, folderContentsRef,
}: UseFolderTreeOptions) => { }: UseFolderTreeOptions) => {
const [folderNodes, setFolderNodes] = useState<Map<FolderId, FolderTreeNode>>(() => { const [folderNodes, setFolderNodes] = useState<Map<FolderNodeId, FolderTreeNode>>(() => {
const rootNode = createRootNode() as FolderTreeNode; const rootNode = createRootNode() as FolderTreeNode;
return new Map([[rootNode.id, rootNode]]); return new Map([[rootNode.id, rootNode]]);
}); });
@@ -90,12 +88,12 @@ const useFolderTree = ({
const data = await getFolderTree(); const data = await getFolderTree();
setFolderNodes((prev) => { setFolderNodes((prev) => {
const next = new Map(prev); const next = new Map(prev);
const rootChildren: FolderId[] = []; const rootChildren: FolderNodeId[] = [];
data.forEach((item) => { data.forEach((item) => {
const id = item.id as FolderId; const id = item.id as FolderNodeId;
const parentId = (item.parent_id || 'root') as FolderId; const parentId = (item.parent_id || 'root') as FolderNodeId;
const children = (item.children || []).map((c) => c as FolderId); const children = (item.children || []).map((c) => c as FolderNodeId);
next.set(id, { next.set(id, {
id, id,
@@ -130,7 +128,7 @@ const useFolderTree = ({
fetchTree(); fetchTree();
}, []); }, []);
const [selectedFolder, setSelectedFolder] = useState<FolderId>(initialSelectedFolder || 'root'); const [selectedFolder, setSelectedFolder] = useState<FolderNodeId>(initialSelectedFolder || 'root');
const [currentFolder, setCurrentFolder] = useState<FolderSummary | null>(null); const [currentFolder, setCurrentFolder] = useState<FolderSummary | null>(null);
const [currentSubfolders, setCurrentSubfolders] = useState<FolderSummary[]>([]); const [currentSubfolders, setCurrentSubfolders] = useState<FolderSummary[]>([]);
@@ -149,7 +147,7 @@ const useFolderTree = ({
}, [focusedDocumentId]); }, [focusedDocumentId]);
const applySelectedFolder = useCallback( const applySelectedFolder = useCallback(
(folderId: FolderId, contents?: FolderContentsEntry | null) => { (folderId: FolderNodeId, contents?: FolderContentsEntry | null) => {
const subfolders = Array.isArray(contents?.subfolders) ? contents.subfolders : []; const subfolders = Array.isArray(contents?.subfolders) ? contents.subfolders : [];
const docs = Array.isArray(contents?.documents) ? contents.documents : []; const docs = Array.isArray(contents?.documents) ? contents.documents : [];
const folderInfo = contents?.folder ?? null; const folderInfo = contents?.folder ?? null;
@@ -159,7 +157,7 @@ const useFolderTree = ({
setCurrentFolder(folderInfo); setCurrentFolder(folderInfo);
const availableDocKeys = docs const availableDocKeys = docs
.map((doc) => createDocumentEntryKey(doc?.id as Identifier)) .map((doc) => createDocumentEntryKey((doc as Document)?.id as Identifier))
.filter(Boolean); .filter(Boolean);
const availableDocKeySet = new Set(availableDocKeys); const availableDocKeySet = new Set(availableDocKeys);
const availableFolderKeys = new Set( const availableFolderKeys = new Set(
@@ -212,13 +210,13 @@ const useFolderTree = ({
], ],
); );
const expandFolderAncestors = useCallback((targetId: FolderId | null) => { const expandFolderAncestors = useCallback((targetId: FolderNodeId | null) => {
if (!targetId || targetId === 'root') { if (!targetId || targetId === 'root') {
return; return;
} }
setFolderNodes((prev: Map<FolderId, FolderTreeNode>) => { setFolderNodes((prev: Map<FolderNodeId, FolderTreeNode>) => {
const next = new Map<FolderId, FolderTreeNode>(prev); const next = new Map<FolderNodeId, FolderTreeNode>(prev);
const node = next.get(targetId); const node = next.get(targetId);
let currentId = node?.parentId ?? 'root'; let currentId = node?.parentId ?? 'root';
let guard = 0; let guard = 0;
@@ -238,7 +236,7 @@ const useFolderTree = ({
const ensureFolderData = useCallback( const ensureFolderData = useCallback(
async ( async (
folderId: FolderId, folderId: FolderNodeId,
{ {
includeDocuments = true, includeDocuments = true,
prefetchDepth = 0, prefetchDepth = 0,
@@ -289,8 +287,8 @@ const useFolderTree = ({
const data = await listFolderContents<FolderContentsEntry>(path, params); const data = await listFolderContents<FolderContentsEntry>(path, params);
const childFolders = Array.isArray(data.subfolders) ? data.subfolders : []; const childFolders = Array.isArray(data.subfolders) ? data.subfolders : [];
const childIds = childFolders const childIds = childFolders
.map((child) => (child?.id ?? null) as FolderId | null) .map((child) => (child?.id ?? null) as FolderNodeId | null)
.filter((id): id is FolderId => Boolean(id)); .filter((id): id is FolderNodeId => Boolean(id));
const enriched = { const enriched = {
...data, ...data,
@@ -303,8 +301,8 @@ const useFolderTree = ({
return enriched; return enriched;
} }
setFolderNodes((prev: Map<FolderId, FolderTreeNode>) => { setFolderNodes((prev: Map<FolderNodeId, FolderTreeNode>) => {
const next = new Map<FolderId, FolderTreeNode>(prev); const next = new Map<FolderNodeId, FolderTreeNode>(prev);
const existingNode = next.get(folderId) || { const existingNode = next.get(folderId) || {
id: folderId, id: folderId,
name: folderId === 'root' ? DEFAULT_FOLDER_NAME : data.folder?.name || 'Folder', name: folderId === 'root' ? DEFAULT_FOLDER_NAME : data.folder?.name || 'Folder',
@@ -326,7 +324,7 @@ const useFolderTree = ({
}); });
childFolders.forEach((child) => { childFolders.forEach((child) => {
const childId = (child?.id ?? null) as FolderId | null; const childId = (child?.id ?? null) as FolderNodeId | null;
if (!childId) { if (!childId) {
return; return;
} }
@@ -346,7 +344,7 @@ const useFolderTree = ({
next.set(childId, { next.set(childId, {
id: childId, id: childId,
name: child.name, name: child.name,
parentId: (child.parent_id ?? 'root') as FolderId, parentId: (child.parent_id ?? 'root') as FolderNodeId,
children: previousChildren, children: previousChildren,
expanded: childNode?.expanded ?? false, expanded: childNode?.expanded ?? false,
loaded: childNode?.loaded ?? false, loaded: childNode?.loaded ?? false,
@@ -369,11 +367,11 @@ const useFolderTree = ({
); );
} }
setFolderContents((prev: Map<FolderId, FolderContentsEntry>) => { setFolderContents((prev: Map<FolderNodeId, FolderContentsEntry>) => {
if (tenantIdRef.current !== requestTenantId) { if (tenantIdRef.current !== requestTenantId) {
return prev; return prev;
} }
const next = new Map<FolderId, FolderContentsEntry>(prev); const next = new Map<FolderNodeId, FolderContentsEntry>(prev);
if (includeDocuments) { if (includeDocuments) {
next.set(folderId, enriched); next.set(folderId, enriched);
} else { } else {
@@ -408,7 +406,7 @@ const useFolderTree = ({
); );
const ensureFolderAncestorsLoaded = useCallback( const ensureFolderAncestorsLoaded = useCallback(
async (targetId: FolderId | null) => { async (targetId: FolderNodeId | null) => {
if (!targetId || targetId === 'root') { if (!targetId || targetId === 'root') {
return; return;
} }
@@ -429,7 +427,7 @@ const useFolderTree = ({
); );
const isInvalidFolderDrop = useCallback( const isInvalidFolderDrop = useCallback(
(sourceId: FolderId | null, targetId: FolderId | null) => { (sourceId: FolderNodeId | null, targetId: FolderNodeId | null) => {
if (!sourceId) return false; if (!sourceId) return false;
if (!targetId || targetId === 'root') { if (!targetId || targetId === 'root') {
return false; return false;
@@ -456,8 +454,8 @@ const useFolderTree = ({
const resetFolderTreeState = useCallback(() => { const resetFolderTreeState = useCallback(() => {
const rootNode = createRootNode() as FolderTreeNode; const rootNode = createRootNode() as FolderTreeNode;
setFolderNodes(new Map<FolderId, FolderTreeNode>([[rootNode.id, rootNode]])); setFolderNodes(new Map<FolderNodeId, FolderTreeNode>([[rootNode.id, rootNode]]));
setFolderContents(new Map<FolderId, FolderContentsEntry>()); setFolderContents(new Map<FolderNodeId, FolderContentsEntry>());
setSelectedFolder('root'); setSelectedFolder('root');
setCurrentFolder(null); setCurrentFolder(null);
setCurrentSubfolders([]); setCurrentSubfolders([]);
@@ -469,10 +467,10 @@ const useFolderTree = ({
}, [selectedFolder, currentFolder]); }, [selectedFolder, currentFolder]);
const folderOptions: FolderOption[] = useMemo(() => { const folderOptions: FolderOption[] = useMemo(() => {
const cache = new Map<FolderId, string>(); const cache = new Map<FolderNodeId, string>();
const computePath = (id: FolderId | null): string => { const computePath = (id: FolderNodeId | null): string => {
if (cache.has(id as FolderId)) { if (cache.has(id as FolderNodeId)) {
return cache.get(id as FolderId) as string; return cache.get(id as FolderNodeId) as string;
} }
if (!id || id === 'root') { if (!id || id === 'root') {
cache.set('root', DEFAULT_FOLDER_NAME); cache.set('root', DEFAULT_FOLDER_NAME);
@@ -506,7 +504,7 @@ const useFolderTree = ({
}, [folderNodes]); }, [folderNodes]);
const folderLabelMap = useMemo(() => { const folderLabelMap = useMemo(() => {
const map = new Map<FolderId, string>(); const map = new Map<FolderNodeId, string>();
folderOptions.forEach((option) => { folderOptions.forEach((option) => {
map.set(option.id, option.label); map.set(option.id, option.label);
}); });
+1
View File
@@ -3,6 +3,7 @@ export type Identifier = string;
export type DocumentId = Identifier; export type DocumentId = Identifier;
export type FolderId = Identifier; export type FolderId = Identifier;
export type FolderNodeId = FolderId | 'root';
export type CapabilitySetId = Identifier; export type CapabilitySetId = Identifier;
export type CapabilityValue = Identifier; export type CapabilityValue = Identifier;
export type TenantId = Identifier; export type TenantId = Identifier;