Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add viewport switching
Browse files Browse the repository at this point in the history
chrisvxd committed Mar 3, 2024
1 parent 4ebf0af commit 9b03328
Showing 13 changed files with 478 additions and 49 deletions.
7 changes: 6 additions & 1 deletion packages/core/components/DraggableComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import getClassNameFactory from "../../lib/get-class-name-factory";
import { Copy, Trash } from "lucide-react";
import { useModifierHeld } from "../../lib/use-modifier-held";
import { ClipLoader } from "react-spinners";
import { useAppContext } from "../Puck/context";

const getClassName = getClassNameFactory("DraggableComponent", styles);

@@ -51,6 +52,7 @@ export const DraggableComponent = ({
indicativeHover?: boolean;
style?: CSSProperties;
}) => {
const { state } = useAppContext();
const isModifierHeld = useModifierHeld("Alt");

useEffect(onMount, []);
@@ -92,7 +94,10 @@ export const DraggableComponent = ({
<ClipLoader aria-label="loading" size={16} color="inherit" />
</div>
)}
<div className={getClassName("overlay")}>
<div
className={getClassName("overlay")}
style={{ zoom: 1 / state.ui.viewport.zoom }}
>
<div className={getClassName("actions")}>
{label && (
<div className={getClassName("actionsLabel")}>{label}</div>
1 change: 0 additions & 1 deletion packages/core/components/DropZone/context.tsx
Original file line number Diff line number Diff line change
@@ -41,7 +41,6 @@ export type DropZoneContext<
pathData?: PathData;
registerPath?: (selector: ItemSelector) => void;
mode?: "edit" | "render";
disableZoom?: boolean;
} | null;

export const dropZoneContext = createContext<DropZoneContext>(null);
2 changes: 0 additions & 2 deletions packages/core/components/DropZone/index.tsx
Original file line number Diff line number Diff line change
@@ -38,7 +38,6 @@ function DropZoneEdit({ zone, allow, disallow, style }: DropZoneProps) {
registerZoneArea,
areasWithZones,
hoveringComponent,
disableZoom = false,
} = ctx! || {};

let content = data.content || [];
@@ -172,7 +171,6 @@ function DropZoneEdit({ zone, allow, disallow, style }: DropZoneProps) {
isDisabled: !isEnabled,
isAreaSelected,
hasChildren: content.length > 0,
zoomEnabled: !disableZoom,
})}
>
<Droppable
8 changes: 0 additions & 8 deletions packages/core/components/DropZone/styles.module.css
Original file line number Diff line number Diff line change
@@ -7,14 +7,6 @@
width: 100%;
}

.DropZone--zoomEnabled {
zoom: 1.33;
}

.DropZone--zoomEnabled .DropZone-renderWrapper {
zoom: 0.75;
}

.DropZone-content {
min-height: 128px;
height: 100%;
144 changes: 144 additions & 0 deletions packages/core/components/Puck/components/Canvas/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { getBox } from "css-box-model";
import {
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useAppContext } from "../../context";
import { ViewportControls } from "../../../ViewportControls";
import styles from "../../styles.module.css";
import { getClassNameFactory } from "../../../../lib";
import { Preview } from "../Preview";
import { AppState } from "../../../../types/Config";
import { getZoomConfig } from "../../../../lib/get-zoom-config";

const getClassName = getClassNameFactory("Puck", styles);

const ZOOM_ON_CHANGE = true;

export const Canvas = () => {
const { dispatch, state, overrides, setUi } = useAppContext();
const { ui } = state;
const frameRef = useRef<HTMLDivElement>(null);

const [rootHeight, setRootHeight] = useState(0);
const [autoZoom, setAutoZoom] = useState(0);
const [showTransition, setShowTransition] = useState(false);

const defaultRender = useMemo<
React.FunctionComponent<{ children?: ReactNode }>
>(() => {
const PuckDefault = ({ children }: { children?: ReactNode }) => (
<>{children}</>
);

return PuckDefault;
}, []);

const CustomPreview = useMemo(
() => overrides.preview || defaultRender,
[overrides]
);

const getFrameDimensions = useCallback(() => {
if (frameRef.current) {
const frame = frameRef.current;

const box = getBox(frame);

return { width: box.contentBox.width, height: box.contentBox.height };
}

return { width: 0, height: 0 };
}, [frameRef]);

// TODO deal with window resize
const resetAutoZoom = useCallback(
(uiViewport: AppState["ui"]["viewport"]) => {
if (frameRef.current) {
const zoomConfig = getZoomConfig(uiViewport, frameRef.current);

setRootHeight(zoomConfig.rootHeight);
setAutoZoom(zoomConfig.autoZoom);

if (zoomConfig.zoom) {
dispatch({
type: "setUi",
ui: {
...ui,
viewport: {
...uiViewport,
zoom: zoomConfig.zoom,
},
},
});
}
}
},
[frameRef, ui.leftSideBarVisible, ui, autoZoom]
);

// Auto zoom
useEffect(() => {
resetAutoZoom(ui.viewport);
}, [frameRef, ui.leftSideBarVisible, ui.rightSideBarVisible]);

// Constrain height
useEffect(() => {
const { height: frameHeight } = getFrameDimensions();

if (ui.viewport.height === "auto") {
setRootHeight(frameHeight / ui.viewport.zoom);
}
}, [ui.viewport.zoom]);

return (
<div
className={getClassName("canvas")}
onClick={() =>
dispatch({
type: "setUi",
ui: { itemSelector: null },
recordHistory: true,
})
}
>
<div className={getClassName("canvasControls")}>
<ViewportControls
autoZoom={autoZoom}
onViewportChange={(viewport) => {
setShowTransition(true);

const uiViewport = { ...viewport, zoom: ui.viewport.zoom };

setUi({ viewport: uiViewport });

if (ZOOM_ON_CHANGE) {
resetAutoZoom(uiViewport);
}
}}
/>
</div>
<div className={getClassName("frame")} ref={frameRef} id="puck-frame">
<div
className={getClassName("root")}
style={{
width: ui.viewport.width,
height: rootHeight,
transform: `scale(${ui.viewport.zoom})`,
transition: showTransition
? "width 150ms ease-out, transform 150ms ease-out"
: "",
}}
>
<CustomPreview>
<Preview />
</CustomPreview>
</div>
</div>
</div>
);
};
14 changes: 5 additions & 9 deletions packages/core/components/Puck/components/Preview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DropZone, dropZoneContext } from "../../../DropZone";
import { DropZone } from "../../../DropZone";
import { rootDroppableId } from "../../../../lib/root-droppable-id";
import { useCallback, useContext, useRef } from "react";
import { useCallback, useRef } from "react";
import { useAppContext } from "../../context";
import AutoFrame from "@measured/auto-frame-component";
import styles from "./styles.module.css";
@@ -22,8 +22,6 @@ export const Preview = ({ id = "puck-preview" }: { id?: string }) => {
// DEPRECATED
const rootProps = state.data.root.props || state.data.root;

const { disableZoom = false } = useContext(dropZoneContext) || {};

const ref = useRef<HTMLIFrameElement>(null);

return (
@@ -40,11 +38,9 @@ export const Preview = ({ id = "puck-preview" }: { id?: string }) => {
data-rfd-iframe
ref={ref}
>
<div style={{ zoom: disableZoom ? 1 : 0.75 }}>
<Page dispatch={dispatch} state={state} {...rootProps}>
<DropZone zone={rootDroppableId} />
</Page>
</div>
<Page dispatch={dispatch} state={state} {...rootProps}>
<DropZone zone={rootDroppableId} />
</Page>
</AutoFrame>
</div>
);
6 changes: 6 additions & 0 deletions packages/core/components/Puck/context.tsx
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import { getItem } from "../../lib/get-item";
import { Plugin } from "../../types/Plugin";
import { Overrides } from "../../types/Overrides";
import { PuckHistory } from "../../lib/use-puck-history";
import { viewports } from "../ViewportControls";

export const defaultAppState: AppState = {
data: { content: [], root: { props: { title: "" } } },
@@ -15,6 +16,11 @@ export const defaultAppState: AppState = {
itemSelector: null,
componentList: {},
isDragging: false,
viewport: {
width: viewports.large.width,
height: viewports.large.height,
zoom: 1,
},
},
};

44 changes: 23 additions & 21 deletions packages/core/components/Puck/index.tsx
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import {
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from "react";
import { DragDropContext, DragStart, DragUpdate } from "@measured/dnd";
@@ -42,6 +43,8 @@ import { Overrides } from "../../types/Overrides";
import { loadOverrides } from "../../lib/load-overrides";
import { usePuckHistory } from "../../lib/use-puck-history";
import { useHistoryStore } from "../../lib/use-history-store";
import { Canvas } from "./components/Canvas";
import { viewports } from "../ViewportControls";

const getClassName = getClassNameFactory("Puck", styles);

@@ -51,7 +54,7 @@ export function Puck<
children,
config,
data: initialData = { content: [], root: { props: { title: "" } } },
ui: initialUi = defaultAppState.ui,
ui: initialUi,
onChange,
onPublish,
plugins = [],
@@ -88,12 +91,24 @@ export function Puck<
);

const [initialAppState] = useState<AppState>(() => {
const initial = { ...defaultAppState.ui, ...initialUi };

const viewportWidth = window.innerWidth;

const viewportDifferences = Object.entries(viewports)
.map(([key, value]) => ({
key,
diff: Math.abs(viewportWidth - value.width),
}))
.sort((a, b) => (a.diff > b.diff ? 1 : -1));

const closestViewport = viewportDifferences[0].key;

return {
...defaultAppState,
data: initialData,
ui: {
...defaultAppState.ui,
...initialUi,
...initial,
// Store categories under componentList on state to allow render functions and plugins to modify
componentList: config.categories
? Object.entries(config.categories).reduce(
@@ -118,6 +133,10 @@ export function Puck<
leftSideBarVisible: false,
rightSideBarVisible: false,
}),
viewport: {
...initial.viewport,
width: initialUi?.viewport?.width || viewports[closestViewport].width,
},
},
};
});
@@ -289,10 +308,6 @@ export function Puck<
[loadedOverrides]
);

const CustomPreview = useMemo(
() => loadedOverrides.preview || defaultRender,
[loadedOverrides]
);
const CustomHeader = useMemo(
() => loadedOverrides.header || defaultHeaderRender,
[loadedOverrides]
@@ -302,8 +317,6 @@ export function Puck<
[loadedOverrides]
);

const disableZoom = children || loadedOverrides.puck ? true : false;

return (
<div className="Puck">
<AppProvider
@@ -395,7 +408,6 @@ export function Puck<
placeholderStyle,
mode: "edit",
areaId: "root",
disableZoom,
}}
>
<CustomPuck>
@@ -405,7 +417,6 @@ export function Puck<
leftSideBarVisible,
menuOpen,
rightSideBarVisible,
disableZoom,
})}
>
<CustomHeader
@@ -496,16 +507,7 @@ export function Puck<
<Outline />
</SidebarSection>
</div>
<div
className={getClassName("frame")}
onClick={() => setItemSelector(null)}
>
<div className={getClassName("root")}>
<CustomPreview>
<Preview />
</CustomPreview>
</div>
</div>
<Canvas />
<div className={getClassName("rightSideBar")}>
<SidebarSection
noPadding
Loading

0 comments on commit 9b03328

Please sign in to comment.