chore: renames pages to views
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import type { CollisionDetection } from '@dnd-kit/core'
|
||||
|
||||
import { rectIntersection } from '@dnd-kit/core'
|
||||
|
||||
// If the toolbar exits the preview area, we need to reset its position
|
||||
// This will prevent the toolbar from getting stuck outside the preview area
|
||||
export const customCollisionDetection: CollisionDetection = ({
|
||||
collisionRect,
|
||||
droppableContainers,
|
||||
...args
|
||||
}) => {
|
||||
const droppableContainer = droppableContainers.find(({ id }) => id === 'live-preview-area')
|
||||
|
||||
const rectIntersectionCollisions = rectIntersection({
|
||||
...args,
|
||||
collisionRect,
|
||||
droppableContainers: [droppableContainer],
|
||||
})
|
||||
|
||||
// Collision detection algorithms return an array of collisions
|
||||
if (rectIntersectionCollisions.length === 0) {
|
||||
// The preview area is not intersecting, return early
|
||||
return rectIntersectionCollisions
|
||||
}
|
||||
|
||||
// Compute whether the draggable element is completely contained within the preview area
|
||||
const previewAreaRect = droppableContainer?.rect?.current
|
||||
|
||||
const isContained =
|
||||
collisionRect.top >= previewAreaRect.top &&
|
||||
collisionRect.left >= previewAreaRect.left &&
|
||||
collisionRect.bottom <= previewAreaRect.bottom &&
|
||||
collisionRect.right <= previewAreaRect.right
|
||||
|
||||
if (isContained) {
|
||||
return rectIntersectionCollisions
|
||||
}
|
||||
}
|
||||
84
packages/next/src/views/LivePreview/Context/context.ts
Normal file
84
packages/next/src/views/LivePreview/Context/context.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { LivePreviewConfig } from 'payload/config'
|
||||
import type { fieldSchemaToJSON } from 'payload/utilities'
|
||||
import type { Dispatch } from 'react'
|
||||
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
import type { usePopupWindow } from '../usePopupWindow'
|
||||
import type { SizeReducerAction } from './sizeReducer'
|
||||
|
||||
export interface LivePreviewContextType {
|
||||
appIsReady: boolean
|
||||
breakpoint: LivePreviewConfig['breakpoints'][number]['name']
|
||||
breakpoints: LivePreviewConfig['breakpoints']
|
||||
fieldSchemaJSON?: ReturnType<typeof fieldSchemaToJSON>
|
||||
iframeHasLoaded: boolean
|
||||
iframeRef: React.RefObject<HTMLIFrameElement>
|
||||
isPopupOpen: boolean
|
||||
measuredDeviceSize: {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
openPopupWindow: ReturnType<typeof usePopupWindow>['openPopupWindow']
|
||||
popupRef?: React.MutableRefObject<Window | null>
|
||||
previewWindowType: 'iframe' | 'popup'
|
||||
setAppIsReady: (appIsReady: boolean) => void
|
||||
setBreakpoint: (breakpoint: LivePreviewConfig['breakpoints'][number]['name']) => void
|
||||
setHeight: (height: number) => void
|
||||
setIframeHasLoaded: (loaded: boolean) => void
|
||||
setMeasuredDeviceSize: (size: { height: number; width: number }) => void
|
||||
setPreviewWindowType: (previewWindowType: 'iframe' | 'popup') => void
|
||||
setSize: Dispatch<SizeReducerAction>
|
||||
setToolbarPosition: (position: { x: number; y: number }) => void
|
||||
setWidth: (width: number) => void
|
||||
setZoom: (zoom: number) => void
|
||||
size: {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
toolbarPosition: {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
url: string | undefined
|
||||
zoom: number
|
||||
}
|
||||
|
||||
export const LivePreviewContext = createContext<LivePreviewContextType>({
|
||||
appIsReady: false,
|
||||
breakpoint: undefined,
|
||||
breakpoints: undefined,
|
||||
fieldSchemaJSON: undefined,
|
||||
iframeHasLoaded: false,
|
||||
iframeRef: undefined,
|
||||
isPopupOpen: false,
|
||||
measuredDeviceSize: {
|
||||
height: 0,
|
||||
width: 0,
|
||||
},
|
||||
openPopupWindow: () => {},
|
||||
popupRef: undefined,
|
||||
previewWindowType: 'iframe',
|
||||
setAppIsReady: () => {},
|
||||
setBreakpoint: () => {},
|
||||
setHeight: () => {},
|
||||
setIframeHasLoaded: () => {},
|
||||
setMeasuredDeviceSize: () => {},
|
||||
setPreviewWindowType: () => {},
|
||||
setSize: () => {},
|
||||
setToolbarPosition: () => {},
|
||||
setWidth: () => {},
|
||||
setZoom: () => {},
|
||||
size: {
|
||||
height: 0,
|
||||
width: 0,
|
||||
},
|
||||
toolbarPosition: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
url: undefined,
|
||||
zoom: 1,
|
||||
})
|
||||
|
||||
export const useLivePreviewContext = () => useContext(LivePreviewContext)
|
||||
198
packages/next/src/views/LivePreview/Context/index.tsx
Normal file
198
packages/next/src/views/LivePreview/Context/index.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
'use client'
|
||||
import type { EditViewProps, LivePreviewConfig } from 'payload/config'
|
||||
|
||||
import { DndContext } from '@dnd-kit/core'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import type { usePopupWindow } from '../usePopupWindow'
|
||||
|
||||
import { customCollisionDetection } from './collisionDetection'
|
||||
import { LivePreviewContext } from './context'
|
||||
import { sizeReducer } from './sizeReducer'
|
||||
|
||||
export type LivePreviewProviderProps = EditViewProps & {
|
||||
appIsReady?: boolean
|
||||
breakpoints?: LivePreviewConfig['breakpoints']
|
||||
children: React.ReactNode
|
||||
deviceSize?: {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
isPopupOpen?: boolean
|
||||
openPopupWindow?: ReturnType<typeof usePopupWindow>['openPopupWindow']
|
||||
popupRef?: React.MutableRefObject<Window>
|
||||
url?: string
|
||||
}
|
||||
|
||||
export const LivePreviewProvider: React.FC<LivePreviewProviderProps> = (props) => {
|
||||
const { breakpoints, children, isPopupOpen, openPopupWindow, popupRef, url } = props
|
||||
|
||||
const [previewWindowType, setPreviewWindowType] = useState<'iframe' | 'popup'>('iframe')
|
||||
|
||||
const [appIsReady, setAppIsReady] = useState(false)
|
||||
|
||||
const iframeRef = React.useRef<HTMLIFrameElement>(null)
|
||||
|
||||
const [iframeHasLoaded, setIframeHasLoaded] = useState(false)
|
||||
|
||||
const [zoom, setZoom] = useState(1)
|
||||
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 })
|
||||
|
||||
const [size, setSize] = React.useReducer(sizeReducer, { height: 0, width: 0 })
|
||||
|
||||
const [measuredDeviceSize, setMeasuredDeviceSize] = useState({
|
||||
height: 0,
|
||||
width: 0,
|
||||
})
|
||||
|
||||
const [breakpoint, setBreakpoint] =
|
||||
React.useState<LivePreviewConfig['breakpoints'][0]['name']>('responsive')
|
||||
|
||||
// const [fieldSchemaJSON] = useState(() => {
|
||||
// let fields: Field[]
|
||||
|
||||
// if ('collection' in props) {
|
||||
// const { collection } = props
|
||||
// fields = collection.fields
|
||||
// }
|
||||
|
||||
// if ('global' in props) {
|
||||
// const { global } = props
|
||||
// fields = global.fields
|
||||
// }
|
||||
|
||||
// return fieldSchemaToJSON(fields)
|
||||
// })
|
||||
|
||||
// The toolbar needs to freely drag and drop around the page
|
||||
const handleDragEnd = (ev) => {
|
||||
// only update position if the toolbar is completely within the preview area
|
||||
// otherwise reset it back to the previous position
|
||||
// TODO: reset to the nearest edge of the preview area
|
||||
if (ev.over && ev.over.id === 'live-preview-area') {
|
||||
const newPos = {
|
||||
x: position.x + ev.delta.x,
|
||||
y: position.y + ev.delta.y,
|
||||
}
|
||||
|
||||
setPosition(newPos)
|
||||
} else {
|
||||
// reset
|
||||
}
|
||||
}
|
||||
|
||||
const setWidth = useCallback(
|
||||
(width) => {
|
||||
setSize({ type: 'width', value: width })
|
||||
},
|
||||
[setSize],
|
||||
)
|
||||
|
||||
const setHeight = useCallback(
|
||||
(height) => {
|
||||
setSize({ type: 'height', value: height })
|
||||
},
|
||||
[setSize],
|
||||
)
|
||||
|
||||
// explicitly set new width and height when as new breakpoints are selected
|
||||
// exclude `custom` breakpoint as it is handled by the `setWidth` and `setHeight` directly
|
||||
useEffect(() => {
|
||||
const foundBreakpoint = breakpoints?.find((bp) => bp.name === breakpoint)
|
||||
|
||||
if (
|
||||
foundBreakpoint &&
|
||||
breakpoint !== 'responsive' &&
|
||||
breakpoint !== 'custom' &&
|
||||
typeof foundBreakpoint?.width === 'number' &&
|
||||
typeof foundBreakpoint?.height === 'number'
|
||||
) {
|
||||
setSize({
|
||||
type: 'reset',
|
||||
value: {
|
||||
height: foundBreakpoint.height,
|
||||
width: foundBreakpoint.width,
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [breakpoint, breakpoints])
|
||||
|
||||
// Receive the `ready` message from the popup window
|
||||
// This indicates that the app is ready to receive `window.postMessage` events
|
||||
// This is also the only cross-origin way of detecting when a popup window has loaded
|
||||
// Unlike iframe elements which have an `onLoad` handler, there is no way to access `window.open` on popups
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (
|
||||
url?.startsWith(event.origin) &&
|
||||
event.data &&
|
||||
typeof event.data === 'object' &&
|
||||
event.data.type === 'payload-live-preview'
|
||||
) {
|
||||
if (event.data.ready) {
|
||||
setAppIsReady(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessage)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
}, [url])
|
||||
|
||||
const handleWindowChange = useCallback(
|
||||
(type: 'iframe' | 'popup') => {
|
||||
setAppIsReady(false)
|
||||
setPreviewWindowType(type)
|
||||
if (type === 'popup') openPopupWindow()
|
||||
},
|
||||
[openPopupWindow],
|
||||
)
|
||||
|
||||
// when the user closes the popup window, switch back to the iframe
|
||||
// the `usePopupWindow` reports the `isPopupOpen` state for us to use here
|
||||
useEffect(() => {
|
||||
if (!isPopupOpen) {
|
||||
handleWindowChange('iframe')
|
||||
}
|
||||
}, [isPopupOpen, handleWindowChange])
|
||||
|
||||
return (
|
||||
<LivePreviewContext.Provider
|
||||
value={{
|
||||
appIsReady,
|
||||
breakpoint,
|
||||
breakpoints,
|
||||
fieldSchemaJSON: [],
|
||||
iframeHasLoaded,
|
||||
iframeRef,
|
||||
isPopupOpen,
|
||||
measuredDeviceSize,
|
||||
openPopupWindow,
|
||||
popupRef,
|
||||
previewWindowType,
|
||||
setAppIsReady,
|
||||
setBreakpoint,
|
||||
setHeight,
|
||||
setIframeHasLoaded,
|
||||
setMeasuredDeviceSize,
|
||||
setPreviewWindowType: handleWindowChange,
|
||||
setSize,
|
||||
setToolbarPosition: setPosition,
|
||||
setWidth,
|
||||
setZoom,
|
||||
size,
|
||||
toolbarPosition: position,
|
||||
url,
|
||||
zoom,
|
||||
}}
|
||||
>
|
||||
<DndContext collisionDetection={customCollisionDetection} onDragEnd={handleDragEnd}>
|
||||
{children}
|
||||
</DndContext>
|
||||
</LivePreviewContext.Provider>
|
||||
)
|
||||
}
|
||||
39
packages/next/src/views/LivePreview/Context/sizeReducer.ts
Normal file
39
packages/next/src/views/LivePreview/Context/sizeReducer.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// export const sizeReducer: (state, action) => {
|
||||
// switch (action.type) {
|
||||
// case 'width':
|
||||
// return { ...state, width: action.value }
|
||||
// case 'height':
|
||||
// return { ...state, height: action.value }
|
||||
// default:
|
||||
// return { ...state, ...(action?.value || {}) }
|
||||
// }
|
||||
// },
|
||||
|
||||
type SizeReducerState = {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
|
||||
export type SizeReducerAction =
|
||||
| {
|
||||
type: 'height' | 'width'
|
||||
value: number
|
||||
}
|
||||
| {
|
||||
type: 'reset'
|
||||
value: {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
}
|
||||
|
||||
export const sizeReducer = (state: SizeReducerState, action: SizeReducerAction) => {
|
||||
switch (action.type) {
|
||||
case 'width':
|
||||
return { ...state, width: action.value }
|
||||
case 'height':
|
||||
return { ...state, height: action.value }
|
||||
default:
|
||||
return { ...state, ...(action?.value || {}) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user