Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat add parent to resolve permissions #816

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ An object describing the [AppState](/docs/api-reference/app-state).
An object describing which props have changed on this component since the last time this function was called. This helps prevent duplicate calls when making async operations.

```tsx copy {2-4} /changed/1 filename="Example only updating the permissions when 'example' prop changes"
const resolveFields = async ({ props }, { changed, lastPermissions }) => {
const resolvePermissions = async ({ props }, { changed, lastPermissions }) => {
if (!changed.example) {
return lastPermissions; // Return the last permissions unless the `example` prop has changed
}
Expand Down
52 changes: 3 additions & 49 deletions packages/core/components/DragDropContext/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,8 @@ import { AutoScroller, defaultPreset, DragDropManager } from "@dnd-kit/dom";
import { DragDropEvents } from "@dnd-kit/abstract";
import { DropZoneProvider } from "../DropZone";
import type { Draggable, Droppable } from "@dnd-kit/dom";
import { getItem, ItemSelector } from "../../lib/get-item";
import {
PathData,
Preview,
ZoneStore,
ZoneStoreProvider,
} from "../DropZone/context";
import { getZoneId } from "../../lib/get-zone-id";
import { getItem } from "../../lib/get-item";
import { Preview, ZoneStore, ZoneStoreProvider } from "../DropZone/context";
import { createNestedDroppablePlugin } from "../../lib/dnd/NestedDroppablePlugin";
import { insertComponent } from "../../lib/insert-component";
import { useDebouncedCallback } from "use-debounce";
Expand Down Expand Up @@ -109,7 +103,7 @@ const DragDropContextClient = ({
children,
disableAutoScroll,
}: DragDropContextProps) => {
const { state, config, dispatch, resolveData } = useAppContext();
const { state, config, dispatch, resolveData, pathData } = useAppContext();

const id = useId();

Expand Down Expand Up @@ -300,45 +294,8 @@ const DragDropContextClient = ({

const [dragListeners, setDragListeners] = useState<DragCbs>({});

const [pathData, setPathData] = useState<PathData>();

const dragMode = useRef<"new" | "existing" | null>(null);

const registerPath = useCallback(
(id: string, selector: ItemSelector, label: string) => {
const [area] = getZoneId(selector.zone);

setPathData((latestPathData = {}) => {
const parentPathData = latestPathData[area] || { path: [] };

return {
...latestPathData,
[id]: {
path: [
...parentPathData.path,
...(selector.zone ? [selector.zone] : []),
],
label: label,
},
};
});
},
[data, setPathData]
);

const unregisterPath = useCallback(
(id: string) => {
setPathData((latestPathData = {}) => {
const newPathData = { ...latestPathData };

delete newPathData[id];

return newPathData;
});
},
[data, setPathData]
);

const initialSelector = useRef<{ zone: string; index: number }>(undefined);

return (
Expand Down Expand Up @@ -590,9 +547,6 @@ const DragDropContextClient = ({
mode: "edit",
areaId: "root",
depth: 0,
registerPath,
unregisterPath,
pathData,
path: [],
}}
>
Expand Down
6 changes: 4 additions & 2 deletions packages/core/components/DraggableComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ export const DraggableComponent = ({
dispatch,
iframe,
state,
registerPath,
unregisterPath,
} = useAppContext();

const ctx = useContext(dropZoneContext);
Expand Down Expand Up @@ -298,7 +300,7 @@ export const DraggableComponent = ({
}, [ref.current]);

useEffect(() => {
ctx?.registerPath!(
registerPath!(
id,
{
index,
Expand All @@ -308,7 +310,7 @@ export const DraggableComponent = ({
);

return () => {
ctx?.unregisterPath?.(id);
unregisterPath?.(id);
};
}, [id, zoneCompound, index, componentType]);

Expand Down
3 changes: 0 additions & 3 deletions packages/core/components/DropZone/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ export type DropZoneContext<UserConfig extends Config = Config> = {
registerZone?: (zoneCompound: string) => void;
unregisterZone?: (zoneCompound: string) => void;
activeZones?: Record<string, boolean>;
pathData?: PathData;
registerPath?: (id: string, selector: ItemSelector, label: string) => void;
unregisterPath?: (id: string) => void;
mode?: "edit" | "render";
depth: number;
registerLocalZone?: (zone: string, active: boolean) => void; // A zone as it pertains to the current area
Expand Down
4 changes: 3 additions & 1 deletion packages/core/components/LayerTree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getZoneId } from "../../lib/get-zone-id";
import { isChildOfZone } from "../../lib/is-child-of-zone";
import { getFrame } from "../../lib/get-frame";
import { onScrollEnd } from "../../lib/on-scroll-end";
import { useAppContext } from "../Puck/context";

const getClassName = getClassNameFactory("LayerTree", styles);
const getClassNameLayer = getClassNameFactory("Layer", styles);
Expand All @@ -35,6 +36,7 @@ export const LayerTree = ({
}) => {
const zones = data.zones || {};
const ctx = useContext(dropZoneContext);
const appContext = useAppContext();

return (
<>
Expand Down Expand Up @@ -67,7 +69,7 @@ export const LayerTree = ({

const isHovering = hoveringComponent === item.props.id;

const childIsSelected = isChildOfZone(item, selectedItem, ctx);
const childIsSelected = isChildOfZone(item, selectedItem, appContext);

const componentConfig: ComponentConfig | undefined =
config.components[item.type];
Expand Down
17 changes: 15 additions & 2 deletions packages/core/components/Puck/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
UserGenerics,
} from "../../types";
import { PuckAction } from "../../reducer";
import { getItem } from "../../lib/get-item";
import { getItem, ItemSelector } from "../../lib/get-item";
import { PuckHistory } from "../../lib/use-puck-history";
import { defaultViewports } from "../ViewportControls/default-viewports";
import { Viewports } from "../../types";
Expand All @@ -27,6 +27,8 @@ import {
useResolvedPermissions,
} from "../../lib/use-resolved-permissions";
import { useResolvedData } from "../../lib/use-resolved-data";
import { usePathData } from "../../lib/use-path-data";
import { PathData } from "../DropZone/context";

export const defaultAppState: AppState = {
data: { content: [], root: {} },
Expand Down Expand Up @@ -83,6 +85,9 @@ export type AppContext<
selectedItem?: G["UserData"]["content"][0];
getPermissions: GetPermissions<UserConfig>;
refreshPermissions: RefreshPermissions<UserConfig>;
pathData?: PathData;
registerPath?: (id: string, selector: ItemSelector, label: string) => void;
unregisterPath?: (id: string) => void;
};

export const defaultContext: AppContext = {
Expand Down Expand Up @@ -132,6 +137,10 @@ export const AppProvider = ({

const [status, setStatus] = useState<Status>("LOADING");

const { pathData, registerPath, unregisterPath } = usePathData(
value.state.data
);

// App is ready when client has loaded, after initial render
// This triggers DropZones to activate
useEffect(() => {
Expand Down Expand Up @@ -171,7 +180,8 @@ export const AppProvider = ({
value.state,
value.globalPermissions || {},
setComponentLoading,
unsetComponentLoading
unsetComponentLoading,
pathData
);

const { resolveData } = useResolvedData(
Expand All @@ -197,6 +207,9 @@ export const AppProvider = ({
componentState,
setComponentState,
resolveData,
pathData,
registerPath,
unregisterPath,
}}
>
{children}
Expand Down
18 changes: 10 additions & 8 deletions packages/core/lib/__tests__/use-breadcrumbs.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { DropZoneContext } from "../../components/DropZone/context";
import {
AppContext,
defaultAppState,
defaultContext,
} from "../../components/Puck/context";
import { Config, Data } from "../../types";
import { convertPathDataToBreadcrumbs } from "../use-breadcrumbs";

Expand All @@ -24,8 +28,9 @@ const config: Config = {
},
};

const dropzoneContext: DropZoneContext = {
data,
const appContext: AppContext = {
...defaultContext,
state: { ...defaultAppState, data },
config,
pathData: {
"MyComponent-1": { path: [], label: "MyComponent" },
Expand All @@ -35,16 +40,13 @@ const dropzoneContext: DropZoneContext = {
label: "MyComponent",
},
},
depth: 0,
path: [],
};

describe("use-breadcrumbs", () => {
describe("convert-path-data-to-breadcrumbs", () => {
it("should convert path data to breadcrumbs", () => {
expect(
convertPathDataToBreadcrumbs(item3, dropzoneContext.pathData, data)
).toMatchInlineSnapshot(`
expect(convertPathDataToBreadcrumbs(item3, appContext.pathData, data))
.toMatchInlineSnapshot(`
[
{
"label": "MyComponent",
Expand Down
7 changes: 4 additions & 3 deletions packages/core/lib/is-child-of-zone.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { DropZoneContext } from "../components/DropZone/context";
import { AppContext } from "../components/Puck/context";
import { Content } from "../types";
import { getItem } from "./get-item";
import { getZoneId } from "./get-zone-id";

export const isChildOfZone = (
item: Content[0],
maybeChild: Content[0] | null | undefined,
ctx: DropZoneContext
ctx: AppContext
) => {
const { data, pathData = {} } = ctx || {};
const { state, pathData = {} } = ctx || {};

return maybeChild && data
return maybeChild && state.data
? !!pathData[maybeChild.props.id]?.path.find((zoneCompound) => {
const [area] = getZoneId(zoneCompound);

Expand Down
6 changes: 3 additions & 3 deletions packages/core/lib/use-breadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ export const useBreadcrumbs = (renderCount?: number) => {
const {
state: { data },
selectedItem,
pathData,
} = useAppContext();
const dzContext = useContext(dropZoneContext);

return useMemo<Breadcrumb[]>(() => {
const breadcrumbs = convertPathDataToBreadcrumbs(
selectedItem,
dzContext?.pathData,
pathData,
data
);

Expand All @@ -96,5 +96,5 @@ export const useBreadcrumbs = (renderCount?: number) => {
}

return breadcrumbs;
}, [selectedItem, dzContext?.pathData, renderCount]);
}, [selectedItem, pathData, renderCount]);
};
46 changes: 33 additions & 13 deletions packages/core/lib/use-parent.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { useCallback, useContext } from "react";
import { useCallback } from "react";
import { useAppContext } from "../components/Puck/context";
import { getItem, ItemSelector } from "./get-item";
import { dropZoneContext } from "../components/DropZone";
import { convertPathDataToBreadcrumbs } from "./use-breadcrumbs";
import { PathData } from "../components/DropZone/context";
import { Data } from "../types";
import { ComponentData, Data } from "../types";

export const getParent = (
itemSelector: ItemSelector | null,
export const getParentByItem = (
item: ComponentData | undefined,
pathData: PathData | undefined,
data: Data
) => {
if (!itemSelector) return null;

const item = getItem(itemSelector, data);
const breadcrumbs = convertPathDataToBreadcrumbs(item, pathData, data);

const lastItem = breadcrumbs[breadcrumbs.length - 1];
Expand All @@ -24,16 +20,40 @@ export const getParent = (
return parent || null;
};

export const getParent = (
itemSelector: ItemSelector | null,
pathData: PathData | undefined,
data: Data
) => {
if (!itemSelector) return null;

const item = getItem(itemSelector, data);

return getParentByItem(item, pathData, data);
};

export const useGetParent = () => {
const { state } = useAppContext();
const { pathData } = useContext(dropZoneContext) || {};
const { state, pathData } = useAppContext();

return useCallback(
(itemSelector: ItemSelector | null) =>
getParent(itemSelector, pathData, state.data),
[pathData, state.data]
);
};

export const useGetParentByItem = () => {
const { state, pathData } = useAppContext();

return useCallback(
() => getParent(state.ui.itemSelector, pathData, state.data),
[state.ui.itemSelector, pathData, state.data]
(item: ComponentData | undefined) =>
getParentByItem(item, pathData, state.data),
[pathData, state.data]
);
};

export const useParent = () => {
return useGetParent()();
const { state } = useAppContext();

return useGetParent()(state.ui.itemSelector);
};
Loading
Loading