Skip to content

Commit

Permalink
feat(uniforms controls): Content in controls have now more props to w…
Browse files Browse the repository at this point in the history
…ork with

if you customize uniform controls with `Content`, you will now receive more properties, e.g. current node id
and a function to update current data
  • Loading branch information
macrozone committed Nov 3, 2022
1 parent c6a88fb commit ef0343e
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 37 deletions.
23 changes: 16 additions & 7 deletions examples/pages/examples/customformlayout.tsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { AutoFields, ColorPickerField } from '@react-page/editor';
import { AutoFields } from '@react-page/editor';
// The editor core
import type { Value, CellPlugin } from '@react-page/editor';
import Editor, { createValue } from '@react-page/editor';
Expand All @@ -24,13 +24,22 @@ const customContentPluginWithSpecialForm: CellPlugin<{
Renderer: ({ data }) => (
<div>
<h3>Name</h3>
<p>Firstname: {data.firstName}</p>
<p>Lastname: {data.lastName}</p>
<p>Age: {data.age}</p>
<p>
Firstname: {data.firstName}
<br />
Lastname: {data.lastName}
<br />
Age: {data.age}
<br />
</p>
<h3>Adress</h3>
<p>{data.street}</p>
<p>{data.lastName}</p>
<p>Age: {data.age}</p>
<p>
{data.street}
<br />
{data.zip} {data.city}
<br />
{data.country}
</p>
</div>
),
id: 'custom-content-plugin-with-custom-layout',
Expand Down
120 changes: 120 additions & 0 deletions examples/pages/examples/customuniformsexperiments.tsx
@@ -0,0 +1,120 @@
/* eslint-disable react/no-unescaped-entities */
import React, { useState } from 'react';
import { AutoFields, ColorPickerField } from '@react-page/editor';
// The editor core
import type { Value, CellPlugin } from '@react-page/editor';
import Editor, { createValue } from '@react-page/editor';

import slate from '@react-page/plugins-slate';

import PageLayout from '../../components/PageLayout';
import { Button } from '@mui/material';

const customContentPluginWithSpecialForm: CellPlugin<{
title: string;
description: string;
}> = {
Renderer: ({ data }) => (
<div>
<h3>Name</h3>
<p>title: {data.title}</p>
<p>description: {data.description}</p>
</div>
),
id: 'custom-content-plugin-with-custom-actions',
title: 'Custom content plugin',
description: 'Some custom content plugin with special actions',
version: 1,
controls: {
type: 'autoform',
schema: {
properties: {
title: { type: 'string' },
description: { type: 'string' },
},
required: [],
},
Content: ({ nodeId, onChange, remove }) => (
<div>
<p>node {nodeId}</p>

<Button
variant="outlined"
onClick={() => {
onChange(
{
title: 'Good day, sir',
},
{
lang: 'en',
}
);
}}
>
set title in English to "Good day, sir"
</Button>
<Button
variant="outlined"
onClick={() => {
onChange(
{
title: 'Guten Tag',
},
{
lang: 'de',
}
);
}}
>
set title in German to "Guten Tag"
</Button>
<AutoFields />

<Button variant="outlined" onClick={remove} color="error">
Remove this cell
</Button>
</div>
),
},
};

const cellPlugins = [slate(), customContentPluginWithSpecialForm];

const INITIAL_VALUE = createValue(
{
rows: [
[
{
plugin: customContentPluginWithSpecialForm,
},
],
],
},
{
cellPlugins,
lang: 'default',
}
);
export default function CustomFormLayout() {
const [value, setValue] = useState<Value>(INITIAL_VALUE);

return (
<PageLayout>
<Editor
cellPlugins={cellPlugins}
value={value}
onChange={setValue}
languages={[
{
label: 'English',
lang: 'en',
},
{
label: 'Deutsch',
lang: 'de',
},
]}
/>
</PageLayout>
);
}
42 changes: 29 additions & 13 deletions packages/editor/src/core/components/hooks/node.ts
Expand Up @@ -15,6 +15,7 @@ import { getDropLevels } from '../../utils/getDropLevels';
import { useUpdateCellData } from './nodeActions';
import { useLang } from './options';
import { useRenderOption } from './renderOptions';
import type { CellPluginOnChangeOptions } from '../../types';

/**
*
Expand Down Expand Up @@ -353,15 +354,17 @@ export const useDebouncedCellData = (nodeId: string) => {
const cellData = useCellData(nodeId);
const [, setData] = useState(cellData);
const dataRef = useRef(cellData);

const currentLang = useLang();
const cellDataRef = useRef(cellData);

const updateCellData = useUpdateCellData(nodeId);
const updateCellDataImmediate = useUpdateCellData(nodeId);
const updateCellDataDebounced = useCallback(
debounce((options) => {
debounce((options?: CellPluginOnChangeOptions) => {
cellDataRef.current = dataRef.current;
updateCellData(dataRef.current, options);
updateCellDataImmediate(dataRef.current, options);
}, 200),
[updateCellData]
[updateCellDataImmediate]
);

const changed = useMemo(
Expand All @@ -379,16 +382,29 @@ export const useDebouncedCellData = (nodeId: string) => {
}, [changed, cellData]);

const onChange = useCallback(
(partialData: Record<string, unknown>, options: unknown) => {
dataRef.current = {
...dataRef.current,
...partialData,
};
setData(dataRef.current);

updateCellDataDebounced(options);
(
partialData: Record<string, unknown>,
options?: CellPluginOnChangeOptions
) => {
// special handling if non default language is changed (special custom code)
if (options?.lang && options.lang !== currentLang) {
// this hook is a bit hacky, because we keep around state of changes and debounce changes
// its probably not the cleanest solution
// however this handling is problematic, if you change any other language.
// this is rarely used and only in custom code
// however, we don't need the debouncing if other languages are changed, because they are not visible anyway and do not feed back into this component
updateCellDataImmediate(partialData, options);
} else {
dataRef.current = {
...dataRef.current,
...partialData,
};
setData(dataRef.current);

updateCellDataDebounced(options);
}
},
[updateCellDataDebounced, setData]
[updateCellDataDebounced, updateCellDataImmediate, setData, currentLang]
);
return [dataRef.current, onChange] as const;
};
6 changes: 2 additions & 4 deletions packages/editor/src/core/components/hooks/nodeActions.ts
Expand Up @@ -26,6 +26,7 @@ import { useAllCellPluginsForNode } from './node';
import { useEditorStore, useLang } from './options';
import { cloneWithNewIds } from '../../../core/utils/cloneWithNewIds';
import { useDisplayModeReferenceNodeId } from './displayMode';
import type { CellPluginOnChangeOptions } from '../../types';

/**
* @param id id of a node
Expand Down Expand Up @@ -80,10 +81,7 @@ export const useUpdateCellData = (id: string) => {
return useCallback(
(
data: null | { [key: string]: unknown },
options: {
lang?: string;
notUndoable?: boolean;
} = {}
options: CellPluginOnChangeOptions = {}
) => {
dispatch(
updateCellData(id)(data, {
Expand Down
23 changes: 18 additions & 5 deletions packages/editor/src/core/types/plugins.ts
Expand Up @@ -9,6 +9,18 @@ export type DataTType = Record<string, unknown>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type DataTAny = any;

export type CellPluginOnChangeOptions = {
/**
* force the language to update the data.
* If not set, will use the current language
*/
lang?: string;
/**
* whether to push the change to the undo-queue
*/
notUndoable?: boolean;
};

export type CellPluginComponentProps<DataT extends DataTType = DataTAny> = {
/**
* the cells nodeId
Expand All @@ -21,7 +33,7 @@ export type CellPluginComponentProps<DataT extends DataTType = DataTAny> = {
/**
* Should be called with the new data if the plugin's data changes.
*/
onChange: (data: Partial<DataT>, options?: { notUndoable: boolean }) => void;
onChange: (data: Partial<DataT>, options?: CellPluginOnChangeOptions) => void;

/**
* call this to remove the cell
Expand Down Expand Up @@ -75,10 +87,11 @@ export type CellPluginCustomControlsComonent<DataT extends DataTType> =
React.ComponentType<CellPluginComponentProps<DataT>>;

export type CellPluginAutoformControlsContent<DataT extends DataTType> =
React.ComponentType<{
data: DataT;
columnCount?: number;
}>;
React.ComponentType<
{
columnCount?: number;
} & CellPluginComponentProps<DataT>
>;

/**
* controls where you can provide a custom component to render the controls.
Expand Down
11 changes: 3 additions & 8 deletions packages/editor/src/ui/AutoformControls/index.tsx
Expand Up @@ -29,13 +29,8 @@ const getDefaultValue = function (bridge: JSONSchemaBridge): {

type Props<T extends DataTType> = CellPluginComponentProps<T> &
AutoformControlsDef<T>;
export function AutoformControls<T extends DataTType>({
onChange,
data,
schema,
columnCount = 2,
Content,
}: Props<T>) {
export function AutoformControls<T extends DataTType>(props: Props<T>) {
const { onChange, data, schema, columnCount = 2, Content } = props;
const bridge = useMemo(
() => makeUniformsSchema<T>(schema as JsonSchema<T>),
[schema]
Expand All @@ -58,7 +53,7 @@ export function AutoformControls<T extends DataTType>({
onSubmit={onChange}
>
{Content ? (
<Content data={data} columnCount={columnCount} />
<Content {...props} columnCount={columnCount} />
) : (
<div
style={{
Expand Down

0 comments on commit ef0343e

Please sign in to comment.