Skip to content

Commit

Permalink
feat: move root props under props key
Browse files Browse the repository at this point in the history
This is a backwards-compatible change, with deprecated warnings. It is necessary to support resolveData on the root.
  • Loading branch information
chrisvxd committed Oct 31, 2023
1 parent 9fb31ac commit 7593584
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 42 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,9 +524,9 @@ The `AppState` object stores the puck application state.

The `Data` object stores the puck page data.

- **root** (`object`):
- **title** (string): Title of the content, typically used for the page title
- **[prop]** (string): User defined data from `root` fields
- **root** (`ComponentData`): The component data for the root of your configuration.
- **props** (object): Extends `ComponentData.props`, with some additional props
- **title** (string, optional): Title of the content, typically used for the page title
- **content** (`ComponentData[]`): Component data for the main content
- **zones** (`object`, optional): Component data for all DropZones
**[zoneCompound]** (`ComponentData[]`): Component data for a specific DropZone `zone` within a component instance
Expand Down
3 changes: 2 additions & 1 deletion apps/demo/app/[...puckPath]/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export function Client({ path, isEdit }: { path: string; isEdit: boolean }) {

useEffect(() => {
if (!isEdit) {
document.title = data?.root?.title || "";
const title = data?.root.props?.title || data.root.title;
document.title = title || "";
}
}, [data, isEdit]);

Expand Down
6 changes: 3 additions & 3 deletions apps/demo/config/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export const initialData: Record<string, Data> = {
props: { size: "96px", id: "VerticalSpace-1687284290127" },
},
],
root: { title: "Puck Example" },
root: { props: { title: "Puck Example" } },
zones: {
"Columns-2d650a8ceb081a2c04f3a2d17a7703ca6efb0d06:column-0": [
{
Expand Down Expand Up @@ -398,11 +398,11 @@ export const initialData: Record<string, Data> = {
},
"/pricing": {
content: [],
root: { title: "Pricing" },
root: { props: { title: "Pricing" } },
},
"/about": {
content: [],
root: { title: "About Us" },
root: { props: { title: "About Us" } },
},
};

Expand Down
34 changes: 27 additions & 7 deletions packages/core/components/Puck/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const PluginRenderer = ({

export function Puck({
config,
data: initialData = { content: [], root: { title: "" } },
data: initialData = { content: [], root: { props: { title: "" } } },
onChange,
onPublish,
plugins = [],
Expand Down Expand Up @@ -253,6 +253,18 @@ export function Puck({

const componentList = useComponentList(config, appState.ui);

// DEPRECATED
const rootProps = data.root.props || data.root;

// DEPRECATED
useEffect(() => {
if (Object.keys(data.root).length > 0 && !data.root.props) {
console.error(
"Warning: Defining props on `root` is deprecated. Please use `root.props`. This will be a breaking change in a future release "
);
}
}, []);

return (
<div className="puck">
<AppProvider
Expand Down Expand Up @@ -416,7 +428,7 @@ export function Puck({
}}
>
<Heading rank={2} size="xs">
{headerTitle || data.root.title || "Page"}
{headerTitle || rootProps.title || "Page"}
{headerPath && (
<small
style={{ fontWeight: 400, marginLeft: 4 }}
Expand Down Expand Up @@ -624,10 +636,18 @@ export function Puck({

resolveData();
} else {
dispatch({
type: "setData",
data: { root: newProps },
});
if (data.root.props) {
dispatch({
type: "setData",
data: { root: { props: { newProps } } },
});
} else {
// DEPRECATED
dispatch({
type: "setData",
data: { root: newProps },
});
}

resolveData();
}
Expand Down Expand Up @@ -659,7 +679,7 @@ export function Puck({
label={field.label}
readOnly={readOnly[fieldName]}
readOnlyFields={readOnly}
value={data.root[fieldName]}
value={rootProps[fieldName]}
onChange={onChange}
/>
);
Expand Down
14 changes: 12 additions & 2 deletions packages/core/components/Render/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,20 @@ import { Config, Data } from "../../types/Config";
import { DropZone, DropZoneProvider } from "../DropZone";

export function Render({ config, data }: { config: Config; data: Data }) {
if (config.root) {
// DEPRECATED
const rootProps = data.root.props || data.root;

const title = rootProps.title || "";

if (config.root?.render) {
return (
<DropZoneProvider value={{ data, config, mode: "render" }}>
<config.root.render {...data.root} editMode={false} id={"puck-root"}>
<config.root.render
{...rootProps}
title={title}
editMode={false}
id={"puck-root"}
>
<DropZone zone={rootDroppableId} />
</config.root.render>
</DropZoneProvider>
Expand Down
19 changes: 17 additions & 2 deletions packages/core/lib/__tests__/use-resolved-data.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const item2 = { type: "MyComponent", props: { id: "MyComponent-2" } };
const item3 = { type: "MyComponent", props: { id: "MyComponent-3" } };

const data: Data = {
root: { title: "" },
root: { props: { title: "" } },
content: [item1],
zones: {
"MyComponent-1:zone": [item2],
Expand All @@ -18,6 +18,15 @@ const data: Data = {
};

const config: Config = {
root: {
resolveData: (data) => {
return {
...data,
props: { title: "Resolved title" },
readOnly: { title: true },
};
},
},
components: {
MyComponent: {
defaultProps: { prop: "example" },
Expand Down Expand Up @@ -76,7 +85,12 @@ describe("use-resolved-data", () => {
},
],
"root": {
"title": "",
"props": {
"title": "Resolved title",
},
"readOnly": {
"title": true,
},
},
"zones": {
"MyComponent-1:zone": [
Expand Down Expand Up @@ -117,6 +131,7 @@ describe("use-resolved-data", () => {
data,
{
...config,
root: {},
components: {
...config.components,
MyComponent: {
Expand Down
9 changes: 7 additions & 2 deletions packages/core/lib/apply-dynamic-props.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Data } from "../types/Config";
import { ComponentData, Data, RootData } from "../types/Config";

export const applyDynamicProps = (
data: Data,
dynamicProps: Record<string, any>
dynamicProps: Record<string, ComponentData>,
rootData?: RootData
) => {
return {
...data,
root: {
...data.root,
...(rootData ? rootData : {}),
},
content: data.content.map((item) => {
return dynamicProps[item.props.id]
? { ...item, ...dynamicProps[item.props.id] }
Expand Down
4 changes: 4 additions & 0 deletions packages/core/lib/resolve-all-data.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Config, Data, MappedItem } from "../types/Config";
import { resolveAllProps } from "./resolve-all-props";
import { resolveRootData } from "./resolve-root-data";

export const resolveAllData = async (
data: Data,
config: Config,
onResolveStart?: (item: MappedItem) => void,
onResolveEnd?: (item: MappedItem) => void
) => {
const dynamicRoot = await resolveRootData(data, config);

const { zones = {} } = data;

const zoneKeys = Object.keys(zones);
Expand All @@ -24,6 +27,7 @@ export const resolveAllData = async (

return {
...data,
root: dynamicRoot,
content: await resolveAllProps(
data.content,
config,
Expand Down
50 changes: 50 additions & 0 deletions packages/core/lib/resolve-root-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Config, Data, RootDataWithProps } from "../types/Config";

export const cache: {
lastChange?: { original: RootDataWithProps; resolved: RootDataWithProps };
} = {};

export const resolveRootData = async (data: Data, config: Config) => {
if (config.root?.resolveData && data.root.props) {
let changed = Object.keys(data.root.props).reduce(
(acc, item) => ({ ...acc, [item]: true }),
{}
);

if (cache.lastChange) {
const { original, resolved } = cache.lastChange;

if (original === data.root) {
return resolved;
}

Object.keys(data.root.props).forEach((propName) => {
if (original.props[propName] === data.root.props![propName]) {
changed[propName] = false;
}
});
}

const rootWithProps = data.root as RootDataWithProps;

const resolvedRoot = await config.root?.resolveData(rootWithProps, {
changed,
});

cache.lastChange = {
original: data.root as RootDataWithProps,
resolved: resolvedRoot as RootDataWithProps,
};

return {
...data.root,
...resolvedRoot,
props: {
...data.root.props,
...resolvedRoot.props,
},
};
}

return data.root;
};
9 changes: 6 additions & 3 deletions packages/core/lib/use-resolved-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Dispatch, useEffect, useState } from "react";
import { PuckAction } from "../reducer";
import { resolveAllProps } from "./resolve-all-props";
import { applyDynamicProps } from "./apply-dynamic-props";
import { resolveRootData } from "./resolve-root-data";

export const useResolvedData = (
data: Data,
Expand Down Expand Up @@ -36,23 +37,25 @@ export const useResolvedData = (
[item.props.id]: { ...prev[item.props.id], loading: false },
}));
}
).then((dynamicContent) => {
).then(async (dynamicContent) => {
const dynamicRoot = await resolveRootData(data, config);

const newDynamicProps = dynamicContent.reduce<Record<string, any>>(
(acc, item) => {
return { ...acc, [item.props.id]: item };
},
{}
);

const processed = applyDynamicProps(data, newDynamicProps);
const processed = applyDynamicProps(data, newDynamicProps, dynamicRoot);

const containsChanges =
JSON.stringify(data) !== JSON.stringify(processed);

if (containsChanges) {
dispatch({
type: "setData",
data: (prev) => applyDynamicProps(prev, newDynamicProps),
data: (prev) => applyDynamicProps(prev, newDynamicProps, dynamicRoot),
recordHistory: true,
});
}
Expand Down
6 changes: 5 additions & 1 deletion packages/core/reducer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ const storeInterceptor = (reducer: StateReducer) => {
};
};

export const createReducer = ({ config }: { config: Config }): StateReducer =>
export const createReducer = ({
config,
}: {
config: Config<any>;
}): StateReducer =>
storeInterceptor((state, action) => {
const data = reduceData(state.data, action, config);
const ui = reduceUi(state.ui, action);
Expand Down
Loading

0 comments on commit 7593584

Please sign in to comment.