chore: optimizes live preview (#3713)
This commit is contained in:
@@ -3,21 +3,31 @@ import type { Dispatch } from 'react'
|
|||||||
import { createContext, useContext } from 'react'
|
import { createContext, useContext } from 'react'
|
||||||
|
|
||||||
import type { LivePreviewConfig } from '../../../../../exports/config'
|
import type { LivePreviewConfig } from '../../../../../exports/config'
|
||||||
|
import type { fieldSchemaToJSON } from '../../../../../utilities/fieldSchemaToJSON'
|
||||||
|
import type { usePopupWindow } from '../usePopupWindow'
|
||||||
import type { SizeReducerAction } from './sizeReducer'
|
import type { SizeReducerAction } from './sizeReducer'
|
||||||
|
|
||||||
export interface LivePreviewContextType {
|
export interface LivePreviewContextType {
|
||||||
|
appIsReady: boolean
|
||||||
breakpoint: LivePreviewConfig['breakpoints'][number]['name']
|
breakpoint: LivePreviewConfig['breakpoints'][number]['name']
|
||||||
breakpoints: LivePreviewConfig['breakpoints']
|
breakpoints: LivePreviewConfig['breakpoints']
|
||||||
|
fieldSchemaJSON?: ReturnType<typeof fieldSchemaToJSON>
|
||||||
iframeHasLoaded: boolean
|
iframeHasLoaded: boolean
|
||||||
iframeRef: React.RefObject<HTMLIFrameElement>
|
iframeRef: React.RefObject<HTMLIFrameElement>
|
||||||
|
isPopupOpen: boolean
|
||||||
measuredDeviceSize: {
|
measuredDeviceSize: {
|
||||||
height: number
|
height: number
|
||||||
width: 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
|
setBreakpoint: (breakpoint: LivePreviewConfig['breakpoints'][number]['name']) => void
|
||||||
setHeight: (height: number) => void
|
setHeight: (height: number) => void
|
||||||
setIframeHasLoaded: (loaded: boolean) => void
|
setIframeHasLoaded: (loaded: boolean) => void
|
||||||
setMeasuredDeviceSize: (size: { height: number; width: number }) => void
|
setMeasuredDeviceSize: (size: { height: number; width: number }) => void
|
||||||
|
setPreviewWindowType: (previewWindowType: 'iframe' | 'popup') => void
|
||||||
setSize: Dispatch<SizeReducerAction>
|
setSize: Dispatch<SizeReducerAction>
|
||||||
setToolbarPosition: (position: { x: number; y: number }) => void
|
setToolbarPosition: (position: { x: number; y: number }) => void
|
||||||
setWidth: (width: number) => void
|
setWidth: (width: number) => void
|
||||||
@@ -30,22 +40,31 @@ export interface LivePreviewContextType {
|
|||||||
x: number
|
x: number
|
||||||
y: number
|
y: number
|
||||||
}
|
}
|
||||||
|
url: string | undefined
|
||||||
zoom: number
|
zoom: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LivePreviewContext = createContext<LivePreviewContextType>({
|
export const LivePreviewContext = createContext<LivePreviewContextType>({
|
||||||
|
appIsReady: false,
|
||||||
breakpoint: undefined,
|
breakpoint: undefined,
|
||||||
breakpoints: undefined,
|
breakpoints: undefined,
|
||||||
|
fieldSchemaJSON: undefined,
|
||||||
iframeHasLoaded: false,
|
iframeHasLoaded: false,
|
||||||
iframeRef: undefined,
|
iframeRef: undefined,
|
||||||
|
isPopupOpen: false,
|
||||||
measuredDeviceSize: {
|
measuredDeviceSize: {
|
||||||
height: 0,
|
height: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
},
|
},
|
||||||
|
openPopupWindow: () => {},
|
||||||
|
popupRef: undefined,
|
||||||
|
previewWindowType: 'iframe',
|
||||||
|
setAppIsReady: () => {},
|
||||||
setBreakpoint: () => {},
|
setBreakpoint: () => {},
|
||||||
setHeight: () => {},
|
setHeight: () => {},
|
||||||
setIframeHasLoaded: () => {},
|
setIframeHasLoaded: () => {},
|
||||||
setMeasuredDeviceSize: () => {},
|
setMeasuredDeviceSize: () => {},
|
||||||
|
setPreviewWindowType: () => {},
|
||||||
setSize: () => {},
|
setSize: () => {},
|
||||||
setToolbarPosition: () => {},
|
setToolbarPosition: () => {},
|
||||||
setWidth: () => {},
|
setWidth: () => {},
|
||||||
@@ -58,6 +77,7 @@ export const LivePreviewContext = createContext<LivePreviewContextType>({
|
|||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
|
url: undefined,
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,48 @@
|
|||||||
import { DndContext } from '@dnd-kit/core'
|
import { DndContext } from '@dnd-kit/core'
|
||||||
import React, { useCallback, useEffect } from 'react'
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import type { LivePreviewConfig } from '../../../../../exports/config'
|
import type { LivePreviewConfig } from '../../../../../exports/config'
|
||||||
|
import type { Field } from '../../../../../fields/config/types'
|
||||||
import type { EditViewProps } from '../../types'
|
import type { EditViewProps } from '../../types'
|
||||||
import type { usePopupWindow } from '../usePopupWindow'
|
import type { usePopupWindow } from '../usePopupWindow'
|
||||||
|
|
||||||
|
import { fieldSchemaToJSON } from '../../../../../utilities/fieldSchemaToJSON'
|
||||||
import { customCollisionDetection } from './collisionDetection'
|
import { customCollisionDetection } from './collisionDetection'
|
||||||
import { LivePreviewContext } from './context'
|
import { LivePreviewContext } from './context'
|
||||||
import { sizeReducer } from './sizeReducer'
|
import { sizeReducer } from './sizeReducer'
|
||||||
|
|
||||||
export type ToolbarProviderProps = EditViewProps & {
|
export type LivePreviewProviderProps = EditViewProps & {
|
||||||
|
appIsReady?: boolean
|
||||||
breakpoints?: LivePreviewConfig['breakpoints']
|
breakpoints?: LivePreviewConfig['breakpoints']
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
deviceSize?: {
|
deviceSize?: {
|
||||||
height: number
|
height: number
|
||||||
width: number
|
width: number
|
||||||
}
|
}
|
||||||
popupState: ReturnType<typeof usePopupWindow>
|
isPopupOpen?: boolean
|
||||||
|
openPopupWindow?: ReturnType<typeof usePopupWindow>['openPopupWindow']
|
||||||
|
popupRef?: React.MutableRefObject<Window>
|
||||||
url?: string
|
url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
|
export const LivePreviewProvider: React.FC<LivePreviewProviderProps> = (props) => {
|
||||||
const { breakpoints, children } = 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 iframeRef = React.useRef<HTMLIFrameElement>(null)
|
||||||
|
|
||||||
const [iframeHasLoaded, setIframeHasLoaded] = React.useState(false)
|
const [iframeHasLoaded, setIframeHasLoaded] = useState(false)
|
||||||
|
|
||||||
const [zoom, setZoom] = React.useState(1)
|
const [zoom, setZoom] = useState(1)
|
||||||
|
|
||||||
const [position, setPosition] = React.useState({ x: 0, y: 0 })
|
const [position, setPosition] = useState({ x: 0, y: 0 })
|
||||||
|
|
||||||
const [size, setSize] = React.useReducer(sizeReducer, { height: 0, width: 0 })
|
const [size, setSize] = React.useReducer(sizeReducer, { height: 0, width: 0 })
|
||||||
|
|
||||||
const [measuredDeviceSize, setMeasuredDeviceSize] = React.useState({
|
const [measuredDeviceSize, setMeasuredDeviceSize] = useState({
|
||||||
height: 0,
|
height: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
})
|
})
|
||||||
@@ -41,6 +50,22 @@ export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
|
|||||||
const [breakpoint, setBreakpoint] =
|
const [breakpoint, setBreakpoint] =
|
||||||
React.useState<LivePreviewConfig['breakpoints'][0]['name']>('responsive')
|
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
|
// The toolbar needs to freely drag and drop around the page
|
||||||
const handleDragEnd = (ev) => {
|
const handleDragEnd = (ev) => {
|
||||||
// only update position if the toolbar is completely within the preview area
|
// only update position if the toolbar is completely within the preview area
|
||||||
@@ -94,24 +119,70 @@ export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}, [breakpoint, breakpoints])
|
}, [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) => {
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
|
||||||
|
if (url.startsWith(event.origin) && data.type === 'payload-live-preview' && 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 (
|
return (
|
||||||
<LivePreviewContext.Provider
|
<LivePreviewContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
appIsReady,
|
||||||
breakpoint,
|
breakpoint,
|
||||||
breakpoints,
|
breakpoints,
|
||||||
|
fieldSchemaJSON,
|
||||||
iframeHasLoaded,
|
iframeHasLoaded,
|
||||||
iframeRef,
|
iframeRef,
|
||||||
|
isPopupOpen,
|
||||||
measuredDeviceSize,
|
measuredDeviceSize,
|
||||||
|
openPopupWindow,
|
||||||
|
popupRef,
|
||||||
|
previewWindowType,
|
||||||
|
setAppIsReady,
|
||||||
setBreakpoint,
|
setBreakpoint,
|
||||||
setHeight,
|
setHeight,
|
||||||
setIframeHasLoaded,
|
setIframeHasLoaded,
|
||||||
setMeasuredDeviceSize,
|
setMeasuredDeviceSize,
|
||||||
|
setPreviewWindowType: handleWindowChange,
|
||||||
setSize,
|
setSize,
|
||||||
setToolbarPosition: setPosition,
|
setToolbarPosition: setPosition,
|
||||||
setWidth,
|
setWidth,
|
||||||
setZoom,
|
setZoom,
|
||||||
size,
|
size,
|
||||||
toolbarPosition: position,
|
toolbarPosition: position,
|
||||||
|
url,
|
||||||
zoom,
|
zoom,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
|
|
||||||
import type { LivePreviewConfig } from '../../../../../exports/config'
|
|
||||||
import type { Field } from '../../../../../fields/config/types'
|
|
||||||
import type { EditViewProps } from '../../types'
|
import type { EditViewProps } from '../../types'
|
||||||
import type { usePopupWindow } from '../usePopupWindow'
|
|
||||||
|
|
||||||
import { fieldSchemaToJSON } from '../../../../../utilities/fieldSchemaToJSON'
|
|
||||||
import { useAllFormFields } from '../../../forms/Form/context'
|
import { useAllFormFields } from '../../../forms/Form/context'
|
||||||
import reduceFieldsToValues from '../../../forms/Form/reduceFieldsToValues'
|
import reduceFieldsToValues from '../../../forms/Form/reduceFieldsToValues'
|
||||||
import { LivePreviewProvider } from '../Context'
|
|
||||||
import { useLivePreviewContext } from '../Context/context'
|
import { useLivePreviewContext } from '../Context/context'
|
||||||
import { DeviceContainer } from '../Device'
|
import { DeviceContainer } from '../Device'
|
||||||
import { IFrame } from '../IFrame'
|
import { IFrame } from '../IFrame'
|
||||||
@@ -17,93 +12,81 @@ import './index.scss'
|
|||||||
|
|
||||||
const baseClass = 'live-preview-window'
|
const baseClass = 'live-preview-window'
|
||||||
|
|
||||||
const Preview: React.FC<
|
export const LivePreview: React.FC<EditViewProps> = (props) => {
|
||||||
EditViewProps & {
|
|
||||||
popupState: ReturnType<typeof usePopupWindow>
|
|
||||||
url?: string
|
|
||||||
}
|
|
||||||
> = (props) => {
|
|
||||||
const {
|
const {
|
||||||
popupState: { isPopupOpen, popupHasLoaded, popupRef },
|
appIsReady,
|
||||||
|
iframeHasLoaded,
|
||||||
|
iframeRef,
|
||||||
|
popupRef,
|
||||||
|
previewWindowType,
|
||||||
|
setIframeHasLoaded,
|
||||||
url,
|
url,
|
||||||
} = props
|
} = useLivePreviewContext()
|
||||||
|
|
||||||
const { iframeHasLoaded, iframeRef, setIframeHasLoaded } = useLivePreviewContext()
|
const { breakpoint, fieldSchemaJSON } = useLivePreviewContext()
|
||||||
|
|
||||||
const { breakpoint } = useLivePreviewContext()
|
const prevWindowType =
|
||||||
|
React.useRef<ReturnType<typeof useLivePreviewContext>['previewWindowType']>()
|
||||||
|
|
||||||
const [fields] = useAllFormFields()
|
const [fields] = useAllFormFields()
|
||||||
|
|
||||||
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 preview could either be an iframe embedded on the page
|
// The preview could either be an iframe embedded on the page
|
||||||
// Or it could be a separate popup window
|
// Or it could be a separate popup window
|
||||||
// We need to transmit data to both accordingly
|
// We need to transmit data to both accordingly
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fields && window && 'postMessage' in window) {
|
// For performance, do no reduce fields to values until after the iframe or popup has loaded
|
||||||
|
if (fields && window && 'postMessage' in window && appIsReady) {
|
||||||
const values = reduceFieldsToValues(fields, true)
|
const values = reduceFieldsToValues(fields, true)
|
||||||
|
|
||||||
// TODO: only send `fieldSchemaToJSON` one time
|
// To reduce on large `postMessage` payloads, only send `fieldSchemaToJSON` one time
|
||||||
|
// To do this, the underlying JS function maintains a cache of this value
|
||||||
|
// So we need to send it through each time the window type changes
|
||||||
|
// But only once per window type change, not on every render, because this is a potentially large obj
|
||||||
|
const shouldSendSchema =
|
||||||
|
!prevWindowType.current || prevWindowType.current !== previewWindowType
|
||||||
|
|
||||||
|
prevWindowType.current = previewWindowType
|
||||||
|
|
||||||
const message = JSON.stringify({
|
const message = JSON.stringify({
|
||||||
data: values,
|
data: values,
|
||||||
fieldSchemaJSON,
|
fieldSchemaJSON: shouldSendSchema ? fieldSchemaJSON : undefined,
|
||||||
type: 'payload-live-preview',
|
type: 'payload-live-preview',
|
||||||
})
|
})
|
||||||
|
|
||||||
// external window
|
// Post message to external popup window
|
||||||
if (isPopupOpen) {
|
if (previewWindowType === 'popup' && popupRef.current) {
|
||||||
setIframeHasLoaded(false)
|
popupRef.current.postMessage(message, url)
|
||||||
if (popupRef.current) {
|
|
||||||
popupRef.current.postMessage(message, url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// embedded iframe
|
// Post message to embedded iframe
|
||||||
if (!isPopupOpen) {
|
if (previewWindowType === 'iframe' && iframeRef.current) {
|
||||||
if (iframeHasLoaded && iframeRef.current) {
|
iframeRef.current.contentWindow?.postMessage(message, url)
|
||||||
iframeRef.current.contentWindow?.postMessage(message, url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
fields,
|
fields,
|
||||||
url,
|
url,
|
||||||
iframeHasLoaded,
|
iframeHasLoaded,
|
||||||
isPopupOpen,
|
previewWindowType,
|
||||||
popupRef,
|
popupRef,
|
||||||
popupHasLoaded,
|
appIsReady,
|
||||||
iframeRef,
|
iframeRef,
|
||||||
setIframeHasLoaded,
|
setIframeHasLoaded,
|
||||||
fieldSchemaJSON,
|
fieldSchemaJSON,
|
||||||
])
|
])
|
||||||
|
|
||||||
if (!isPopupOpen) {
|
if (previewWindowType === 'iframe') {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
baseClass,
|
baseClass,
|
||||||
isPopupOpen && `${baseClass}--popup-open`,
|
|
||||||
breakpoint && breakpoint !== 'responsive' && `${baseClass}--has-breakpoint`,
|
breakpoint && breakpoint !== 'responsive' && `${baseClass}--has-breakpoint`,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ')}
|
.join(' ')}
|
||||||
>
|
>
|
||||||
<div className={`${baseClass}__wrapper`}>
|
<div className={`${baseClass}__wrapper`}>
|
||||||
<LivePreviewToolbar {...props} iframeRef={iframeRef} url={url} />
|
<LivePreviewToolbar {...props} />
|
||||||
<div className={`${baseClass}__main`}>
|
<div className={`${baseClass}__main`}>
|
||||||
<DeviceContainer>
|
<DeviceContainer>
|
||||||
<IFrame ref={iframeRef} setIframeHasLoaded={setIframeHasLoaded} url={url} />
|
<IFrame ref={iframeRef} setIframeHasLoaded={setIframeHasLoaded} url={url} />
|
||||||
@@ -114,29 +97,3 @@ const Preview: React.FC<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LivePreview: React.FC<
|
|
||||||
EditViewProps & {
|
|
||||||
livePreviewConfig?: LivePreviewConfig
|
|
||||||
popupState: ReturnType<typeof usePopupWindow>
|
|
||||||
url?: string
|
|
||||||
}
|
|
||||||
> = (props) => {
|
|
||||||
const { livePreviewConfig, url } = props
|
|
||||||
|
|
||||||
const breakpoints: LivePreviewConfig['breakpoints'] = [
|
|
||||||
...(livePreviewConfig?.breakpoints || []),
|
|
||||||
{
|
|
||||||
name: 'responsive',
|
|
||||||
height: '100%',
|
|
||||||
label: 'Responsive',
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LivePreviewProvider {...props} breakpoints={breakpoints} url={url}>
|
|
||||||
<Preview {...props} />
|
|
||||||
</LivePreviewProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import type { LivePreviewToolbarProps } from '..'
|
import type { EditViewProps } from '../../../types'
|
||||||
|
|
||||||
import { X } from '../../../..'
|
import { X } from '../../../..'
|
||||||
import { ExternalLinkIcon } from '../../../../graphics/ExternalLink'
|
import { ExternalLinkIcon } from '../../../../graphics/ExternalLink'
|
||||||
@@ -10,13 +10,9 @@ import './index.scss'
|
|||||||
|
|
||||||
const baseClass = 'live-preview-toolbar-controls'
|
const baseClass = 'live-preview-toolbar-controls'
|
||||||
|
|
||||||
export const ToolbarControls: React.FC<LivePreviewToolbarProps> = (props) => {
|
export const ToolbarControls: React.FC<EditViewProps> = () => {
|
||||||
const { breakpoint, breakpoints, setBreakpoint, setZoom, zoom } = useLivePreviewContext()
|
const { breakpoint, breakpoints, setBreakpoint, setPreviewWindowType, setZoom, url, zoom } =
|
||||||
|
useLivePreviewContext()
|
||||||
const {
|
|
||||||
popupState: { openPopupWindow },
|
|
||||||
url,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={baseClass}>
|
<div className={baseClass}>
|
||||||
@@ -57,7 +53,15 @@ export const ToolbarControls: React.FC<LivePreviewToolbarProps> = (props) => {
|
|||||||
<option value={150}>150%</option>
|
<option value={150}>150%</option>
|
||||||
<option value={200}>200%</option>
|
<option value={200}>200%</option>
|
||||||
</select>
|
</select>
|
||||||
<a className={`${baseClass}__external`} href={url} onClick={openPopupWindow} type="button">
|
<a
|
||||||
|
className={`${baseClass}__external`}
|
||||||
|
href={url}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setPreviewWindowType('popup')
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
<ExternalLinkIcon />
|
<ExternalLinkIcon />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useDraggable } from '@dnd-kit/core'
|
import { useDraggable } from '@dnd-kit/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import type { ToolbarProviderProps } from '../Context'
|
import type { EditViewProps } from '../../types'
|
||||||
|
|
||||||
import DragHandle from '../../../icons/Drag'
|
import DragHandle from '../../../icons/Drag'
|
||||||
import { useLivePreviewContext } from '../Context/context'
|
import { useLivePreviewContext } from '../Context/context'
|
||||||
@@ -10,11 +10,7 @@ import './index.scss'
|
|||||||
|
|
||||||
const baseClass = 'live-preview-toolbar'
|
const baseClass = 'live-preview-toolbar'
|
||||||
|
|
||||||
export type LivePreviewToolbarProps = Omit<ToolbarProviderProps, 'children'> & {
|
const DraggableToolbar: React.FC<EditViewProps> = (props) => {
|
||||||
iframeRef: React.RefObject<HTMLIFrameElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
const DraggableToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
|
|
||||||
const { toolbarPosition } = useLivePreviewContext()
|
const { toolbarPosition } = useLivePreviewContext()
|
||||||
|
|
||||||
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
||||||
@@ -50,7 +46,7 @@ const DraggableToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const StaticToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
|
const StaticToolbar: React.FC<EditViewProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className={[baseClass, `${baseClass}--static`].join(' ')}>
|
<div className={[baseClass, `${baseClass}--static`].join(' ')}>
|
||||||
<ToolbarControls {...props} />
|
<ToolbarControls {...props} />
|
||||||
@@ -59,7 +55,7 @@ const StaticToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LivePreviewToolbar: React.FC<
|
export const LivePreviewToolbar: React.FC<
|
||||||
LivePreviewToolbarProps & {
|
EditViewProps & {
|
||||||
draggable?: boolean
|
draggable?: boolean
|
||||||
}
|
}
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
|
|||||||
@@ -17,47 +17,17 @@ import { useDocumentInfo } from '../../utilities/DocumentInfo'
|
|||||||
import { useLocale } from '../../utilities/Locale'
|
import { useLocale } from '../../utilities/Locale'
|
||||||
import Meta from '../../utilities/Meta'
|
import Meta from '../../utilities/Meta'
|
||||||
import { SetStepNav } from '../collections/Edit/SetStepNav'
|
import { SetStepNav } from '../collections/Edit/SetStepNav'
|
||||||
|
import { LivePreviewProvider } from './Context'
|
||||||
|
import { useLivePreviewContext } from './Context/context'
|
||||||
import { LivePreview } from './Preview'
|
import { LivePreview } from './Preview'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import { usePopupWindow } from './usePopupWindow'
|
import { usePopupWindow } from './usePopupWindow'
|
||||||
|
|
||||||
const baseClass = 'live-preview'
|
const baseClass = 'live-preview'
|
||||||
|
|
||||||
export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
const PreviewView: React.FC<EditViewProps> = (props) => {
|
||||||
const { i18n, t } = useTranslation('general')
|
const { i18n, t } = useTranslation('general')
|
||||||
const config = useConfig()
|
const { previewWindowType } = useLivePreviewContext()
|
||||||
const documentInfo = useDocumentInfo()
|
|
||||||
const locale = useLocale()
|
|
||||||
|
|
||||||
let livePreviewConfig: LivePreviewConfig = config?.admin?.livePreview
|
|
||||||
|
|
||||||
if ('collection' in props) {
|
|
||||||
livePreviewConfig = {
|
|
||||||
...(livePreviewConfig || {}),
|
|
||||||
...(props?.collection.admin.livePreview || {}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('global' in props) {
|
|
||||||
livePreviewConfig = {
|
|
||||||
...(livePreviewConfig || {}),
|
|
||||||
...(props?.global.admin.livePreview || {}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const url =
|
|
||||||
typeof livePreviewConfig?.url === 'function'
|
|
||||||
? livePreviewConfig?.url({
|
|
||||||
data: props?.data,
|
|
||||||
documentInfo,
|
|
||||||
locale,
|
|
||||||
})
|
|
||||||
: livePreviewConfig?.url
|
|
||||||
|
|
||||||
const popupState = usePopupWindow({
|
|
||||||
eventType: 'payload-live-preview',
|
|
||||||
url,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { apiURL, data, permissions } = props
|
const { apiURL, data, permissions } = props
|
||||||
|
|
||||||
@@ -113,14 +83,14 @@ export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
|||||||
permissions={permissions}
|
permissions={permissions}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={[baseClass, popupState?.isPopupOpen && `${baseClass}--detached`]
|
className={[baseClass, previewWindowType === 'popup' && `${baseClass}--detached`]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ')}
|
.join(' ')}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
`${baseClass}__main`,
|
`${baseClass}__main`,
|
||||||
popupState?.isPopupOpen && `${baseClass}__main--popup-open`,
|
previewWindowType === 'popup' && `${baseClass}__main--popup-open`,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ')}
|
.join(' ')}
|
||||||
@@ -148,13 +118,67 @@ export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</Gutter>
|
</Gutter>
|
||||||
</div>
|
</div>
|
||||||
<LivePreview
|
<LivePreview {...props} />
|
||||||
{...props}
|
|
||||||
livePreviewConfig={livePreviewConfig}
|
|
||||||
popupState={popupState}
|
|
||||||
url={url}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
||||||
|
const config = useConfig()
|
||||||
|
const documentInfo = useDocumentInfo()
|
||||||
|
const locale = useLocale()
|
||||||
|
|
||||||
|
let livePreviewConfig: LivePreviewConfig = config?.admin?.livePreview
|
||||||
|
|
||||||
|
if ('collection' in props) {
|
||||||
|
livePreviewConfig = {
|
||||||
|
...(livePreviewConfig || {}),
|
||||||
|
...(props?.collection.admin.livePreview || {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('global' in props) {
|
||||||
|
livePreviewConfig = {
|
||||||
|
...(livePreviewConfig || {}),
|
||||||
|
...(props?.global.admin.livePreview || {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url =
|
||||||
|
typeof livePreviewConfig?.url === 'function'
|
||||||
|
? livePreviewConfig?.url({
|
||||||
|
data: props?.data,
|
||||||
|
documentInfo,
|
||||||
|
locale,
|
||||||
|
})
|
||||||
|
: livePreviewConfig?.url
|
||||||
|
|
||||||
|
const breakpoints: LivePreviewConfig['breakpoints'] = [
|
||||||
|
...(livePreviewConfig?.breakpoints || []),
|
||||||
|
{
|
||||||
|
name: 'responsive',
|
||||||
|
height: '100%',
|
||||||
|
label: 'Responsive',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const { isPopupOpen, openPopupWindow, popupRef } = usePopupWindow({
|
||||||
|
eventType: 'payload-live-preview',
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LivePreviewProvider
|
||||||
|
{...props}
|
||||||
|
breakpoints={breakpoints}
|
||||||
|
isPopupOpen={isPopupOpen}
|
||||||
|
openPopupWindow={openPopupWindow}
|
||||||
|
popupRef={popupRef}
|
||||||
|
url={url}
|
||||||
|
>
|
||||||
|
<PreviewView {...props} />
|
||||||
|
</LivePreviewProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,17 +19,14 @@ export const usePopupWindow = (props: {
|
|||||||
url: string
|
url: string
|
||||||
}): {
|
}): {
|
||||||
isPopupOpen: boolean
|
isPopupOpen: boolean
|
||||||
openPopupWindow: (e: React.MouseEvent<HTMLAnchorElement>) => void
|
openPopupWindow: () => void
|
||||||
popupHasLoaded: boolean
|
|
||||||
popupRef?: React.MutableRefObject<Window | null>
|
popupRef?: React.MutableRefObject<Window | null>
|
||||||
} => {
|
} => {
|
||||||
const { eventType, onMessage, url } = props
|
const { eventType, onMessage, url } = props
|
||||||
const isReceivingMessage = useRef(false)
|
const isReceivingMessage = useRef(false)
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [popupHasLoaded, setPopupHasLoaded] = useState(false)
|
|
||||||
const { serverURL } = useConfig()
|
const { serverURL } = useConfig()
|
||||||
const popupRef = useRef<Window | null>(null)
|
const popupRef = useRef<Window | null>(null)
|
||||||
const hasAttachedMessageListener = useRef(false)
|
|
||||||
|
|
||||||
// Optionally broadcast messages back out to the parent component
|
// Optionally broadcast messages back out to the parent component
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -65,8 +62,10 @@ export const usePopupWindow = (props: {
|
|||||||
|
|
||||||
// Customize the size, position, and style of the popup window
|
// Customize the size, position, and style of the popup window
|
||||||
const openPopupWindow = useCallback(
|
const openPopupWindow = useCallback(
|
||||||
(e) => {
|
(e?: MouseEvent) => {
|
||||||
e.preventDefault()
|
if (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
const features = {
|
const features = {
|
||||||
height: 700,
|
height: 700,
|
||||||
@@ -106,27 +105,6 @@ export const usePopupWindow = (props: {
|
|||||||
[url],
|
[url],
|
||||||
)
|
)
|
||||||
|
|
||||||
// the only cross-origin way of detecting when a popup window has loaded
|
|
||||||
// we catch a message event that the site rendered within the popup window fires
|
|
||||||
// there is no way in js to add an event listener to a popup window across domains
|
|
||||||
useEffect(() => {
|
|
||||||
if (hasAttachedMessageListener.current) return
|
|
||||||
hasAttachedMessageListener.current = true
|
|
||||||
|
|
||||||
window.addEventListener('message', (event) => {
|
|
||||||
const data = JSON.parse(event.data)
|
|
||||||
|
|
||||||
if (
|
|
||||||
url.startsWith(event.origin) &&
|
|
||||||
data.type === eventType &&
|
|
||||||
data.popupReady &&
|
|
||||||
!popupHasLoaded
|
|
||||||
) {
|
|
||||||
setPopupHasLoaded(true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [url, eventType, popupHasLoaded])
|
|
||||||
|
|
||||||
// this is the most stable and widely supported way to check if a popup window is no longer open
|
// this is the most stable and widely supported way to check if a popup window is no longer open
|
||||||
// we poll its ref every x ms and use the popup window's `closed` property
|
// we poll its ref every x ms and use the popup window's `closed` property
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -137,7 +115,6 @@ export const usePopupWindow = (props: {
|
|||||||
if (popupRef.current.closed) {
|
if (popupRef.current.closed) {
|
||||||
clearInterval(timer)
|
clearInterval(timer)
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
setPopupHasLoaded(false)
|
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
} else {
|
} else {
|
||||||
@@ -154,7 +131,6 @@ export const usePopupWindow = (props: {
|
|||||||
return {
|
return {
|
||||||
isPopupOpen: isOpen,
|
isPopupOpen: isOpen,
|
||||||
openPopupWindow,
|
openPopupWindow,
|
||||||
popupHasLoaded,
|
|
||||||
popupRef,
|
popupRef,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
test/live-preview/collections/Users.ts
Normal file
10
test/live-preview/collections/Users.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||||
|
|
||||||
|
export const Users: CollectionConfig = {
|
||||||
|
slug: 'users',
|
||||||
|
auth: true,
|
||||||
|
admin: {
|
||||||
|
useAsTitle: 'title',
|
||||||
|
},
|
||||||
|
fields: [],
|
||||||
|
}
|
||||||
@@ -1,20 +1,12 @@
|
|||||||
import path from 'path'
|
|
||||||
|
|
||||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
|
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
|
||||||
import { devUser } from '../credentials'
|
|
||||||
import Categories from './collections/Categories'
|
import Categories from './collections/Categories'
|
||||||
import { Media } from './collections/Media'
|
import { Media } from './collections/Media'
|
||||||
import { Pages } from './collections/Pages'
|
import { Pages } from './collections/Pages'
|
||||||
import { Posts, postsSlug } from './collections/Posts'
|
import { Posts } from './collections/Posts'
|
||||||
|
import { Users } from './collections/Users'
|
||||||
import { Footer } from './globals/Footer'
|
import { Footer } from './globals/Footer'
|
||||||
import { Header } from './globals/Header'
|
import { Header } from './globals/Header'
|
||||||
import { footer } from './seed/footer'
|
import { seed } from './seed'
|
||||||
import { header } from './seed/header'
|
|
||||||
import { home } from './seed/home'
|
|
||||||
import { post1 } from './seed/post-1'
|
|
||||||
import { post2 } from './seed/post-2'
|
|
||||||
import { post3 } from './seed/post-3'
|
|
||||||
import { postsPage } from './seed/posts-page'
|
|
||||||
|
|
||||||
export const pagesSlug = 'pages'
|
export const pagesSlug = 'pages'
|
||||||
|
|
||||||
@@ -40,92 +32,7 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
cors: ['http://localhost:3001'],
|
cors: ['http://localhost:3001'],
|
||||||
csrf: ['http://localhost:3001'],
|
csrf: ['http://localhost:3001'],
|
||||||
collections: [
|
collections: [Users, Pages, Posts, Categories, Media],
|
||||||
{
|
|
||||||
slug: 'users',
|
|
||||||
auth: true,
|
|
||||||
admin: {
|
|
||||||
useAsTitle: 'title',
|
|
||||||
},
|
|
||||||
fields: [],
|
|
||||||
},
|
|
||||||
Pages,
|
|
||||||
Posts,
|
|
||||||
Categories,
|
|
||||||
Media,
|
|
||||||
],
|
|
||||||
globals: [Header, Footer],
|
globals: [Header, Footer],
|
||||||
onInit: async (payload) => {
|
onInit: seed,
|
||||||
await payload.create({
|
|
||||||
collection: 'users',
|
|
||||||
data: {
|
|
||||||
email: devUser.email,
|
|
||||||
password: devUser.password,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const media = await payload.create({
|
|
||||||
collection: 'media',
|
|
||||||
filePath: path.resolve(__dirname, 'image-1.jpg'),
|
|
||||||
data: {
|
|
||||||
alt: 'Image 1',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const mediaID = payload.db.defaultIDType === 'number' ? media.id : `"${media.id}"`
|
|
||||||
|
|
||||||
const [post1Doc, post2Doc, post3Doc] = await Promise.all([
|
|
||||||
await payload.create({
|
|
||||||
collection: postsSlug,
|
|
||||||
data: JSON.parse(JSON.stringify(post1).replace(/"\{\{IMAGE\}\}"/g, mediaID)),
|
|
||||||
}),
|
|
||||||
await payload.create({
|
|
||||||
collection: postsSlug,
|
|
||||||
data: JSON.parse(JSON.stringify(post2).replace(/"\{\{IMAGE\}\}"/g, mediaID)),
|
|
||||||
}),
|
|
||||||
await payload.create({
|
|
||||||
collection: postsSlug,
|
|
||||||
data: JSON.parse(JSON.stringify(post3).replace(/"\{\{IMAGE\}\}"/g, mediaID)),
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
|
|
||||||
const postsPageDoc = await payload.create({
|
|
||||||
collection: pagesSlug,
|
|
||||||
data: JSON.parse(JSON.stringify(postsPage).replace(/"\{\{IMAGE\}\}"/g, mediaID)),
|
|
||||||
})
|
|
||||||
|
|
||||||
let postsPageDocID = postsPageDoc.id
|
|
||||||
let post1DocID = post1Doc.id
|
|
||||||
let post2DocID = post2Doc.id
|
|
||||||
let post3DocID = post3Doc.id
|
|
||||||
|
|
||||||
if (payload.db.defaultIDType !== 'number') {
|
|
||||||
postsPageDocID = `"${postsPageDoc.id}"`
|
|
||||||
post1DocID = `"${post1Doc.id}"`
|
|
||||||
post2DocID = `"${post2Doc.id}"`
|
|
||||||
post3DocID = `"${post3Doc.id}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
await payload.create({
|
|
||||||
collection: pagesSlug,
|
|
||||||
data: JSON.parse(
|
|
||||||
JSON.stringify(home)
|
|
||||||
.replace(/"\{\{MEDIA_ID\}\}"/g, mediaID)
|
|
||||||
.replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)
|
|
||||||
.replace(/"\{\{POST_1_ID\}\}"/g, post1DocID)
|
|
||||||
.replace(/"\{\{POST_2_ID\}\}"/g, post2DocID)
|
|
||||||
.replace(/"\{\{POST_3_ID\}\}"/g, post3DocID),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
await payload.updateGlobal({
|
|
||||||
slug: 'header',
|
|
||||||
data: JSON.parse(JSON.stringify(header).replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)),
|
|
||||||
})
|
|
||||||
|
|
||||||
await payload.updateGlobal({
|
|
||||||
slug: 'footer',
|
|
||||||
data: JSON.parse(JSON.stringify(footer)),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
88
test/live-preview/seed/index.ts
Normal file
88
test/live-preview/seed/index.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import type { Config } from '../../../packages/payload/src/config/types'
|
||||||
|
|
||||||
|
import { devUser } from '../../credentials'
|
||||||
|
import { postsSlug } from '../collections/Posts'
|
||||||
|
import { pagesSlug } from '../config'
|
||||||
|
import { footer } from './footer'
|
||||||
|
import { header } from './header'
|
||||||
|
import { home } from './home'
|
||||||
|
import { post1 } from './post-1'
|
||||||
|
import { post2 } from './post-2'
|
||||||
|
import { post3 } from './post-3'
|
||||||
|
import { postsPage } from './posts-page'
|
||||||
|
|
||||||
|
export const seed: Config['onInit'] = async (payload) => {
|
||||||
|
await payload.create({
|
||||||
|
collection: 'users',
|
||||||
|
data: {
|
||||||
|
email: devUser.email,
|
||||||
|
password: devUser.password,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const media = await payload.create({
|
||||||
|
collection: 'media',
|
||||||
|
filePath: path.resolve(__dirname, 'image-1.jpg'),
|
||||||
|
data: {
|
||||||
|
alt: 'Image 1',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const mediaID = payload.db.defaultIDType === 'number' ? media.id : `"${media.id}"`
|
||||||
|
|
||||||
|
const [post1Doc, post2Doc, post3Doc] = await Promise.all([
|
||||||
|
await payload.create({
|
||||||
|
collection: postsSlug,
|
||||||
|
data: JSON.parse(JSON.stringify(post1).replace(/"\{\{IMAGE\}\}"/g, mediaID)),
|
||||||
|
}),
|
||||||
|
await payload.create({
|
||||||
|
collection: postsSlug,
|
||||||
|
data: JSON.parse(JSON.stringify(post2).replace(/"\{\{IMAGE\}\}"/g, mediaID)),
|
||||||
|
}),
|
||||||
|
await payload.create({
|
||||||
|
collection: postsSlug,
|
||||||
|
data: JSON.parse(JSON.stringify(post3).replace(/"\{\{IMAGE\}\}"/g, mediaID)),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
const postsPageDoc = await payload.create({
|
||||||
|
collection: pagesSlug,
|
||||||
|
data: JSON.parse(JSON.stringify(postsPage).replace(/"\{\{IMAGE\}\}"/g, mediaID)),
|
||||||
|
})
|
||||||
|
|
||||||
|
let postsPageDocID = postsPageDoc.id
|
||||||
|
let post1DocID = post1Doc.id
|
||||||
|
let post2DocID = post2Doc.id
|
||||||
|
let post3DocID = post3Doc.id
|
||||||
|
|
||||||
|
if (payload.db.defaultIDType !== 'number') {
|
||||||
|
postsPageDocID = `"${postsPageDoc.id}"`
|
||||||
|
post1DocID = `"${post1Doc.id}"`
|
||||||
|
post2DocID = `"${post2Doc.id}"`
|
||||||
|
post3DocID = `"${post3Doc.id}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
collection: pagesSlug,
|
||||||
|
data: JSON.parse(
|
||||||
|
JSON.stringify(home)
|
||||||
|
.replace(/"\{\{MEDIA_ID\}\}"/g, mediaID)
|
||||||
|
.replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)
|
||||||
|
.replace(/"\{\{POST_1_ID\}\}"/g, post1DocID)
|
||||||
|
.replace(/"\{\{POST_2_ID\}\}"/g, post2DocID)
|
||||||
|
.replace(/"\{\{POST_3_ID\}\}"/g, post3DocID),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.updateGlobal({
|
||||||
|
slug: 'header',
|
||||||
|
data: JSON.parse(JSON.stringify(header).replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)),
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.updateGlobal({
|
||||||
|
slug: 'footer',
|
||||||
|
data: JSON.parse(JSON.stringify(footer)),
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user