Add ability to save data
This commit is contained in:
parent
393b4a5860
commit
88bd52763c
5 changed files with 100 additions and 72 deletions
|
@ -5,21 +5,20 @@ import Quote from "@editorjs/quote";
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { FormChange } from "@saleor/hooks/useForm";
|
||||
import strikethroughIcon from "@saleor/icons/StrikethroughIcon";
|
||||
import classNames from "classnames";
|
||||
import createGenericInlineTool from "editorjs-inline-tool";
|
||||
import React from "react";
|
||||
|
||||
export type RichTextEditorChange = (data: OutputData) => void;
|
||||
export interface RichTextEditorProps {
|
||||
data: OutputData;
|
||||
disabled: boolean;
|
||||
error: boolean;
|
||||
helperText: string;
|
||||
// TODO: Remove any type
|
||||
initial: OutputData | any;
|
||||
label: string;
|
||||
name: string;
|
||||
onChange: FormChange;
|
||||
onChange: RichTextEditorChange;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -99,45 +98,56 @@ const useStyles = makeStyles(
|
|||
{ name: "RichTextEditor" }
|
||||
);
|
||||
|
||||
class NewEditor extends EditorJS {}
|
||||
|
||||
const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||
data,
|
||||
error,
|
||||
helperText,
|
||||
initial,
|
||||
label
|
||||
label,
|
||||
onChange
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
|
||||
const [isFocused, setFocus] = React.useState(false);
|
||||
const editor = React.useRef<EditorJS>();
|
||||
const editorContainer = React.useRef<HTMLDivElement>();
|
||||
React.useEffect(() => {
|
||||
editor.current = new NewEditor({
|
||||
data: initial,
|
||||
holder: editorContainer.current,
|
||||
tools: {
|
||||
header: {
|
||||
class: Header,
|
||||
config: {
|
||||
defaultLevel: 1,
|
||||
levels: [1, 2, 3]
|
||||
}
|
||||
},
|
||||
list: List,
|
||||
quote: Quote,
|
||||
strikethrough: createGenericInlineTool({
|
||||
sanitize: {
|
||||
s: true
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (data) {
|
||||
editor.current = new EditorJS({
|
||||
data,
|
||||
holder: editorContainer.current,
|
||||
onChange: async api => {
|
||||
const savedData = await api.saver.save();
|
||||
onChange(savedData);
|
||||
},
|
||||
shortcut: "CMD+S",
|
||||
tagName: "s",
|
||||
toolboxIcon: strikethroughIcon
|
||||
})
|
||||
tools: {
|
||||
header: {
|
||||
class: Header,
|
||||
config: {
|
||||
defaultLevel: 1,
|
||||
levels: [1, 2, 3]
|
||||
}
|
||||
},
|
||||
list: List,
|
||||
quote: Quote,
|
||||
strikethrough: createGenericInlineTool({
|
||||
sanitize: {
|
||||
s: true
|
||||
},
|
||||
shortcut: "CMD+S",
|
||||
tagName: "s",
|
||||
toolboxIcon: strikethroughIcon
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
React.useEffect(() => () => editor.current.destroy(), []);
|
||||
|
||||
return editor.current?.destroy;
|
||||
},
|
||||
// Rerender editor only if changed from undefined to defined state
|
||||
[data === undefined]
|
||||
);
|
||||
React.useEffect(() => editor.current?.destroy, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
import { OutputData } from "@editorjs/editorjs";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import RichTextEditor, {
|
||||
RichTextEditorChange
|
||||
} from "@saleor/components/RichTextEditor";
|
||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
interface ProductDetailsFormProps {
|
||||
data: {
|
||||
description: RawDraftContentState;
|
||||
description: OutputData;
|
||||
name: string;
|
||||
};
|
||||
disabled?: boolean;
|
||||
errors: ProductErrorFragment[];
|
||||
// Draftail isn't controlled - it needs only initial input
|
||||
// because it's autosaving on its own.
|
||||
// Ref https://github.com/mirumee/saleor/issues/4470
|
||||
initialDescription: RawDraftContentState;
|
||||
|
||||
onDescriptionChange: RichTextEditorChange;
|
||||
onChange(event: any);
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
|
|||
data,
|
||||
disabled,
|
||||
errors,
|
||||
initialDescription,
|
||||
onDescriptionChange,
|
||||
onChange
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
@ -57,13 +57,13 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
|
|||
/>
|
||||
<FormSpacer />
|
||||
<RichTextEditor
|
||||
data={data.description}
|
||||
disabled={disabled}
|
||||
error={!!formErrors.descriptionJson}
|
||||
helperText={getProductErrorMessage(formErrors.descriptionJson, intl)}
|
||||
initial={initialDescription}
|
||||
label={intl.formatMessage(commonMessages.description)}
|
||||
name="description"
|
||||
onChange={onChange}
|
||||
onChange={onDescriptionChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { OutputData } from "@editorjs/editorjs";
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import AvailabilityCard from "@saleor/components/AvailabilityCard";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
|
@ -19,7 +20,6 @@ import { maybe } from "@saleor/misc";
|
|||
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
|
||||
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
|
||||
import { FetchMoreProps, ListActions, ReorderAction } from "@saleor/types";
|
||||
import { convertFromRaw, RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
|
@ -75,11 +75,12 @@ export interface ProductUpdatePageProps extends ListActions {
|
|||
}
|
||||
|
||||
export interface ProductUpdatePageSubmitData extends ProductUpdatePageFormData {
|
||||
addStocks: ProductStockInput[];
|
||||
attributes: ProductAttributeInput[];
|
||||
collections: string[];
|
||||
addStocks: ProductStockInput[];
|
||||
updateStocks: ProductStockInput[];
|
||||
description: OutputData;
|
||||
removeStocks: string[];
|
||||
updateStocks: ProductStockInput[];
|
||||
}
|
||||
|
||||
export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||
|
@ -135,10 +136,6 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
product?.taxType.description
|
||||
);
|
||||
|
||||
const initialDescription = maybe<RawDraftContentState>(() =>
|
||||
JSON.parse(product.descriptionJson)
|
||||
);
|
||||
|
||||
const categories = getChoices(categoryChoiceList);
|
||||
const collections = getChoices(collectionChoiceList);
|
||||
const currency = product?.variants[0]?.price.currency;
|
||||
|
@ -175,7 +172,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
initialDescription={initialDescription}
|
||||
onDescriptionChange={handlers.changeDescription}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
|
@ -262,11 +259,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
description={data.seoDescription}
|
||||
descriptionPlaceholder={maybe(() =>
|
||||
convertFromRaw(data.description)
|
||||
.getPlainText()
|
||||
.slice(0, 300)
|
||||
)}
|
||||
descriptionPlaceholder={""} // TODO: cast description to string
|
||||
slug={data.slug}
|
||||
slugPlaceholder={data.name}
|
||||
loading={disabled}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { OutputData } from "@editorjs/editorjs";
|
||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import useFormset, {
|
||||
|
@ -21,7 +23,6 @@ import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
|||
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import { diff } from "fast-array-diff";
|
||||
import React from "react";
|
||||
|
||||
|
@ -35,7 +36,6 @@ export interface ProductUpdateFormData extends MetadataFormData {
|
|||
changeTaxCode: boolean;
|
||||
chargeTaxes: boolean;
|
||||
collections: string[];
|
||||
description: RawDraftContentState;
|
||||
isAvailable: boolean;
|
||||
isAvailableForPurchase: boolean;
|
||||
isPublished: boolean;
|
||||
|
@ -52,27 +52,36 @@ export interface ProductUpdateFormData extends MetadataFormData {
|
|||
}
|
||||
export interface ProductUpdateData extends ProductUpdateFormData {
|
||||
attributes: ProductAttributeInput[];
|
||||
description: OutputData;
|
||||
stocks: ProductStockInput[];
|
||||
}
|
||||
export interface ProductUpdateSubmitData extends ProductUpdateFormData {
|
||||
attributes: ProductAttributeInput[];
|
||||
collections: string[];
|
||||
description: OutputData;
|
||||
addStocks: ProductStockInput[];
|
||||
updateStocks: ProductStockInput[];
|
||||
removeStocks: string[];
|
||||
}
|
||||
|
||||
type ProductUpdateHandlers = Record<
|
||||
"changeMetadata" | "selectCategory" | "selectCollection" | "selectTaxRate",
|
||||
FormChange
|
||||
> &
|
||||
Record<
|
||||
"changeStock" | "selectAttribute" | "selectAttributeMultiple",
|
||||
FormsetChange<string>
|
||||
> &
|
||||
Record<"addStock" | "deleteStock", (id: string) => void>;
|
||||
interface ProductUpdateHandlers
|
||||
extends Record<
|
||||
| "changeMetadata"
|
||||
| "selectCategory"
|
||||
| "selectCollection"
|
||||
| "selectTaxRate",
|
||||
FormChange
|
||||
>,
|
||||
Record<
|
||||
"changeStock" | "selectAttribute" | "selectAttributeMultiple",
|
||||
FormsetChange<string>
|
||||
>,
|
||||
Record<"addStock" | "deleteStock", (id: string) => void> {
|
||||
changeDescription: RichTextEditorChange;
|
||||
}
|
||||
export interface UseProductUpdateFormResult {
|
||||
change: FormChange;
|
||||
|
||||
data: ProductUpdateData;
|
||||
handlers: ProductUpdateHandlers;
|
||||
hasChanged: boolean;
|
||||
|
@ -155,6 +164,15 @@ function useProductUpdateForm(
|
|||
);
|
||||
const attributes = useFormset(getAttributeInputFromProduct(product));
|
||||
const stocks = useFormset(getStockInputFromProduct(product));
|
||||
const description = React.useRef<OutputData>();
|
||||
|
||||
React.useEffect(() => {
|
||||
try {
|
||||
description.current = JSON.parse(product.descriptionJson);
|
||||
} catch {
|
||||
description.current = undefined;
|
||||
}
|
||||
}, [product]);
|
||||
|
||||
const {
|
||||
isMetadataModified,
|
||||
|
@ -209,28 +227,36 @@ function useProductUpdateForm(
|
|||
opts.taxTypes
|
||||
);
|
||||
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
||||
const changeDescription: RichTextEditorChange = data => {
|
||||
triggerChange();
|
||||
description.current = data;
|
||||
};
|
||||
|
||||
const data: ProductUpdateData = {
|
||||
...form.data,
|
||||
attributes: attributes.data,
|
||||
description: description.current,
|
||||
stocks: stocks.data
|
||||
};
|
||||
const submitData: ProductUpdateSubmitData = {
|
||||
// Need to make it function to always have description.current up to date
|
||||
const getSubmitData = (): ProductUpdateSubmitData => ({
|
||||
...data,
|
||||
...getAvailabilityData(data),
|
||||
...getStocksData(product, stocks.data),
|
||||
...getMetadata(data, isMetadataModified, isPrivateMetadataModified),
|
||||
addStocks: [],
|
||||
attributes: attributes.data
|
||||
};
|
||||
attributes: attributes.data,
|
||||
description: description.current
|
||||
});
|
||||
|
||||
const submit = () => handleFormSubmit(submitData, onSubmit, setChanged);
|
||||
const submit = () => handleFormSubmit(getSubmitData(), onSubmit, setChanged);
|
||||
|
||||
return {
|
||||
change: handleChange,
|
||||
data,
|
||||
handlers: {
|
||||
addStock: handleStockAdd,
|
||||
changeDescription,
|
||||
changeMetadata,
|
||||
changeStock: handleStockChange,
|
||||
deleteStock: handleStockDelete,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { OutputData } from "@editorjs/editorjs";
|
||||
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
|
@ -175,7 +176,6 @@ export interface ProductUpdatePageFormData extends MetadataFormData {
|
|||
changeTaxCode: boolean;
|
||||
chargeTaxes: boolean;
|
||||
collections: string[];
|
||||
description: RawDraftContentState;
|
||||
isAvailable: boolean;
|
||||
isAvailableForPurchase: boolean;
|
||||
isPublished: boolean;
|
||||
|
@ -205,7 +205,6 @@ export function getProductUpdatePageFormData(
|
|||
() => product.collections.map(collection => collection.id),
|
||||
[]
|
||||
),
|
||||
description: maybe(() => JSON.parse(product.descriptionJson)),
|
||||
isAvailable: !!product?.isAvailable,
|
||||
isAvailableForPurchase: !!product?.isAvailableForPurchase,
|
||||
isPublished: maybe(() => product.isPublished, false),
|
||||
|
|
Loading…
Reference in a new issue