From 621f49c3d19613b04e4418437999df3f132b9858 Mon Sep 17 00:00:00 2001 From: Leonardo Giacone Date: Thu, 9 Jan 2025 13:36:26 +0100 Subject: [PATCH] fix: aco small improvements (#4470) --- .../src/components/FolderTree/List/index.tsx | 4 + packages/app-aco/src/contexts/records.tsx | 17 ++ .../UsersTeamsSelection/ListItemMeta.tsx | 2 + .../src/components/Dialogs/DialogsContext.tsx | 146 +++++++++--------- .../BottomInfoBar/SupportedFileTypes.tsx | 8 +- .../FileManagerViewContext.tsx | 37 ++++- .../FileManagerViewProvider/useListFiles.ts | 9 +- .../BulkActions/BulkActions.styled.tsx | 2 +- 8 files changed, 139 insertions(+), 86 deletions(-) diff --git a/packages/app-aco/src/components/FolderTree/List/index.tsx b/packages/app-aco/src/components/FolderTree/List/index.tsx index 627035ca5ad..74eb13c089f 100644 --- a/packages/app-aco/src/components/FolderTree/List/index.tsx +++ b/packages/app-aco/src/components/FolderTree/List/index.tsx @@ -56,6 +56,8 @@ export const List = ({ newTree: NodeModel[], { dragSourceId, dropTargetId }: DropOptions ) => { + // Store the current state of the tree before the drop action + const oldTree = [...treeData]; try { const item = folders.find(folder => folder.id === dragSourceId); @@ -73,6 +75,8 @@ export const List = ({ { refetchFoldersList: true } ); } catch (error) { + // If an error occurred, revert the tree back to its previous state + setTreeData(oldTree); return showSnackbar(error.message); } }; diff --git a/packages/app-aco/src/contexts/records.tsx b/packages/app-aco/src/contexts/records.tsx index 59ebc6778f0..2d64e3ee8df 100644 --- a/packages/app-aco/src/contexts/records.tsx +++ b/packages/app-aco/src/contexts/records.tsx @@ -155,6 +155,10 @@ export const SearchRecordsProvider = ({ children }: Props) => { setRecords(prev => { return [record, ...prev]; }); + setMeta(meta => ({ + ...meta, + totalCount: ++meta.totalCount + })); }, updateRecordInCache: (record: any) => { const { id: recordId } = parseIdentifier(record.id); @@ -179,6 +183,10 @@ export const SearchRecordsProvider = ({ children }: Props) => { record => record.id !== id && !record.id.startsWith(`${id}#`) ); }); + setMeta(meta => ({ + ...meta, + totalCount: --meta.totalCount + })); }, async listRecords(params) { @@ -279,6 +287,10 @@ export const SearchRecordsProvider = ({ children }: Props) => { setRecords(prev => { return prev.filter(record => record.id !== recordId); }); + setMeta(meta => ({ + ...meta, + totalCount: --meta.totalCount + })); return data; } setRecords(prev => { @@ -293,6 +305,11 @@ export const SearchRecordsProvider = ({ children }: Props) => { return next; }); + setMeta(meta => ({ + ...meta, + totalCount: ++meta.totalCount + })); + setTags(tags => { if (!data.tags || data.tags.length === 0) { return tags; diff --git a/packages/app-aco/src/dialogs/DialogSetPermissions/UsersTeamsSelection/ListItemMeta.tsx b/packages/app-aco/src/dialogs/DialogSetPermissions/UsersTeamsSelection/ListItemMeta.tsx index 92a1824c9b0..c54ed40c0d0 100644 --- a/packages/app-aco/src/dialogs/DialogSetPermissions/UsersTeamsSelection/ListItemMeta.tsx +++ b/packages/app-aco/src/dialogs/DialogSetPermissions/UsersTeamsSelection/ListItemMeta.tsx @@ -122,6 +122,8 @@ export const ListItemMeta = ({ disabled={!!disabledReason} // Should prevent first item from being autofocused, but it doesn't. 🤷‍ focusOnOpen={false} + // This is needed because the z-index value is set in `packages/app-admin/src/components/Dialogs/styled.tsx` + portalZIndex={101} > {TARGET_LEVELS.map(level => ( void; - onClose?: () => void; } -export const initializeState = (): State => ({ - title: `Confirmation`, - content: undefined, - acceptLabel: `Confirm`, - cancelLabel: `Cancel`, - loadingLabel: `Loading`, - onAccept: undefined, - onClose: undefined, - open: false, - loading: false +export const initializeState = (params: Partial = {}): DialogState => ({ + id: `dialog-${generateId()}`, + title: params.title ?? `Confirmation`, + content: params.content, + acceptLabel: params.acceptLabel ?? `Confirm`, + cancelLabel: params.cancelLabel ?? `Cancel`, + loadingLabel: params.loadingLabel ?? `Loading`, + onAccept: params.onAccept, + onClose: params.onClose, + open: params.open ?? false, + loading: params.loading ?? false, + element: params.element }); export const DialogsContext = React.createContext(undefined); export const DialogsProvider = ({ children }: DialogsProviderProps) => { const { showSnackbar } = useSnackbar(); + const [dialogs, setDialogs] = useState>(new Map()); - const [state, setState] = useState(initializeState()); - - const showDialog = (params: ShowDialogParams | JSX.Element) => { - setState(state => ({ - ...state, - ...params, - open: true - })); + const showDialog = (params: ShowDialogParams) => { + const newDialog = initializeState({ ...params, open: true }); + setDialogs(dialogs => new Map(dialogs).set(newDialog.id, newDialog)); }; const showCustomDialog = ({ onSubmit, element }: ShowCustomDialogParams) => { - setState(state => ({ - ...state, + const newDialog = initializeState({ element, onAccept: onSubmit, open: true - })); + }); + setDialogs(dialogs => new Map(dialogs).set(newDialog.id, newDialog)); }; - const closeDialog = () => { - if (typeof state.onClose === "function") { - state.onClose(); - } - - setState(state => ({ - ...state, - open: false, - element: undefined, - content: null - })); + const closeDialog = (id: string) => { + setDialogs(dialogs => { + const newDialogs = new Map(dialogs); + newDialogs.delete(id); + return newDialogs; + }); }; - const onSubmit = async (data: GenericFormData) => { - try { - if (typeof state.onAccept === "function") { - setState(state => ({ - ...state, - loading: true - })); + const onSubmit = async (id: string, data: GenericFormData) => { + const dialog = dialogs.get(id); + if (!dialog) { + return; + } - await state.onAccept(data); + try { + if (typeof dialog.onAccept === "function") { + setDialogs(dialogs => { + const newDialogs = new Map(dialogs); + newDialogs.set(id, { ...dialog, loading: true }); + return newDialogs; + }); + + await dialog.onAccept(data); } } catch (error) { showSnackbar(error.message); } finally { - setState(state => ({ - ...state, - loading: false - })); - closeDialog(); + setDialogs(dialogs => { + const newDialogs = new Map(dialogs); + newDialogs.set(id, { ...dialog, loading: false }); + return newDialogs; + }); + closeDialog(id); } }; @@ -122,39 +117,36 @@ export const DialogsProvider = ({ children }: DialogsProviderProps) => { return ( {children} - <> - {state.element ? ( + {Array.from(dialogs.values()).map(dialog => + dialog.element ? ( closeDialog(dialog.id)} + onSubmit={data => onSubmit(dialog.id, data)} > - {state.element} + {dialog.element} - ) : null} - {!state.element ? ( + ) : ( closeDialog(dialog.id)} + onSubmit={data => onSubmit(dialog.id, data)} /> - ) : null} - + ) + )} ); }; -interface DialogsProviderProps { - children: React.ReactNode; -} - export const createDialogsProvider = () => { return createProvider(Component => { return function DialogsProviderDecorator({ children }: DialogsProviderProps) { diff --git a/packages/app-file-manager/src/components/BottomInfoBar/SupportedFileTypes.tsx b/packages/app-file-manager/src/components/BottomInfoBar/SupportedFileTypes.tsx index 6217f16cb81..adb306fd4e5 100644 --- a/packages/app-file-manager/src/components/BottomInfoBar/SupportedFileTypes.tsx +++ b/packages/app-file-manager/src/components/BottomInfoBar/SupportedFileTypes.tsx @@ -45,8 +45,8 @@ const SupportedFileTypes = ({ if (accept.length === 0) { return ( - {t`Showing {currentCount} out of {totalCountLabel} from all file extensions.`({ - currentCount, + {t`Showing {currentCountLabel} out of {totalCountLabel} from all file extensions.`({ + currentCountLabel: String(currentCount), totalCountLabel: getLabel(totalCount) })} @@ -55,9 +55,9 @@ const SupportedFileTypes = ({ return ( - {t`Showing {currentCount} out of {totalCountLabel} from the following file extensions: {files}.`( + {t`Showing {currentCountLabel} out of {totalCountLabel} from the following file extensions: {files}.`( { - currentCount, + currentCountLabel: String(currentCount), totalCountLabel: getLabel(totalCount), files: getUniqueFilePlugins(accept).join(", ") } diff --git a/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/FileManagerViewContext.tsx b/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/FileManagerViewContext.tsx index 24fd0893af9..38545cfbee6 100644 --- a/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/FileManagerViewContext.tsx +++ b/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/FileManagerViewContext.tsx @@ -32,7 +32,7 @@ export interface FileManagerViewContext e hasOnSelectCallback: boolean; listTitle: string; loadMoreFiles: () => void; - meta: ListMeta | undefined; + meta: ListMeta; moveFileToFolder: (fileId: string, folderId: string) => Promise; multiple: boolean; onClose: () => void; @@ -111,7 +111,7 @@ export const FileManagerViewProvider = ({ children, ...props }: FileManagerViewP const tags = useTags(modifiers); const [state, setState] = useStateIfMounted(initializeState()); - const { loading, files, meta, listFiles, setFiles, getListVariables } = useListFiles({ + const { loading, files, meta, listFiles, setFiles, setMeta, getListVariables } = useListFiles({ folderId: currentFolderId, modifiers, state @@ -138,7 +138,7 @@ export const FileManagerViewProvider = ({ children, ...props }: FileManagerViewP }; const loadMoreFiles = () => { - if (meta?.cursor) { + if (meta.cursor) { loadFiles("LIST_MORE", { after: meta.cursor }); } }; @@ -181,6 +181,11 @@ export const FileManagerViewProvider = ({ children, ...props }: FileManagerViewP if (!file) { // No file found - must be deleted by previous operation setFiles(files => files.filter(file => file.id !== id)); + // Decrease totalCount without performing a new API call + setMeta(meta => ({ + ...meta, + totalCount: --meta.totalCount + })); } else { setFiles(prevFiles => { const fileIndex = prevFiles.findIndex(file => file.id === id); @@ -200,6 +205,11 @@ export const FileManagerViewProvider = ({ children, ...props }: FileManagerViewP ...prevFiles.slice(fileIndex + 1) ]; }); + // Increase totalCount without performing a new API call + setMeta(meta => ({ + ...meta, + totalCount: ++meta.totalCount + })); } return file; @@ -236,6 +246,11 @@ export const FileManagerViewProvider = ({ children, ...props }: FileManagerViewP if (newFile) { newFile.tags = removeScopePrefix(newFile.tags || []); setFiles(files => [newFile, ...files]); + // Increase totalCount without performing a new API call + setMeta(meta => ({ + ...meta, + totalCount: ++meta.totalCount + })); } return newFile; }; @@ -290,6 +305,12 @@ export const FileManagerViewProvider = ({ children, ...props }: FileManagerViewP return files; } + // Decrease totalCount without performing a new API call + setMeta(meta => ({ + ...meta, + totalCount: --meta.totalCount + })); + return [...files.slice(0, index), ...files.slice(index + 1)]; }); }; @@ -315,6 +336,11 @@ export const FileManagerViewProvider = ({ children, ...props }: FileManagerViewP if (newFile) { newFile.tags = removeScopePrefix(newFile.tags); setFiles(files => [newFile, ...files]); + // Increase totalCount without performing a new API call + setMeta(meta => ({ + ...meta, + totalCount: ++meta.totalCount + })); } return newFile; }; @@ -325,6 +351,11 @@ export const FileManagerViewProvider = ({ children, ...props }: FileManagerViewP ) => { await updateFile(fileId, { location: { folderId } }); setFiles(files => files.filter(file => file.id !== fileId)); + // Decrease totalCount without performing a new API call + setMeta(meta => ({ + ...meta, + totalCount: --meta.totalCount + })); }; const addScopePrefix = (tags: string[] = []) => { diff --git a/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/useListFiles.ts b/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/useListFiles.ts index a28a45b6629..401b4f8128f 100644 --- a/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/useListFiles.ts +++ b/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/useListFiles.ts @@ -38,11 +38,17 @@ interface UseListFilesParams { state: State; } +const defaultMeta: ListMeta = { + totalCount: 0, + hasMoreItems: false, + cursor: null +}; + export function useListFiles({ modifiers, folderId, state }: UseListFilesParams) { const { identity } = useSecurity(); const fileManager = useFileManagerApi(); const { getDescendantFolders } = useFolders(); - const [meta, setMeta] = useStateIfMounted(undefined); + const [meta, setMeta] = useStateIfMounted(defaultMeta); const [files, setFiles] = useStateIfMounted([]); const [loading, setLoading] = useStateIfMounted>({}); const [lastSort, setLastSort] = useStateIfMounted(undefined); @@ -155,6 +161,7 @@ export function useListFiles({ modifiers, folderId, state }: UseListFilesParams) loading, meta, setFiles, + setMeta, listFiles, getListVariables }; diff --git a/packages/app-headless-cms/src/admin/components/ContentEntries/BulkActions/BulkActions.styled.tsx b/packages/app-headless-cms/src/admin/components/ContentEntries/BulkActions/BulkActions.styled.tsx index c8ed13ba467..247941064e9 100644 --- a/packages/app-headless-cms/src/admin/components/ContentEntries/BulkActions/BulkActions.styled.tsx +++ b/packages/app-headless-cms/src/admin/components/ContentEntries/BulkActions/BulkActions.styled.tsx @@ -9,7 +9,7 @@ export const BulkActionsContainer = styled.div` position: absolute; top: 0; left: 0; - z-index: 4; + z-index: 3; `; export const BulkActionsInner = styled.div`