Skip to content

Commit

Permalink
feat: add bulk actions to page builder (#3530)
Browse files Browse the repository at this point in the history
  • Loading branch information
leopuleo committed Sep 22, 2023
1 parent 0880ce2 commit d74a0b9
Show file tree
Hide file tree
Showing 33 changed files with 830 additions and 66 deletions.
10 changes: 6 additions & 4 deletions packages/app-aco/src/contexts/records.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,18 +243,20 @@ export const SearchRecordsProvider: React.VFC<Props> = ({ children }) => {
throw new Error("Record `id` is mandatory");
}

const { id: recordId } = parseIdentifier(id);

const { data: response } = await apolloFetchingHandler<GetSearchRecordResponse>(
loadingHandler("GET", setLoading),
() =>
client.query<GetSearchRecordResponse, GetSearchRecordQueryVariables>({
query: GET_RECORD,
variables: { id },
variables: { id: recordId },
fetchPolicy: "network-only"
})
);

if (!response) {
throw new Error(`Could not fetch record "${id}" - no response.`);
throw new Error(`Could not fetch record "${recordId}" - no response.`);
}

const { data, error } = getResponseData(response, mode);
Expand All @@ -266,12 +268,12 @@ export const SearchRecordsProvider: React.VFC<Props> = ({ children }) => {
if (!data) {
// No record found - must be deleted by previous operation
setRecords(prev => {
return prev.filter(record => record.id !== id);
return prev.filter(record => record.id !== recordId);
});
return data;
}
setRecords(prev => {
const index = prev.findIndex(record => record.id === id);
const index = prev.findIndex(record => record.id === recordId);

// No record found in the list - must be added by previous operation
if (index === -1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ const ActionDelete = () => {
return `${count} ${count === 1 ? "entry" : "entries"}`;
}, [worker.items.length]);

const openPublishEntriesDialog = () =>
const openDeleteEntriesDialog = () =>
showConfirmationDialog({
title: "Delete entries",
message: `You are about to publish ${entriesLabel}. Are you sure you want to continue?`,
message: `You are about to delete ${entriesLabel}. Are you sure you want to continue?`,
loadingLabel: `Processing ${entriesLabel}`,
execute: async () => {
await worker.processInSeries(async ({ item, report }) => {
Expand Down Expand Up @@ -69,7 +69,7 @@ const ActionDelete = () => {
return (
<IconButton
icon={<DeleteIcon />}
onAction={openPublishEntriesDialog}
onAction={openDeleteEntriesDialog}
label={`Delete ${entriesLabel}`}
tooltipPlacement={"bottom"}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useMemo } from "react";
import { ReactComponent as MoveIcon } from "@material-design-icons/svg/round/drive_file_move.svg";
import { ReactComponent as MoveIcon } from "@material-design-icons/svg/filled/drive_file_move.svg";
import { useRecords, useMoveToFolderDialog, useNavigateFolder } from "@webiny/app-aco";
import { FolderItem } from "@webiny/app-aco/types";
import { observer } from "mobx-react-lite";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo } from "react";
import { ReactComponent as PublishIcon } from "@material-design-icons/svg/round/publish.svg";
import { ReactComponent as PublishIcon } from "@material-design-icons/svg/filled/publish.svg";
import { useRecords } from "@webiny/app-aco";
import { observer } from "mobx-react-lite";
import { ContentEntryListConfig } from "~/admin/config/contentEntries";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo } from "react";
import { ReactComponent as UnpublishIcon } from "@material-design-icons/svg/round/settings_backup_restore.svg";
import { ReactComponent as UnpublishIcon } from "@material-design-icons/svg/filled/settings_backup_restore.svg";
import { observer } from "mobx-react-lite";
import { useRecords } from "@webiny/app-aco";
import { ContentEntryListConfig } from "~/admin/config/contentEntries";
Expand Down
2 changes: 2 additions & 0 deletions packages/app-page-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@webiny/lexical-editor": "0.0.0",
"@webiny/plugins": "0.0.0",
"@webiny/react-composition": "0.0.0",
"@webiny/react-properties": "0.0.0",
"@webiny/react-router": "0.0.0",
"@webiny/telemetry": "0.0.0",
"@webiny/ui": "0.0.0",
Expand All @@ -58,6 +59,7 @@
"is-hotkey": "^0.1.3",
"lodash": "^4.17.10",
"medium-editor": "^5.23.3",
"mobx-react-lite": "^3.4.3",
"nanoid": "^3.1.20",
"platform": "^1.3.5",
"prop-types": "^15.7.2",
Expand Down
4 changes: 4 additions & 0 deletions packages/app-page-builder/src/PageBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import { ReactComponent as PagesIcon } from "./admin/assets/table_chart-24px.svg
import { WebsiteSettings } from "./modules/WebsiteSettings/WebsiteSettings";
import { AdminPageBuilderContextProvider } from "~/admin/contexts/AdminPageBuilder";
import { DefaultOnPagePublish } from "~/admin/plugins/pageDetails/pageRevisions/DefaultOnPagePublish";
import { DefaultOnPageUnpublish } from "~/admin/plugins/pageDetails/pageRevisions/DefaultOnPageUnpublish";
import { DefaultOnPageDelete } from "~/admin/plugins/pageDetails/pageRevisions/DefaultOnPageDelete";
import { EditorProps, EditorRenderer } from "./admin/components/Editor";
import { PagesModule } from "~/admin/views/Pages/PagesModule";

export type { EditorProps };
export { EditorRenderer };
Expand Down Expand Up @@ -110,12 +112,14 @@ const EditorRendererPlugin = createComponentPlugin(EditorRenderer, () => {
export const PageBuilder: React.FC = () => {
return (
<Fragment>
<PagesModule />
<PageBuilderProviderPlugin />
<EditorRendererPlugin />
<Plugins>
<PageBuilderMenu />
<WebsiteSettings />
<DefaultOnPagePublish />
<DefaultOnPageUnpublish />
<DefaultOnPageDelete />
</Plugins>
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useMemo } from "react";
import { ReactComponent as DeleteIcon } from "@material-design-icons/svg/outlined/delete.svg";
import { useRecords } from "@webiny/app-aco";
import { observer } from "mobx-react-lite";
import { PageListConfig } from "~/admin/config/pages";
import { useAdminPageBuilder } from "~/admin/hooks/useAdminPageBuilder";
import { usePagesPermissions } from "~/hooks/permissions";
import { getPagesLabel } from "~/admin/components/BulkActions/BulkActions";

export const ActionDelete = observer(() => {
const { canDelete } = usePagesPermissions();
const { deletePage, client } = useAdminPageBuilder();
const { removeRecordFromCache } = useRecords();

const { useWorker, useButtons, useDialog } = PageListConfig.Browser.BulkAction;
const { IconButton } = useButtons();
const worker = useWorker();
const { showConfirmationDialog, showResultsDialog } = useDialog();

const pagesLabel = useMemo(() => {
return getPagesLabel(worker.items.length);
}, [worker.items.length]);

const canDeleteAll = useMemo(() => {
return worker.items.every(item => canDelete(item?.createdBy?.id));
}, [worker.items]);

const openDeletePagesDialog = () =>
showConfirmationDialog({
title: "Delete pages",
message: `You are about to delete ${pagesLabel}. Are you sure you want to continue?`,
loadingLabel: `Processing ${pagesLabel}`,
execute: async () => {
await worker.processInSeries(async ({ item, report }) => {
try {
const response = await deletePage(
{ id: item.id },
{
client: client,
mutationOptions: {
update(_, { data }) {
if (data.pageBuilder.deletePage.error) {
return;
}
}
}
}
);

const { error } = response;

if (error) {
throw new Error(
error.message || "Unknown error while deleting the page"
);
}

removeRecordFromCache(item.pid);

report.success({
title: `${item.title}`,
message: "Page successfully deleted."
});
} catch (e) {
report.error({
title: `${item.title}`,
message: e.message
});
}
});

worker.resetItems();

showResultsDialog({
results: worker.results,
title: "Delete pages",
message: "Operation completed, here below you find the complete report:"
});
}
});

if (!canDeleteAll) {
console.log("Does not have permission to delete one or more pages.");
return null;
}

return (
<IconButton
icon={<DeleteIcon />}
onAction={openDeletePagesDialog}
label={`Delete ${pagesLabel}`}
tooltipPlacement={"bottom"}
/>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useMemo } from "react";
import { ReactComponent as ExportIcon } from "@material-design-icons/svg/outlined/file_download.svg";
import { observer } from "mobx-react-lite";
import { PageListConfig } from "~/admin/config/pages";
import useExportPageRevisionSelectorDialog from "~/editor/plugins/defaultBar/components/ExportPageButton/useExportPageRevisionSelectorDialog";
import useExportPageDialog from "~/editor/plugins/defaultBar/components/ExportPageButton/useExportPageDialog";
import { getPagesLabel } from "~/admin/components/BulkActions/BulkActions";

export const ActionExport = observer(() => {
const { showExportPageRevisionSelectorDialog } = useExportPageRevisionSelectorDialog();
const { showExportPageInitializeDialog } = useExportPageDialog();

const { useWorker, useButtons } = PageListConfig.Browser.BulkAction;
const { IconButton } = useButtons();
const worker = useWorker();

const selected = useMemo(() => {
return worker.items.map(item => item.pid);
}, [worker.items]);

const pagesLabel = useMemo(() => {
return getPagesLabel(selected.length);
}, [selected.length]);

const openExportPagesDialog = () =>
showExportPageRevisionSelectorDialog({
onAccept: () => showExportPageInitializeDialog({ ids: selected }),
selected
});

return (
<IconButton
icon={<ExportIcon />}
onAction={openExportPagesDialog}
label={`Export ${pagesLabel}`}
tooltipPlacement={"bottom"}
/>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useCallback, useMemo } from "react";
import { ReactComponent as MoveIcon } from "@material-design-icons/svg/outlined/drive_file_move.svg";
import { useRecords, useMoveToFolderDialog, useNavigateFolder } from "@webiny/app-aco";
import { FolderItem } from "@webiny/app-aco/types";
import { observer } from "mobx-react-lite";
import { PageListConfig } from "~/admin/config/pages";
import { ROOT_FOLDER } from "~/admin/constants";
import { getPagesLabel } from "~/admin/components/BulkActions/BulkActions";

export const ActionMove = observer(() => {
const { moveRecord } = useRecords();
const { currentFolderId } = useNavigateFolder();

const { useWorker, useButtons, useDialog } = PageListConfig.Browser.BulkAction;
const { IconButton } = useButtons();
const worker = useWorker();
const { showConfirmationDialog, showResultsDialog } = useDialog();
const { showDialog: showMoveDialog } = useMoveToFolderDialog();

const pagesLabel = useMemo(() => {
return getPagesLabel(worker.items.length);
}, [worker.items.length]);

const openWorkerDialog = useCallback(
(folder: FolderItem) => {
showConfirmationDialog({
title: "Move pages",
message: `You are about to move ${pagesLabel} to ${folder.title}. Are you sure you want to continue?`,
loadingLabel: `Processing ${pagesLabel}`,
execute: async () => {
await worker.processInSeries(async ({ item, report }) => {
try {
await moveRecord({
id: item.pid,
location: {
folderId: folder.id
}
});

report.success({
title: `${item.title}`,
message: "Page successfully moved."
});
} catch (e) {
report.error({
title: `${item.title}`,
message: e.message
});
}
});

worker.resetItems();

showResultsDialog({
results: worker.results,
title: "Move pages",
message: "Operation completed, here below you find the complete report:"
});
}
});
},
[pagesLabel]
);

const openMovePagesDialog = () =>
showMoveDialog({
title: "Select folder",
message: "Select a new location for selected pages:",
loadingLabel: `Processing ${pagesLabel}`,
acceptLabel: `Move`,
focusedFolderId: currentFolderId || ROOT_FOLDER,
async onAccept({ folder }) {
openWorkerDialog(folder);
}
});

return (
<IconButton
icon={<MoveIcon />}
onAction={openMovePagesDialog}
label={`Move ${pagesLabel}`}
tooltipPlacement={"bottom"}
/>
);
});

0 comments on commit d74a0b9

Please sign in to comment.