Skip to content

Commit

Permalink
fix(file-manager): add bulk editing of extension fields (#3715)
Browse files Browse the repository at this point in the history
  • Loading branch information
leopuleo authored and Pavel910 committed Nov 23, 2023
1 parent f038466 commit f1e0d28
Show file tree
Hide file tree
Showing 30 changed files with 1,676 additions and 5 deletions.
3 changes: 2 additions & 1 deletion apps/api/graphql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ export const handler = createHandler({
type: "text",
renderer: {
name: "text-input"
}
},
bulkEdit: true
});

modifier.addField({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FILE_MODEL_ID } from "~/cmsFileStorage/file.model";
import { createModelField } from "~/cmsFileStorage/createModelField";
import { CmsPrivateModelFull } from "@webiny/api-headless-cms";

type CmsModelField = Omit<BaseModelField, "storageId">;
type CmsModelField = Omit<BaseModelField, "storageId"> & { bulkEdit?: boolean };

class CmsModelFieldsModifier {
private fields: BaseModelField[];
Expand All @@ -14,8 +14,11 @@ class CmsModelFieldsModifier {
}

addField(field: CmsModelField) {
const { bulkEdit, tags, ...rest } = field;

this.fields.push({
...field,
...rest,
tags: (tags ?? []).concat(bulkEdit ? ["$bulk-edit"] : []),
storageId: `${field.type}@${field.id}`
});
}
Expand Down
5 changes: 5 additions & 0 deletions packages/app-file-manager/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const base = require("../../jest.config.base");

module.exports = {
...base({ path: __dirname })
};
3 changes: 2 additions & 1 deletion packages/app-file-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"react-custom-scrollbars": "^4.2.1",
"react-dom": "17.0.2",
"react-hotkeyz": "^1.0.4",
"react-lazy-load": "^3.1.14"
"react-lazy-load": "^3.1.14",
"zod": "^3.22.4"
},
"devDependencies": {
"@babel/cli": "^7.22.6",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import styled from "@emotion/styled";
import { ReactComponent as AddIcon } from "@material-design-icons/svg/round/add.svg";
import { Dialog } from "@webiny/ui/Dialog";

export const ActionEditFormContainer = styled.div`
margin: -24px !important;
`;

export const DialogContainer = styled(Dialog)`
z-index: 22;
.mdc-dialog__surface {
width: 800px;
min-width: 800px;
}
`;

export const BatchEditorContainer = styled.div`
padding: 24px;
`;

export const AddOperationInner = styled.div`
padding: 24px 0 0;
text-align: center;
`;

interface ButtonIconProps {
disabled?: boolean;
}

export const ButtonIcon = styled(AddIcon)<ButtonIconProps>`
fill: ${props =>
props.disabled ? "var(--mdc-theme-text-hint-on-light)" : "var(--mdc-theme-primary)"};
width: 18px;
margin-right: 8px;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React, { useCallback, useEffect, useMemo } from "react";
import { ReactComponent as EditIcon } from "@material-design-icons/svg/outlined/edit.svg";
import { prepareFormData } from "@webiny/app-headless-cms-common";
import { observer } from "mobx-react-lite";
import omit from "lodash/omit";

import { FileManagerViewConfig } from "~/modules/FileManagerRenderer/FileManagerView/FileManagerViewConfig";
import { useFileManagerApi } from "~/modules/FileManagerApiProvider/FileManagerApiContext";
import { useFileManagerView } from "~/modules/FileManagerRenderer/FileManagerViewProvider";

import { useFileModel } from "~/hooks/useFileModel";
import { getFilesLabel } from "~/components/BulkActions";
import { GraphQLInputMapper } from "~/components/BulkActions/ActionEdit/GraphQLInputMapper";
import { BatchDTO } from "~/components/BulkActions/ActionEdit/domain";

import { BatchEditorDialog } from "./BatchEditorDialog";
import { ActionEditPresenter } from "./ActionEditPresenter";

export const ActionEdit = observer(() => {
const { fields: defaultFields } = useFileModel();
const {
useWorker,
useButtons,
useDialog: useBulkActionDialog
} = FileManagerViewConfig.Browser.BulkAction;
const worker = useWorker();
const { updateFile } = useFileManagerView();
const { canEdit } = useFileManagerApi();
const { IconButton } = useButtons();
const { showConfirmationDialog, showResultsDialog } = useBulkActionDialog();

const presenter = useMemo<ActionEditPresenter>(() => {
return new ActionEditPresenter();
}, []);

useEffect(() => {
presenter.load(defaultFields);
}, [defaultFields]);

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

const canEditAll = useMemo(() => {
return worker.items.every(item => canEdit(item));
}, [worker.items]);

const openWorkerDialog = (batch: BatchDTO) => {
showConfirmationDialog({
title: "Edit files",
message: `You are about to edit ${filesLabel}. Are you sure you want to continue?`,
loadingLabel: `Processing ${filesLabel}`,
execute: async () => {
await worker.processInSeries(async ({ item, report }) => {
try {
const extensions = defaultFields.find(
field => field.fieldId === "extensions"
);

const extensionsData = GraphQLInputMapper.toGraphQLExtensions(
item.extensions,
batch
);

const output = omit(item, ["id", "createdBy", "createdOn", "src"]);

const fileData = {
...output,
extensions: prepareFormData(
extensionsData,
extensions?.settings?.fields || []
)
};

await updateFile(item.id, fileData);

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

worker.resetItems();

showResultsDialog({
results: worker.results,
title: "Edit files",
message: "Finished editing files! See full report below:"
});
}
});
};

const onBatchEditorSubmit = useCallback(
(batch: BatchDTO) => {
presenter.closeEditor();
openWorkerDialog(batch);
},
[openWorkerDialog]
);

if (!presenter.vm.show) {
return null;
}

if (!canEditAll) {
console.log("You don't have permissions to edit files.");
return null;
}

return (
<>
<IconButton
icon={<EditIcon />}
onAction={() => presenter.openEditor()}
label={`Edit ${filesLabel}`}
tooltipPlacement={"bottom"}
/>
<BatchEditorDialog
onClose={() => presenter.closeEditor()}
fields={presenter.vm.fields}
batch={presenter.vm.currentBatch}
vm={presenter.vm.editorVm}
onApply={onBatchEditorSubmit}
/>
</>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { FileItem } from "@webiny/app-admin/types";

export type ActionFormData = Partial<Omit<FileItem, "id">>;
Loading

0 comments on commit f1e0d28

Please sign in to comment.