chore: optimizes live preview (#3713)

This commit is contained in:
Jacob Fletcher
2023-10-17 12:42:31 -04:00
committed by GitHub
parent dd0ac066ce
commit cdbfc9132a
11 changed files with 325 additions and 272 deletions

View File

@@ -3,21 +3,31 @@ import type { Dispatch } from 'react'
import { createContext, useContext } from 'react'
import type { LivePreviewConfig } from '../../../../../exports/config'
import type { fieldSchemaToJSON } from '../../../../../utilities/fieldSchemaToJSON'
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
@@ -30,22 +40,31 @@ export interface LivePreviewContextType {
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: () => {},
@@ -58,6 +77,7 @@ export const LivePreviewContext = createContext<LivePreviewContextType>({
x: 0,
y: 0,
},
url: undefined,
zoom: 1,
})

View File

@@ -1,39 +1,48 @@
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 { Field } from '../../../../../fields/config/types'
import type { EditViewProps } from '../../types'
import type { usePopupWindow } from '../usePopupWindow'
import { fieldSchemaToJSON } from '../../../../../utilities/fieldSchemaToJSON'
import { customCollisionDetection } from './collisionDetection'
import { LivePreviewContext } from './context'
import { sizeReducer } from './sizeReducer'
export type ToolbarProviderProps = EditViewProps & {
export type LivePreviewProviderProps = EditViewProps & {
appIsReady?: boolean
breakpoints?: LivePreviewConfig['breakpoints']
children: React.ReactNode
deviceSize?: {
height: number
width: number
}
popupState: ReturnType<typeof usePopupWindow>
isPopupOpen?: boolean
openPopupWindow?: ReturnType<typeof usePopupWindow>['openPopupWindow']
popupRef?: React.MutableRefObject<Window>
url?: string
}
export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
const { breakpoints, children } = props
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] = 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 [measuredDeviceSize, setMeasuredDeviceSize] = React.useState({
const [measuredDeviceSize, setMeasuredDeviceSize] = useState({
height: 0,
width: 0,
})
@@ -41,6 +50,22 @@ export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
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
@@ -94,24 +119,70 @@ export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
}
}, [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 (
<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,
}}
>

View File

@@ -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 { usePopupWindow } from '../usePopupWindow'
import { fieldSchemaToJSON } from '../../../../../utilities/fieldSchemaToJSON'
import { useAllFormFields } from '../../../forms/Form/context'
import reduceFieldsToValues from '../../../forms/Form/reduceFieldsToValues'
import { LivePreviewProvider } from '../Context'
import { useLivePreviewContext } from '../Context/context'
import { DeviceContainer } from '../Device'
import { IFrame } from '../IFrame'
@@ -17,93 +12,81 @@ import './index.scss'
const baseClass = 'live-preview-window'
const Preview: React.FC<
EditViewProps & {
popupState: ReturnType<typeof usePopupWindow>
url?: string
}
> = (props) => {
export const LivePreview: React.FC<EditViewProps> = (props) => {
const {
popupState: { isPopupOpen, popupHasLoaded, popupRef },
appIsReady,
iframeHasLoaded,
iframeRef,
popupRef,
previewWindowType,
setIframeHasLoaded,
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 [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
// Or it could be a separate popup window
// We need to transmit data to both accordingly
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)
// 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({
data: values,
fieldSchemaJSON,
fieldSchemaJSON: shouldSendSchema ? fieldSchemaJSON : undefined,
type: 'payload-live-preview',
})
// external window
if (isPopupOpen) {
setIframeHasLoaded(false)
if (popupRef.current) {
popupRef.current.postMessage(message, url)
}
// Post message to external popup window
if (previewWindowType === 'popup' && popupRef.current) {
popupRef.current.postMessage(message, url)
}
// embedded iframe
if (!isPopupOpen) {
if (iframeHasLoaded && iframeRef.current) {
iframeRef.current.contentWindow?.postMessage(message, url)
}
// Post message to embedded iframe
if (previewWindowType === 'iframe' && iframeRef.current) {
iframeRef.current.contentWindow?.postMessage(message, url)
}
}
}, [
fields,
url,
iframeHasLoaded,
isPopupOpen,
previewWindowType,
popupRef,
popupHasLoaded,
appIsReady,
iframeRef,
setIframeHasLoaded,
fieldSchemaJSON,
])
if (!isPopupOpen) {
if (previewWindowType === 'iframe') {
return (
<div
className={[
baseClass,
isPopupOpen && `${baseClass}--popup-open`,
breakpoint && breakpoint !== 'responsive' && `${baseClass}--has-breakpoint`,
]
.filter(Boolean)
.join(' ')}
>
<div className={`${baseClass}__wrapper`}>
<LivePreviewToolbar {...props} iframeRef={iframeRef} url={url} />
<LivePreviewToolbar {...props} />
<div className={`${baseClass}__main`}>
<DeviceContainer>
<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>
)
}

View File

@@ -1,6 +1,6 @@
import React from 'react'
import type { LivePreviewToolbarProps } from '..'
import type { EditViewProps } from '../../../types'
import { X } from '../../../..'
import { ExternalLinkIcon } from '../../../../graphics/ExternalLink'
@@ -10,13 +10,9 @@ import './index.scss'
const baseClass = 'live-preview-toolbar-controls'
export const ToolbarControls: React.FC<LivePreviewToolbarProps> = (props) => {
const { breakpoint, breakpoints, setBreakpoint, setZoom, zoom } = useLivePreviewContext()
const {
popupState: { openPopupWindow },
url,
} = props
export const ToolbarControls: React.FC<EditViewProps> = () => {
const { breakpoint, breakpoints, setBreakpoint, setPreviewWindowType, setZoom, url, zoom } =
useLivePreviewContext()
return (
<div className={baseClass}>
@@ -57,7 +53,15 @@ export const ToolbarControls: React.FC<LivePreviewToolbarProps> = (props) => {
<option value={150}>150%</option>
<option value={200}>200%</option>
</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 />
</a>
</div>

View File

@@ -1,7 +1,7 @@
import { useDraggable } from '@dnd-kit/core'
import React from 'react'
import type { ToolbarProviderProps } from '../Context'
import type { EditViewProps } from '../../types'
import DragHandle from '../../../icons/Drag'
import { useLivePreviewContext } from '../Context/context'
@@ -10,11 +10,7 @@ import './index.scss'
const baseClass = 'live-preview-toolbar'
export type LivePreviewToolbarProps = Omit<ToolbarProviderProps, 'children'> & {
iframeRef: React.RefObject<HTMLIFrameElement>
}
const DraggableToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
const DraggableToolbar: React.FC<EditViewProps> = (props) => {
const { toolbarPosition } = useLivePreviewContext()
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 (
<div className={[baseClass, `${baseClass}--static`].join(' ')}>
<ToolbarControls {...props} />
@@ -59,7 +55,7 @@ const StaticToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
}
export const LivePreviewToolbar: React.FC<
LivePreviewToolbarProps & {
EditViewProps & {
draggable?: boolean
}
> = (props) => {

View File

@@ -17,47 +17,17 @@ import { useDocumentInfo } from '../../utilities/DocumentInfo'
import { useLocale } from '../../utilities/Locale'
import Meta from '../../utilities/Meta'
import { SetStepNav } from '../collections/Edit/SetStepNav'
import { LivePreviewProvider } from './Context'
import { useLivePreviewContext } from './Context/context'
import { LivePreview } from './Preview'
import './index.scss'
import { usePopupWindow } from './usePopupWindow'
const baseClass = 'live-preview'
export const LivePreviewView: React.FC<EditViewProps> = (props) => {
const PreviewView: React.FC<EditViewProps> = (props) => {
const { i18n, t } = useTranslation('general')
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 popupState = usePopupWindow({
eventType: 'payload-live-preview',
url,
})
const { previewWindowType } = useLivePreviewContext()
const { apiURL, data, permissions } = props
@@ -113,14 +83,14 @@ export const LivePreviewView: React.FC<EditViewProps> = (props) => {
permissions={permissions}
/>
<div
className={[baseClass, popupState?.isPopupOpen && `${baseClass}--detached`]
className={[baseClass, previewWindowType === 'popup' && `${baseClass}--detached`]
.filter(Boolean)
.join(' ')}
>
<div
className={[
`${baseClass}__main`,
popupState?.isPopupOpen && `${baseClass}__main--popup-open`,
previewWindowType === 'popup' && `${baseClass}__main--popup-open`,
]
.filter(Boolean)
.join(' ')}
@@ -148,13 +118,67 @@ export const LivePreviewView: React.FC<EditViewProps> = (props) => {
)}
</Gutter>
</div>
<LivePreview
{...props}
livePreviewConfig={livePreviewConfig}
popupState={popupState}
url={url}
/>
<LivePreview {...props} />
</div>
</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>
)
}

View File

@@ -19,17 +19,14 @@ export const usePopupWindow = (props: {
url: string
}): {
isPopupOpen: boolean
openPopupWindow: (e: React.MouseEvent<HTMLAnchorElement>) => void
popupHasLoaded: boolean
openPopupWindow: () => void
popupRef?: React.MutableRefObject<Window | null>
} => {
const { eventType, onMessage, url } = props
const isReceivingMessage = useRef(false)
const [isOpen, setIsOpen] = useState(false)
const [popupHasLoaded, setPopupHasLoaded] = useState(false)
const { serverURL } = useConfig()
const popupRef = useRef<Window | null>(null)
const hasAttachedMessageListener = useRef(false)
// Optionally broadcast messages back out to the parent component
useEffect(() => {
@@ -65,8 +62,10 @@ export const usePopupWindow = (props: {
// Customize the size, position, and style of the popup window
const openPopupWindow = useCallback(
(e) => {
e.preventDefault()
(e?: MouseEvent) => {
if (e) {
e.preventDefault()
}
const features = {
height: 700,
@@ -106,27 +105,6 @@ export const usePopupWindow = (props: {
[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
// we poll its ref every x ms and use the popup window's `closed` property
useEffect(() => {
@@ -137,7 +115,6 @@ export const usePopupWindow = (props: {
if (popupRef.current.closed) {
clearInterval(timer)
setIsOpen(false)
setPopupHasLoaded(false)
}
}, 1000)
} else {
@@ -154,7 +131,6 @@ export const usePopupWindow = (props: {
return {
isPopupOpen: isOpen,
openPopupWindow,
popupHasLoaded,
popupRef,
}
}

View 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: [],
}

View File

@@ -1,20 +1,12 @@
import path from 'path'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials'
import Categories from './collections/Categories'
import { Media } from './collections/Media'
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 { Header } from './globals/Header'
import { footer } from './seed/footer'
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'
import { seed } from './seed'
export const pagesSlug = 'pages'
@@ -40,92 +32,7 @@ export default buildConfigWithDefaults({
},
cors: ['http://localhost:3001'],
csrf: ['http://localhost:3001'],
collections: [
{
slug: 'users',
auth: true,
admin: {
useAsTitle: 'title',
},
fields: [],
},
Pages,
Posts,
Categories,
Media,
],
collections: [Users, Pages, Posts, Categories, Media],
globals: [Header, Footer],
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)),
})
},
onInit: seed,
})

View File

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

View 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)),
})
}