This commit is contained in:
dominik-zeglen 2020-11-03 14:53:17 +01:00
parent 88bd52763c
commit 437df6fe9a
6 changed files with 146 additions and 42 deletions

View file

@ -19,6 +19,7 @@ export interface RichTextEditorProps {
label: string;
name: string;
onChange: RichTextEditorChange;
onReady?: () => void;
}
const useStyles = makeStyles(
@ -86,7 +87,10 @@ const useStyles = makeStyles(
border: `1px solid ${fade(theme.palette.text.secondary, 0.4)}`,
borderRadius: 4,
boxShadow: `inset 0 0 0 0 ${theme.palette.primary.main}`,
fontSize: theme.typography.body1.fontSize,
padding: theme.spacing(3, 2),
paddingBottom: theme.spacing(),
paddingLeft: 10,
position: "relative",
transition: theme.transitions.duration.short + "ms"
},
@ -103,7 +107,8 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
error,
helperText,
label,
onChange
onChange,
onReady
}) => {
const classes = useStyles({});
@ -120,6 +125,7 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
const savedData = await api.saver.save();
onChange(savedData);
},
onReady,
tools: {
header: {
class: Header,

View file

@ -86,12 +86,6 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
const intl = useIntl();
const localizeDate = useDateLocalize();
// Ensures that it will not change after component rerenders, because it
// generates different block keys and it causes editor to lose its content.
const initialDescription = React.useRef(
convertToRaw(ContentState.createFromText(""))
);
// Display values
const [selectedCategory, setSelectedCategory] = useStateFromProps(
initial?.category || ""
@ -144,8 +138,8 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
data={data}
disabled={disabled}
errors={errors}
initialDescription={initialDescription.current}
onChange={change}
onDescriptionChange={handlers.changeDescription}
/>
<CardSpacer />
{data.attributes.length > 0 && (
@ -167,6 +161,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
weightUnit={weightUnit}
onChange={change}
/>
<CardSpacer />
<ProductPricing
currency={currency}
data={data}

View file

@ -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 } from "@saleor/hooks/useForm";
import useFormset, { FormsetChange } from "@saleor/hooks/useFormset";
@ -17,7 +19,7 @@ import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/Searc
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 useRichText from "@saleor/utils/richText/useRichText";
import React from "react";
import { SearchProductTypes_search_edges_node } from "../../../searches/types/SearchProductTypes";
@ -34,7 +36,7 @@ export interface ProductCreateFormData extends MetadataFormData {
changeTaxCode: boolean;
chargeTaxes: boolean;
collections: string[];
description: RawDraftContentState;
description: OutputData;
isAvailable: boolean;
isAvailableForPurchase: boolean;
isPublished: boolean;
@ -56,19 +58,22 @@ export interface ProductCreateData extends ProductCreateFormData {
stocks: ProductStockInput[];
}
type ProductCreateHandlers = Record<
| "changeMetadata"
| "selectCategory"
| "selectCollection"
| "selectProductType"
| "selectTaxRate",
FormChange
> &
Record<
"changeStock" | "selectAttribute" | "selectAttributeMultiple",
FormsetChange<string>
> &
Record<"addStock" | "deleteStock", (id: string) => void>;
interface ProductCreateHandlers
extends Record<
| "changeMetadata"
| "selectCategory"
| "selectCollection"
| "selectProductType"
| "selectTaxRate",
FormChange
>,
Record<
"changeStock" | "selectAttribute" | "selectAttributeMultiple",
FormsetChange<string>
>,
Record<"addStock" | "deleteStock", (id: string) => void> {
changeDescription: RichTextEditorChange;
}
export interface UseProductCreateFormResult {
change: FormChange;
data: ProductCreateData;
@ -106,7 +111,7 @@ const defaultInitialFormData: ProductCreateFormData &
changeTaxCode: false,
chargeTaxes: false,
collections: [],
description: {} as any,
description: null,
isAvailable: false,
isAvailableForPurchase: false,
isPublished: false,
@ -117,7 +122,7 @@ const defaultInitialFormData: ProductCreateFormData &
publicationDate: "",
seoDescription: "",
seoTitle: "",
sku: null,
sku: "",
slug: "",
stockQuantity: null,
taxCode: null,
@ -152,6 +157,10 @@ function useProductCreateForm(
const [productType, setProductType] = useStateFromProps<ProductType>(
initialProductType || null
);
const [description, changeDescription] = useRichText({
initial: null,
triggerChange
});
const {
makeChangeHandler: makeMetadataChangeHandler
@ -211,19 +220,21 @@ function useProductCreateForm(
);
const changeMetadata = makeMetadataChangeHandler(handleChange);
const data: ProductCreateData = {
const getData = (): ProductCreateData => ({
...form.data,
attributes: attributes.data,
description: description.current,
productType,
stocks: stocks.data
};
const submit = () => onSubmit(data);
});
const submit = () => onSubmit(getData());
return {
change: handleChange,
data,
data: getData(),
handlers: {
addStock: handleStockAdd,
changeDescription,
changeMetadata,
changeStock: handleStockChange,
deleteStock: handleStockDelete,

View file

@ -23,6 +23,7 @@ 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 useRichText from "@saleor/utils/richText/useRichText";
import { diff } from "fast-array-diff";
import React from "react";
@ -164,15 +165,10 @@ 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 [description, changeDescription] = useRichText({
initial: product?.descriptionJson,
triggerChange
});
const {
isMetadataModified,
@ -227,10 +223,6 @@ function useProductUpdateForm(
opts.taxTypes
);
const changeMetadata = makeMetadataChangeHandler(handleChange);
const changeDescription: RichTextEditorChange = data => {
triggerChange();
description.current = data;
};
const data: ProductUpdateData = {
...form.data,

View file

@ -0,0 +1,72 @@
import { OutputData } from "@editorjs/editorjs";
import { renderHook } from "@testing-library/react-hooks";
import useRichText from "./useRichText";
type Fixtures = Record<"short" | "long", OutputData>;
const fixtures: Fixtures = {
long: {
blocks: [
{
data: {
level: 1,
text: "Some header"
},
type: "header"
},
{
data: {
text: "Some text"
},
type: "paragraph"
}
]
},
short: {
blocks: [
{
data: {
text: "Some text"
},
type: "paragraph"
}
]
}
};
describe("useRichText", () => {
it("properly saves data in form", () => {
const triggerChange = jest.fn();
const hook = renderHook(() =>
useRichText({
triggerChange
})
);
const [data, change] = hook.result.current;
expect(data.current).toBe(undefined);
change(fixtures.short);
expect(data.current).toMatchObject(fixtures.short);
expect(triggerChange).toHaveBeenCalled();
});
it("properly updates data in form", () => {
const triggerChange = jest.fn();
const hook = renderHook(() =>
useRichText({
initial: JSON.stringify(fixtures.short),
triggerChange
})
);
const [data, change] = hook.result.current;
expect(data.current).toMatchObject(fixtures.short);
change(fixtures.long);
expect(data.current).toMatchObject(fixtures.long);
expect(triggerChange).toHaveBeenCalled();
});
});

View file

@ -0,0 +1,28 @@
import { OutputData } from "@editorjs/editorjs";
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
import { MutableRefObject, useEffect, useRef } from "react";
function useRichText(opts: {
initial?: string | null;
triggerChange: () => void;
}): [MutableRefObject<OutputData>, RichTextEditorChange] {
const data = useRef<OutputData>(
opts.initial === null ? { blocks: [] } : undefined
);
useEffect(() => {
try {
data.current = JSON.parse(opts.initial);
} catch {
data.current = undefined;
}
}, [opts.initial]);
const change: RichTextEditorChange = newData => {
opts.triggerChange();
data.current = newData;
};
return [data, change];
}
export default useRichText;