chore(next): ssr versions view (#5085)
This commit is contained in:
232
packages/next/src/pages/API/index.client.tsx
Normal file
232
packages/next/src/pages/API/index.client.tsx
Normal file
@@ -0,0 +1,232 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
|
||||
import {
|
||||
CopyToClipboard,
|
||||
Gutter,
|
||||
Checkbox,
|
||||
SetDocumentStepNav as SetStepNav,
|
||||
Form,
|
||||
Select,
|
||||
Number as NumberInput,
|
||||
EditViewProps,
|
||||
useConfig,
|
||||
MinimizeMaximize,
|
||||
useActions,
|
||||
useTranslation,
|
||||
useLocale,
|
||||
} from '@payloadcms/ui'
|
||||
import { RenderJSON } from './RenderJSON'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import qs from 'qs'
|
||||
import { toast } from 'react-toastify'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'query-inspector'
|
||||
|
||||
export const APIViewClient: React.FC<EditViewProps> = (props) => {
|
||||
const { data: initialData } = props
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const { setViewActions } = useActions()
|
||||
const { i18n } = useTranslation()
|
||||
const { code } = useLocale()
|
||||
|
||||
const {
|
||||
localization,
|
||||
routes: { api: apiRoute },
|
||||
serverURL,
|
||||
collections,
|
||||
globals,
|
||||
} = useConfig()
|
||||
|
||||
const collectionConfig =
|
||||
'collectionSlug' in props &&
|
||||
collections.find((collection) => collection.slug === props.collectionSlug)
|
||||
|
||||
const globalConfig =
|
||||
'globalSlug' in props && globals.find((global) => global.slug === props.globalSlug)
|
||||
|
||||
const id = 'id' in props ? props.id : undefined
|
||||
|
||||
const collectionSlug = collectionConfig?.slug
|
||||
const globalSlug = globalConfig?.slug
|
||||
|
||||
const localeOptions =
|
||||
localization &&
|
||||
localization.locales.map((locale) => ({ label: locale.label, value: locale.code }))
|
||||
|
||||
const isEditing = Boolean(globalSlug || (collectionSlug && !!id))
|
||||
|
||||
let draftsEnabled: boolean = false
|
||||
let docEndpoint: string = ''
|
||||
|
||||
if (collectionConfig) {
|
||||
draftsEnabled = Boolean(collectionConfig.versions?.drafts)
|
||||
docEndpoint = `/${collectionSlug}/${id}`
|
||||
}
|
||||
|
||||
if (globalConfig) {
|
||||
draftsEnabled = Boolean(globalConfig.versions?.drafts)
|
||||
docEndpoint = `/globals/${globalSlug}`
|
||||
}
|
||||
|
||||
const [data, setData] = React.useState<any>(initialData)
|
||||
const [draft, setDraft] = React.useState<boolean>(searchParams.get('draft') === 'true')
|
||||
const [locale, setLocale] = React.useState<string>(searchParams?.get('locale') || code)
|
||||
const [depth, setDepth] = React.useState<string>(searchParams.get('depth') || '1')
|
||||
const [authenticated, setAuthenticated] = React.useState<boolean>(true)
|
||||
const [fullscreen, setFullscreen] = React.useState<boolean>(false)
|
||||
|
||||
const fetchURL = `${serverURL}${apiRoute}${docEndpoint}${qs.stringify(
|
||||
{
|
||||
locale,
|
||||
draft,
|
||||
depth,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
)}`
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await fetch(fetchURL, {
|
||||
method: 'GET',
|
||||
credentials: authenticated ? 'include' : 'omit',
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const json = await res.json()
|
||||
setData(json)
|
||||
} catch (error) {
|
||||
toast.error('Error parsing response')
|
||||
console.error(error)
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Error making request')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
fetchData()
|
||||
}, [i18n.language, fetchURL, authenticated])
|
||||
|
||||
React.useEffect(() => {
|
||||
const editConfig = (collectionConfig || globalConfig)?.admin?.components?.views?.Edit
|
||||
const apiActions =
|
||||
editConfig && 'API' in editConfig && 'actions' in editConfig.API ? editConfig.API.actions : []
|
||||
|
||||
setViewActions(apiActions)
|
||||
|
||||
return () => {
|
||||
setViewActions([])
|
||||
}
|
||||
}, [collectionConfig, globalConfig, setViewActions])
|
||||
|
||||
return (
|
||||
<Gutter
|
||||
className={[baseClass, fullscreen && `${baseClass}--fullscreen`].filter(Boolean).join(' ')}
|
||||
right={false}
|
||||
>
|
||||
<SetStepNav
|
||||
collectionSlug={collectionSlug}
|
||||
useAsTitle={collectionConfig?.admin?.useAsTitle}
|
||||
pluralLabel={collectionConfig?.labels.plural}
|
||||
globalLabel={globalConfig?.label}
|
||||
globalSlug={globalSlug}
|
||||
id={id}
|
||||
isEditing={isEditing}
|
||||
view="API"
|
||||
/>
|
||||
<div className={`${baseClass}__configuration`}>
|
||||
<div className={`${baseClass}__api-url`}>
|
||||
<span className={`${baseClass}__label`}>
|
||||
API URL <CopyToClipboard value={fetchURL} />
|
||||
</span>
|
||||
<a href={fetchURL} rel="noopener noreferrer" target="_blank">
|
||||
{fetchURL}
|
||||
</a>
|
||||
</div>
|
||||
<Form
|
||||
initialState={{
|
||||
authenticated: {
|
||||
value: authenticated || false,
|
||||
initialValue: authenticated || false,
|
||||
valid: true,
|
||||
},
|
||||
draft: {
|
||||
value: draft || false,
|
||||
initialValue: draft || false,
|
||||
valid: true,
|
||||
},
|
||||
depth: {
|
||||
value: Number(depth || 0),
|
||||
initialValue: Number(depth || 0),
|
||||
valid: true,
|
||||
},
|
||||
locale: {
|
||||
value: locale,
|
||||
initialValue: locale,
|
||||
valid: true,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__form-fields`}>
|
||||
<div className={`${baseClass}__filter-query-checkboxes`}>
|
||||
{draftsEnabled && (
|
||||
<Checkbox
|
||||
name="draft"
|
||||
path="draft"
|
||||
label="Draft"
|
||||
onChange={() => setDraft(!draft)}
|
||||
/>
|
||||
)}
|
||||
<Checkbox
|
||||
name="authenticated"
|
||||
path="authenticated"
|
||||
label="Authenticated"
|
||||
onChange={() => setAuthenticated(!authenticated)}
|
||||
/>
|
||||
</div>
|
||||
{localeOptions && (
|
||||
<Select
|
||||
label="Locale"
|
||||
name="locale"
|
||||
options={localeOptions}
|
||||
path="locale"
|
||||
onChange={(value) => setLocale(value)}
|
||||
/>
|
||||
)}
|
||||
<NumberInput
|
||||
label="Depth"
|
||||
name="depth"
|
||||
path="depth"
|
||||
min={0}
|
||||
max={10}
|
||||
step={1}
|
||||
onChange={(value) => setDepth(value.toString())}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
<div className={`${baseClass}__results-wrapper`}>
|
||||
<div className={`${baseClass}__toggle-fullscreen-button-container`}>
|
||||
<button
|
||||
aria-label="toggle fullscreen"
|
||||
className={`${baseClass}__toggle-fullscreen-button`}
|
||||
onClick={() => setFullscreen(!fullscreen)}
|
||||
type="button"
|
||||
>
|
||||
<MinimizeMaximize isMinimized={!fullscreen} />
|
||||
</button>
|
||||
</div>
|
||||
<div className={`${baseClass}__results`}>
|
||||
<RenderJSON object={data} />
|
||||
</div>
|
||||
</div>
|
||||
</Gutter>
|
||||
)
|
||||
}
|
||||
@@ -1,232 +1,9 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
import React from 'react'
|
||||
import { ServerSideEditViewProps } from '../../../../ui/src/views/types'
|
||||
import { APIViewClient } from './index.client'
|
||||
import { sanitizedEditViewProps } from '../Edit/sanitizedEditViewProps'
|
||||
|
||||
import {
|
||||
CopyToClipboard,
|
||||
Gutter,
|
||||
Checkbox,
|
||||
SetDocumentStepNav as SetStepNav,
|
||||
Form,
|
||||
Select,
|
||||
Number as NumberInput,
|
||||
EditViewProps,
|
||||
useConfig,
|
||||
MinimizeMaximize,
|
||||
useActions,
|
||||
useTranslation,
|
||||
useLocale,
|
||||
} from '@payloadcms/ui'
|
||||
import { RenderJSON } from './RenderJSON'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import qs from 'qs'
|
||||
import { toast } from 'react-toastify'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'query-inspector'
|
||||
|
||||
export const APIView: React.FC<EditViewProps> = (props) => {
|
||||
const { data: initialData } = props
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const { setViewActions } = useActions()
|
||||
const { i18n } = useTranslation()
|
||||
const { code } = useLocale()
|
||||
|
||||
const {
|
||||
localization,
|
||||
routes: { api: apiRoute },
|
||||
serverURL,
|
||||
collections,
|
||||
globals,
|
||||
} = useConfig()
|
||||
|
||||
const collectionConfig =
|
||||
'collectionSlug' in props &&
|
||||
collections.find((collection) => collection.slug === props.collectionSlug)
|
||||
|
||||
const globalConfig =
|
||||
'globalSlug' in props && globals.find((global) => global.slug === props.globalSlug)
|
||||
|
||||
const id = 'id' in props ? props.id : undefined
|
||||
|
||||
const collectionSlug = collectionConfig?.slug
|
||||
const globalSlug = globalConfig?.slug
|
||||
|
||||
const localeOptions =
|
||||
localization &&
|
||||
localization.locales.map((locale) => ({ label: locale.label, value: locale.code }))
|
||||
|
||||
const isEditing = Boolean(globalSlug || (collectionSlug && !!id))
|
||||
|
||||
let draftsEnabled: boolean = false
|
||||
let docEndpoint: string = ''
|
||||
|
||||
if (collectionConfig) {
|
||||
draftsEnabled = Boolean(collectionConfig.versions?.drafts)
|
||||
docEndpoint = `/${collectionSlug}/${id}`
|
||||
}
|
||||
|
||||
if (globalConfig) {
|
||||
draftsEnabled = Boolean(globalConfig.versions?.drafts)
|
||||
docEndpoint = `/globals/${globalSlug}`
|
||||
}
|
||||
|
||||
const [data, setData] = React.useState<any>(initialData)
|
||||
const [draft, setDraft] = React.useState<boolean>(searchParams.get('draft') === 'true')
|
||||
const [locale, setLocale] = React.useState<string>(searchParams?.get('locale') || code)
|
||||
const [depth, setDepth] = React.useState<string>(searchParams.get('depth') || '1')
|
||||
const [authenticated, setAuthenticated] = React.useState<boolean>(true)
|
||||
const [fullscreen, setFullscreen] = React.useState<boolean>(false)
|
||||
|
||||
const fetchURL = `${serverURL}${apiRoute}${docEndpoint}${qs.stringify(
|
||||
{
|
||||
locale,
|
||||
draft,
|
||||
depth,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
)}`
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await fetch(fetchURL, {
|
||||
method: 'GET',
|
||||
credentials: authenticated ? 'include' : 'omit',
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const json = await res.json()
|
||||
setData(json)
|
||||
} catch (error) {
|
||||
toast.error('Error parsing response')
|
||||
console.error(error)
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Error making request')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
fetchData()
|
||||
}, [i18n.language, fetchURL, authenticated])
|
||||
|
||||
React.useEffect(() => {
|
||||
const editConfig = (collectionConfig || globalConfig)?.admin?.components?.views?.Edit
|
||||
const apiActions =
|
||||
editConfig && 'API' in editConfig && 'actions' in editConfig.API ? editConfig.API.actions : []
|
||||
|
||||
setViewActions(apiActions)
|
||||
|
||||
return () => {
|
||||
setViewActions([])
|
||||
}
|
||||
}, [collectionConfig, globalConfig, setViewActions])
|
||||
|
||||
return (
|
||||
<Gutter
|
||||
className={[baseClass, fullscreen && `${baseClass}--fullscreen`].filter(Boolean).join(' ')}
|
||||
right={false}
|
||||
>
|
||||
<SetStepNav
|
||||
collectionSlug={collectionSlug}
|
||||
useAsTitle={collectionConfig?.admin?.useAsTitle}
|
||||
pluralLabel={collectionConfig?.labels.plural}
|
||||
globalLabel={globalConfig?.label}
|
||||
globalSlug={globalSlug}
|
||||
id={id}
|
||||
isEditing={isEditing}
|
||||
view="API"
|
||||
/>
|
||||
<div className={`${baseClass}__configuration`}>
|
||||
<div className={`${baseClass}__api-url`}>
|
||||
<span className={`${baseClass}__label`}>
|
||||
API URL <CopyToClipboard value={fetchURL} />
|
||||
</span>
|
||||
<a href={fetchURL} rel="noopener noreferrer" target="_blank">
|
||||
{fetchURL}
|
||||
</a>
|
||||
</div>
|
||||
<Form
|
||||
initialState={{
|
||||
authenticated: {
|
||||
value: authenticated || false,
|
||||
initialValue: authenticated || false,
|
||||
valid: true,
|
||||
},
|
||||
draft: {
|
||||
value: draft || false,
|
||||
initialValue: draft || false,
|
||||
valid: true,
|
||||
},
|
||||
depth: {
|
||||
value: Number(depth || 0),
|
||||
initialValue: Number(depth || 0),
|
||||
valid: true,
|
||||
},
|
||||
locale: {
|
||||
value: locale,
|
||||
initialValue: locale,
|
||||
valid: true,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__form-fields`}>
|
||||
<div className={`${baseClass}__filter-query-checkboxes`}>
|
||||
{draftsEnabled && (
|
||||
<Checkbox
|
||||
name="draft"
|
||||
path="draft"
|
||||
label="Draft"
|
||||
onChange={() => setDraft(!draft)}
|
||||
/>
|
||||
)}
|
||||
<Checkbox
|
||||
name="authenticated"
|
||||
path="authenticated"
|
||||
label="Authenticated"
|
||||
onChange={() => setAuthenticated(!authenticated)}
|
||||
/>
|
||||
</div>
|
||||
{localeOptions && (
|
||||
<Select
|
||||
label="Locale"
|
||||
name="locale"
|
||||
options={localeOptions}
|
||||
path="locale"
|
||||
onChange={(value) => setLocale(value)}
|
||||
/>
|
||||
)}
|
||||
<NumberInput
|
||||
label="Depth"
|
||||
name="depth"
|
||||
path="depth"
|
||||
min={0}
|
||||
max={10}
|
||||
step={1}
|
||||
onChange={(value) => setDepth(value.toString())}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
<div className={`${baseClass}__results-wrapper`}>
|
||||
<div className={`${baseClass}__toggle-fullscreen-button-container`}>
|
||||
<button
|
||||
aria-label="toggle fullscreen"
|
||||
className={`${baseClass}__toggle-fullscreen-button`}
|
||||
onClick={() => setFullscreen(!fullscreen)}
|
||||
type="button"
|
||||
>
|
||||
<MinimizeMaximize isMinimized={!fullscreen} />
|
||||
</button>
|
||||
</div>
|
||||
<div className={`${baseClass}__results`}>
|
||||
<RenderJSON object={data} />
|
||||
</div>
|
||||
</div>
|
||||
</Gutter>
|
||||
)
|
||||
export const APIView: React.FC<ServerSideEditViewProps> = async (props) => {
|
||||
const clientSideProps = sanitizedEditViewProps(props)
|
||||
return <APIViewClient {...clientSideProps} />
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export const getCustomViewByPath = (
|
||||
): AdminViewComponent => {
|
||||
if (typeof views?.Edit === 'object' && typeof views?.Edit !== 'function') {
|
||||
const foundViewConfig = Object.entries(views.Edit).find(([, view]) => {
|
||||
if (typeof view === 'object' && typeof view !== 'function') {
|
||||
if (typeof view === 'object' && typeof view !== 'function' && 'path' in view) {
|
||||
return view.path === path
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -43,7 +43,7 @@ export const getViewsFromConfig = async ({
|
||||
if ('create' in docPermissions && docPermissions?.create?.permission) {
|
||||
CustomView = getCustomViewByKey(views, 'Default')
|
||||
DefaultView = lazy(() =>
|
||||
import('@payloadcms/ui').then((module) => ({ default: module.DefaultEditView })),
|
||||
import('../Edit/index.tsx').then((module) => ({ default: module.EditView })),
|
||||
)
|
||||
}
|
||||
break
|
||||
@@ -53,7 +53,7 @@ export const getViewsFromConfig = async ({
|
||||
if (docPermissions?.read?.permission) {
|
||||
CustomView = getCustomViewByKey(views, 'Default')
|
||||
DefaultView = lazy(() =>
|
||||
import('@payloadcms/ui').then((module) => ({ default: module.DefaultEditView })),
|
||||
import('../Edit/index.tsx').then((module) => ({ default: module.EditView })),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ export const getViewsFromConfig = async ({
|
||||
if (docPermissions?.read?.permission) {
|
||||
CustomView = getCustomViewByKey(views, 'Default')
|
||||
DefaultView = lazy(() =>
|
||||
import('@payloadcms/ui').then((module) => ({ default: module.DefaultEditView })),
|
||||
import('../Edit/index.tsx').then((module) => ({ default: module.EditView })),
|
||||
)
|
||||
}
|
||||
} else if (routeSegments?.length === 1) {
|
||||
@@ -161,7 +161,7 @@ export const getViewsFromConfig = async ({
|
||||
if (docPermissions?.read?.permission) {
|
||||
CustomView = getCustomViewByKey(views, 'Default')
|
||||
DefaultView = lazy(() =>
|
||||
import('@payloadcms/ui').then((module) => ({ default: module.DefaultEditView })),
|
||||
import('../Edit/index.tsx').then((module) => ({ default: module.EditView })),
|
||||
)
|
||||
}
|
||||
break
|
||||
|
||||
@@ -16,12 +16,12 @@ import {
|
||||
HydrateClientUser,
|
||||
DocumentInfoProvider,
|
||||
} from '@payloadcms/ui'
|
||||
import type { EditViewProps } from '@payloadcms/ui'
|
||||
import queryString from 'qs'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { AdminViewComponent } from 'payload/config'
|
||||
import { getViewsFromConfig } from './getViewsFromConfig'
|
||||
import type { DocumentPermissions } from 'payload/types'
|
||||
import { ServerSideEditViewProps } from '../../../../ui/src/views/types'
|
||||
|
||||
export const Document = async ({
|
||||
params,
|
||||
@@ -167,7 +167,7 @@ export const Document = async ({
|
||||
uploadEdits: undefined,
|
||||
}
|
||||
|
||||
const componentProps: EditViewProps = {
|
||||
const componentProps: ServerSideEditViewProps = {
|
||||
id,
|
||||
action: `${action}?${queryString.stringify(formQueryParams)}`,
|
||||
apiURL,
|
||||
@@ -183,6 +183,14 @@ export const Document = async ({
|
||||
updatedAt: data?.updatedAt?.toString(),
|
||||
user,
|
||||
locale,
|
||||
payload,
|
||||
config,
|
||||
searchParams,
|
||||
i18n,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
params,
|
||||
permissions,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
9
packages/next/src/pages/Edit/index.tsx
Normal file
9
packages/next/src/pages/Edit/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { DefaultEditView } from '@payloadcms/ui'
|
||||
import { ServerSideEditViewProps } from '../../../../ui/src/views/types'
|
||||
import { sanitizedEditViewProps } from './sanitizedEditViewProps'
|
||||
|
||||
export const EditView: React.FC<ServerSideEditViewProps> = async (props) => {
|
||||
const clientSideProps = sanitizedEditViewProps(props)
|
||||
return <DefaultEditView {...clientSideProps} />
|
||||
}
|
||||
13
packages/next/src/pages/Edit/sanitizedEditViewProps.ts
Normal file
13
packages/next/src/pages/Edit/sanitizedEditViewProps.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { EditViewProps, ServerSideEditViewProps } from '@payloadcms/ui'
|
||||
|
||||
export const sanitizedEditViewProps = (props: ServerSideEditViewProps) => {
|
||||
const clientSideProps = { ...props }
|
||||
delete clientSideProps.payload
|
||||
delete clientSideProps.config
|
||||
delete clientSideProps.searchParams
|
||||
delete clientSideProps.i18n
|
||||
delete clientSideProps.collectionConfig
|
||||
delete clientSideProps.globalConfig
|
||||
|
||||
return clientSideProps as EditViewProps
|
||||
}
|
||||
114
packages/next/src/pages/Version/Default/SetStepNav.tsx
Normal file
114
packages/next/src/pages/Version/Default/SetStepNav.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import {
|
||||
FieldMap,
|
||||
StepNavItem,
|
||||
formatDate,
|
||||
useConfig,
|
||||
useLocale,
|
||||
useStepNav,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { FieldAffectingData, SanitizedCollectionConfig } from 'payload/types'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
export const SetStepNav: React.FC<{
|
||||
collectionSlug?: string
|
||||
globalSlug?: string
|
||||
mostRecentDoc: any
|
||||
doc: any
|
||||
id?: string | number
|
||||
fieldMap: FieldMap
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
}> = ({ collectionSlug, globalSlug, mostRecentDoc, doc, id, fieldMap, collectionConfig }) => {
|
||||
const config = useConfig()
|
||||
const { setStepNav } = useStepNav()
|
||||
const { i18n, t } = useTranslation()
|
||||
const locale = useLocale()
|
||||
|
||||
useEffect(() => {
|
||||
let nav: StepNavItem[] = []
|
||||
|
||||
const {
|
||||
admin: { dateFormat },
|
||||
routes: { admin: adminRoute },
|
||||
} = config
|
||||
|
||||
if (collectionSlug) {
|
||||
let docLabel = ''
|
||||
|
||||
const useAsTitle = collectionConfig?.admin?.useAsTitle || 'id'
|
||||
const pluralLabel = collectionConfig?.labels?.plural
|
||||
|
||||
if (mostRecentDoc) {
|
||||
if (useAsTitle !== 'id') {
|
||||
const titleField = fieldMap.find(
|
||||
({ isFieldAffectingData, name: fieldName }) =>
|
||||
isFieldAffectingData && fieldName === useAsTitle,
|
||||
) as FieldAffectingData
|
||||
|
||||
if (titleField && mostRecentDoc[useAsTitle]) {
|
||||
if (titleField.localized) {
|
||||
docLabel = mostRecentDoc[useAsTitle]?.[locale.code]
|
||||
} else {
|
||||
docLabel = mostRecentDoc[useAsTitle]
|
||||
}
|
||||
} else {
|
||||
docLabel = `[${t('general:untitled')}]`
|
||||
}
|
||||
} else {
|
||||
docLabel = mostRecentDoc.id
|
||||
}
|
||||
}
|
||||
|
||||
nav = [
|
||||
{
|
||||
label: getTranslation(pluralLabel, i18n),
|
||||
url: `${adminRoute}/collections/${collectionSlug}`,
|
||||
},
|
||||
{
|
||||
label: docLabel,
|
||||
url: `${adminRoute}/collections/${collectionSlug}/${id}`,
|
||||
},
|
||||
{
|
||||
label: 'Versions',
|
||||
url: `${adminRoute}/collections/${collectionSlug}/${id}/versions`,
|
||||
},
|
||||
{
|
||||
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n?.language) : '',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (globalSlug) {
|
||||
nav = [
|
||||
{
|
||||
label: global.label,
|
||||
url: `${adminRoute}/globals/${global.slug}`,
|
||||
},
|
||||
{
|
||||
label: 'Versions',
|
||||
url: `${adminRoute}/globals/${global.slug}/versions`,
|
||||
},
|
||||
{
|
||||
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n?.language) : '',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
setStepNav(nav)
|
||||
}, [
|
||||
config,
|
||||
setStepNav,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
doc,
|
||||
mostRecentDoc,
|
||||
id,
|
||||
locale,
|
||||
t,
|
||||
i18n,
|
||||
collectionConfig,
|
||||
])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,15 +1,24 @@
|
||||
import React from 'react'
|
||||
import type { FieldAffectingData } from 'payload/types'
|
||||
import type { StepNavItem } from '@payloadcms/ui'
|
||||
import type { DefaultVersionsViewProps } from './types'
|
||||
import { fieldAffectsData } from 'payload/types'
|
||||
import { Gutter, SetStepNav, formatDate } from '@payloadcms/ui'
|
||||
import RenderFieldsToDiff from '../RenderFieldsToDiff'
|
||||
import fieldComponents from '../RenderFieldsToDiff/fields'
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import type { CompareOption, DefaultVersionsViewProps } from './types'
|
||||
import {
|
||||
Gutter,
|
||||
Option,
|
||||
formatDate,
|
||||
useComponentMap,
|
||||
useConfig,
|
||||
usePayloadAPI,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import Restore from '../Restore'
|
||||
import './index.scss'
|
||||
import { mostRecentVersionOption } from '../shared'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import diffComponents from '../RenderFieldsToDiff/fields'
|
||||
import RenderFieldsToDiff from '../RenderFieldsToDiff'
|
||||
import { SetStepNav } from './SetStepNav'
|
||||
import { SelectLocales } from '../SelectLocales'
|
||||
import { SelectComparison } from '../SelectComparison'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'view-version'
|
||||
|
||||
@@ -17,85 +26,38 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
doc,
|
||||
mostRecentDoc,
|
||||
publishedDoc,
|
||||
compareDoc,
|
||||
locales,
|
||||
initialComparisonDoc,
|
||||
localeOptions,
|
||||
docPermissions,
|
||||
fields,
|
||||
config,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
id,
|
||||
versionID,
|
||||
locale,
|
||||
i18n,
|
||||
}) => {
|
||||
const config = useConfig()
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const { getFieldMap } = useComponentMap()
|
||||
|
||||
const [fieldMap] = useState(() => getFieldMap({ collectionSlug, globalSlug }))
|
||||
|
||||
const [collectionConfig] = useState(() =>
|
||||
config.collections.find((collection) => collection.slug === collectionSlug),
|
||||
)
|
||||
|
||||
const [globalConfig] = useState(() => config.globals.find((global) => global.slug === globalSlug))
|
||||
|
||||
const [locales, setLocales] = useState<Option[]>(localeOptions)
|
||||
const [compareValue, setCompareValue] = useState<CompareOption>(mostRecentVersionOption)
|
||||
|
||||
const {
|
||||
routes: { admin },
|
||||
admin: { dateFormat },
|
||||
routes: { api: apiRoute },
|
||||
localization,
|
||||
serverURL,
|
||||
} = config
|
||||
|
||||
let nav: StepNavItem[] = []
|
||||
|
||||
if (collectionConfig) {
|
||||
let docLabel = ''
|
||||
|
||||
if (mostRecentDoc) {
|
||||
const { useAsTitle } = collectionConfig.admin
|
||||
|
||||
if (useAsTitle !== 'id') {
|
||||
const titleField = collectionConfig.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === useAsTitle,
|
||||
) as FieldAffectingData
|
||||
|
||||
if (titleField && mostRecentDoc[useAsTitle]) {
|
||||
if (titleField.localized) {
|
||||
docLabel = mostRecentDoc[useAsTitle]?.[locale]
|
||||
} else {
|
||||
docLabel = mostRecentDoc[useAsTitle]
|
||||
}
|
||||
} else {
|
||||
docLabel = `[${i18n.t('general:untitled')}]`
|
||||
}
|
||||
} else {
|
||||
docLabel = mostRecentDoc.id
|
||||
}
|
||||
}
|
||||
|
||||
nav = [
|
||||
{
|
||||
label: getTranslation(collectionConfig.labels.plural, i18n),
|
||||
url: `${admin}/collections/${collectionConfig.slug}`,
|
||||
},
|
||||
{
|
||||
label: docLabel,
|
||||
url: `${admin}/collections/${collectionConfig.slug}/${id}`,
|
||||
},
|
||||
{
|
||||
label: 'Versions',
|
||||
url: `${admin}/collections/${collectionConfig.slug}/${id}/versions`,
|
||||
},
|
||||
{
|
||||
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n.language) : '',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (globalConfig) {
|
||||
nav = [
|
||||
{
|
||||
label: globalConfig.label,
|
||||
url: `${admin}/globals/${globalConfig.slug}`,
|
||||
},
|
||||
{
|
||||
label: i18n.t('version:versions'),
|
||||
url: `${admin}/globals/${globalConfig.slug}/versions`,
|
||||
},
|
||||
{
|
||||
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n.language) : '',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// useEffect(() => {
|
||||
// const editConfig = (collectionConfig || globalConfig)?.admin?.components?.views?.Edit
|
||||
// const versionActions =
|
||||
@@ -110,30 +72,49 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
? formatDate(doc.createdAt, dateFormat, i18n.language)
|
||||
: ''
|
||||
|
||||
// TODO: this value should ultimately be dynamic based on the user's selection
|
||||
// This will come from URL params
|
||||
let compareValue = mostRecentVersionOption
|
||||
const originalDocFetchURL = `${serverURL}${apiRoute}${globalSlug ? 'globals/' : ''}/${
|
||||
collectionSlug || globalSlug
|
||||
}${collectionSlug ? `/${id}` : ''}`
|
||||
|
||||
let comparison = compareDoc?.version
|
||||
const compareBaseURL = `${serverURL}${apiRoute}/${globalSlug ? 'globals/' : ''}${
|
||||
collectionSlug || globalSlug
|
||||
}/versions`
|
||||
|
||||
if (compareValue?.value === 'mostRecent') {
|
||||
comparison = mostRecentDoc
|
||||
}
|
||||
const compareFetchURL =
|
||||
compareValue?.value === 'mostRecent' || compareValue?.value === 'published'
|
||||
? originalDocFetchURL
|
||||
: `${compareBaseURL}/${compareValue.value}`
|
||||
|
||||
if (compareValue?.value === 'published') {
|
||||
comparison = publishedDoc
|
||||
}
|
||||
const [{ data: currentComparisonDoc }] = usePayloadAPI(compareFetchURL, {
|
||||
initialParams: { depth: 1, draft: 'true', locale: '*' },
|
||||
initialData: initialComparisonDoc,
|
||||
})
|
||||
|
||||
const comparison =
|
||||
compareValue?.value === 'mostRecent'
|
||||
? mostRecentDoc
|
||||
: compareValue?.value === 'published'
|
||||
? publishedDoc
|
||||
: currentComparisonDoc?.version // the `version` key is only present on `versions` documents
|
||||
|
||||
const canUpdate = docPermissions?.update?.permission
|
||||
|
||||
return (
|
||||
<main className={baseClass}>
|
||||
<SetStepNav nav={nav} />
|
||||
<SetStepNav
|
||||
collectionSlug={collectionSlug}
|
||||
globalSlug={globalSlug}
|
||||
mostRecentDoc={mostRecentDoc}
|
||||
doc={doc}
|
||||
id={id}
|
||||
fieldMap={fieldMap}
|
||||
collectionConfig={collectionConfig}
|
||||
/>
|
||||
<Gutter className={`${baseClass}__wrap`}>
|
||||
<div className={`${baseClass}__header-wrap`}>
|
||||
<p className={`${baseClass}__created-at`}>
|
||||
{i18n.t('version:versionCreatedOn', {
|
||||
version: i18n.t(doc?.autosave ? 'autosavedVersion' : 'version'),
|
||||
version: i18n.t(doc?.autosave ? 'version:autosavedVersion' : 'version:version'),
|
||||
})}
|
||||
</p>
|
||||
<header className={`${baseClass}__header`}>
|
||||
@@ -141,8 +122,8 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
{canUpdate && (
|
||||
<Restore
|
||||
className={`${baseClass}__restore`}
|
||||
collectionSlug={collectionConfig?.slug}
|
||||
globalSlug={globalConfig?.slug}
|
||||
collectionSlug={collectionSlug}
|
||||
globalSlug={globalSlug}
|
||||
label={collectionConfig?.labels.singular || globalConfig?.label}
|
||||
originalDocID={id}
|
||||
versionDate={formattedCreatedAt}
|
||||
@@ -152,24 +133,23 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
</header>
|
||||
</div>
|
||||
<div className={`${baseClass}__controls`}>
|
||||
{/* <CompareVersion
|
||||
<SelectComparison
|
||||
baseURL={compareBaseURL}
|
||||
onChange={setCompareValue}
|
||||
parentID={parentID}
|
||||
parentID={id}
|
||||
publishedDoc={publishedDoc}
|
||||
value={compareValue}
|
||||
versionID={versionID}
|
||||
/> */}
|
||||
{/* {localization && (
|
||||
/>
|
||||
{localization && (
|
||||
<SelectLocales onChange={setLocales} options={localeOptions} value={locales} />
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
{doc?.version && (
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparison}
|
||||
fieldComponents={fieldComponents}
|
||||
fieldPermissions={docPermissions?.fields}
|
||||
fields={fields}
|
||||
fieldMap={fieldMap}
|
||||
locales={
|
||||
locales
|
||||
? locales.map(({ label }) => (typeof label === 'string' ? label : undefined))
|
||||
@@ -177,8 +157,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
}
|
||||
version={doc?.version}
|
||||
i18n={i18n}
|
||||
locale={locale}
|
||||
config={config}
|
||||
diffComponents={diffComponents}
|
||||
/>
|
||||
)}
|
||||
</Gutter>
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
import {
|
||||
CollectionPermission,
|
||||
FieldPermissions,
|
||||
GlobalPermission,
|
||||
Permissions,
|
||||
User,
|
||||
} from 'payload/auth'
|
||||
import {
|
||||
Document,
|
||||
Field,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedConfig,
|
||||
SanitizedGlobalConfig,
|
||||
} from 'payload/types'
|
||||
import { Option } from '@payloadcms/ui'
|
||||
import { CollectionPermission, GlobalPermission, Permissions, User } from 'payload/auth'
|
||||
|
||||
import { Document, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
export type CompareOption = {
|
||||
label: string
|
||||
@@ -25,17 +14,13 @@ export type DefaultVersionsViewProps = {
|
||||
doc: Document
|
||||
mostRecentDoc: Document
|
||||
publishedDoc: Document
|
||||
compareDoc: Document
|
||||
fields: Field[]
|
||||
locales: CompareOption[]
|
||||
initialComparisonDoc: Document
|
||||
localeOptions: Option[]
|
||||
user: User
|
||||
permissions: Permissions
|
||||
config: SanitizedConfig
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
id?: string
|
||||
id?: string | number
|
||||
versionID?: string
|
||||
docPermissions: CollectionPermission | GlobalPermission
|
||||
locale: string
|
||||
i18n: I18n
|
||||
collectionSlug?: SanitizedCollectionConfig['slug']
|
||||
globalSlug?: SanitizedCollectionConfig['slug']
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React from 'react'
|
||||
|
||||
import type { ArrayField, BlockField, Field } from 'payload/types'
|
||||
import type { Field } from 'payload/types'
|
||||
import type { Props } from '../types'
|
||||
|
||||
import RenderFieldsToDiff from '../..'
|
||||
import { fieldAffectsData } from 'payload/types'
|
||||
import { getUniqueListBy } from 'payload/utilities'
|
||||
import Label from '../../Label'
|
||||
import { MappedField } from '@payloadcms/ui'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'iterable-diff'
|
||||
|
||||
const Iterable: React.FC<Props & { field: ArrayField | BlockField }> = ({
|
||||
const Iterable: React.FC<Props> = ({
|
||||
comparison,
|
||||
field,
|
||||
fieldComponents,
|
||||
locale,
|
||||
locales,
|
||||
permissions,
|
||||
version,
|
||||
i18n,
|
||||
config,
|
||||
diffComponents,
|
||||
}) => {
|
||||
const versionRowCount = Array.isArray(version) ? version.length : 0
|
||||
const comparisonRowCount = Array.isArray(comparison) ? comparison.length : 0
|
||||
@@ -41,28 +41,30 @@ const Iterable: React.FC<Props & { field: ArrayField | BlockField }> = ({
|
||||
const versionRow = version?.[i] || {}
|
||||
const comparisonRow = comparison?.[i] || {}
|
||||
|
||||
let subFields: Field[] = []
|
||||
let subFields: MappedField[] = []
|
||||
|
||||
if (field.type === 'array') subFields = field.fields
|
||||
if (field.type === 'array') subFields = field.subfields
|
||||
|
||||
if (field.type === 'blocks') {
|
||||
subFields = [
|
||||
{
|
||||
name: 'blockType',
|
||||
label: i18n.t('fields:blockType'),
|
||||
type: 'text',
|
||||
},
|
||||
// {
|
||||
// name: 'blockType',
|
||||
// label: i18n.t('fields:blockType'),
|
||||
// type: 'text',
|
||||
// },
|
||||
]
|
||||
|
||||
if (versionRow?.blockType === comparisonRow?.blockType) {
|
||||
const matchedBlock = field.blocks.find(
|
||||
(block) => block.slug === versionRow?.blockType,
|
||||
) || { fields: [] }
|
||||
|
||||
subFields = [...subFields, ...matchedBlock.fields]
|
||||
} else {
|
||||
const matchedVersionBlock = field.blocks.find(
|
||||
(block) => block.slug === versionRow?.blockType,
|
||||
) || { fields: [] }
|
||||
|
||||
const matchedComparisonBlock = field.blocks.find(
|
||||
(block) => block.slug === comparisonRow?.blockType,
|
||||
) || { fields: [] }
|
||||
@@ -78,17 +80,12 @@ const Iterable: React.FC<Props & { field: ArrayField | BlockField }> = ({
|
||||
<div className={`${baseClass}__wrap`} key={i}>
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparisonRow}
|
||||
fieldComponents={fieldComponents}
|
||||
fieldMap={subFields}
|
||||
fieldPermissions={permissions}
|
||||
fields={subFields.filter(
|
||||
(subField) =>
|
||||
!(fieldAffectsData(subField) && 'name' in subField && subField.name === 'id'),
|
||||
)}
|
||||
locales={locales}
|
||||
version={versionRow}
|
||||
i18n={i18n}
|
||||
locale={locale}
|
||||
config={config}
|
||||
diffComponents={diffComponents}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
import type { FieldWithSubFields } from 'payload/types'
|
||||
import type { Props } from '../types'
|
||||
|
||||
import RenderFieldsToDiff from '../..'
|
||||
@@ -10,17 +9,17 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'nested-diff'
|
||||
|
||||
const Nested: React.FC<Props & { field: FieldWithSubFields }> = ({
|
||||
const Nested: React.FC<Props> = ({
|
||||
comparison,
|
||||
disableGutter = false,
|
||||
field,
|
||||
fieldComponents,
|
||||
locale,
|
||||
locales,
|
||||
permissions,
|
||||
version,
|
||||
i18n,
|
||||
config,
|
||||
fieldMap,
|
||||
diffComponents,
|
||||
}) => {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
@@ -37,14 +36,12 @@ const Nested: React.FC<Props & { field: FieldWithSubFields }> = ({
|
||||
>
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparison}
|
||||
fieldComponents={fieldComponents}
|
||||
fieldMap={fieldMap}
|
||||
fieldPermissions={permissions}
|
||||
fields={field.fields}
|
||||
locales={locales}
|
||||
version={version}
|
||||
i18n={i18n}
|
||||
locale={locale}
|
||||
config={config}
|
||||
diffComponents={diffComponents}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,7 +71,6 @@ const Relationship: React.FC<Props & { field: RelationshipField }> = ({
|
||||
version,
|
||||
i18n,
|
||||
locale,
|
||||
config: { collections },
|
||||
}) => {
|
||||
let placeholder = ''
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getTranslation, I18n } from '@payloadcms/translations'
|
||||
import React from 'react'
|
||||
import { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
import type { OptionObject, SelectField } from 'payload/types'
|
||||
import type { Props } from '../types'
|
||||
@@ -52,6 +51,7 @@ const Select: React.FC<Props> = ({ comparison, diffMethod, field, locale, versio
|
||||
typeof comparison !== 'undefined'
|
||||
? getTranslatedOptions(getOptionsToRender(comparison, field.options, field.hasMany), i18n)
|
||||
: placeholder
|
||||
|
||||
const versionToRender =
|
||||
typeof version !== 'undefined'
|
||||
? getTranslatedOptions(getOptionsToRender(version, field.options, field.hasMany), i18n)
|
||||
@@ -61,10 +61,10 @@ const Select: React.FC<Props> = ({ comparison, diffMethod, field, locale, versio
|
||||
<div className={baseClass}>
|
||||
<Label>
|
||||
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
|
||||
{getTranslation(field.label, i18n)}
|
||||
{getTranslation(field.label || '', i18n)}
|
||||
</Label>
|
||||
<DiffViewer
|
||||
diffMethod={diffMethod as DiffMethod}
|
||||
diffMethod={diffMethod}
|
||||
versionToRender={versionToRender}
|
||||
comparisonToRender={comparisonToRender}
|
||||
placeholder={placeholder}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { TabsField } from 'payload/types'
|
||||
import type { Props } from '../types'
|
||||
|
||||
import RenderFieldsToDiff from '../..'
|
||||
@@ -8,54 +7,53 @@ import Nested from '../Nested'
|
||||
|
||||
const baseClass = 'tabs-diff'
|
||||
|
||||
const Tabs: React.FC<Props & { field: TabsField }> = ({
|
||||
const Tabs: React.FC<Props> = ({
|
||||
comparison,
|
||||
field,
|
||||
fieldComponents,
|
||||
locales,
|
||||
permissions,
|
||||
version,
|
||||
i18n,
|
||||
config,
|
||||
locale,
|
||||
}) => (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
{field.tabs.map((tab, i) => {
|
||||
if ('name' in tab) {
|
||||
diffComponents,
|
||||
}) => {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
{field.tabs.map((tab, i) => {
|
||||
if ('name' in tab) {
|
||||
return (
|
||||
<Nested
|
||||
comparison={comparison?.[tab.name]}
|
||||
fieldMap={tab.subfields}
|
||||
key={i}
|
||||
locales={locales}
|
||||
permissions={permissions}
|
||||
version={version?.[tab.name]}
|
||||
i18n={i18n}
|
||||
locale={locale}
|
||||
field={field}
|
||||
diffComponents={diffComponents}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Nested
|
||||
comparison={comparison?.[tab.name]}
|
||||
field={tab}
|
||||
fieldComponents={fieldComponents}
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparison}
|
||||
fieldMap={tab.subfields}
|
||||
fieldPermissions={permissions}
|
||||
key={i}
|
||||
locales={locales}
|
||||
permissions={permissions}
|
||||
version={version?.[tab.name]}
|
||||
version={version}
|
||||
i18n={i18n}
|
||||
config={config}
|
||||
locale={locale}
|
||||
diffComponents={diffComponents}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparison}
|
||||
fieldComponents={fieldComponents}
|
||||
fieldPermissions={permissions}
|
||||
fields={tab.fields}
|
||||
key={i}
|
||||
locales={locales}
|
||||
version={version}
|
||||
i18n={i18n}
|
||||
config={config}
|
||||
locale={locale}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default Tabs
|
||||
|
||||
@@ -5,9 +5,10 @@ import type { Props } from '../types'
|
||||
|
||||
import Label from '../../Label'
|
||||
import { diffStyles } from '../styles'
|
||||
import './index.scss'
|
||||
import { DiffViewer } from './DiffViewer'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'text-diff'
|
||||
|
||||
const Text: React.FC<Props> = ({
|
||||
@@ -35,8 +36,7 @@ const Text: React.FC<Props> = ({
|
||||
<div className={baseClass}>
|
||||
<Label>
|
||||
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
|
||||
{typeof field.label === 'string' ? field.label : '[field-label]' /* TODO */}
|
||||
{getTranslation(field.label, i18n)}
|
||||
{getTranslation(field?.label || '', i18n)}
|
||||
</Label>
|
||||
<DiffViewer
|
||||
comparisonToRender={comparisonToRender}
|
||||
|
||||
@@ -18,12 +18,12 @@ export default {
|
||||
number: Text,
|
||||
point: Text,
|
||||
radio: Select,
|
||||
relationship: Relationship,
|
||||
relationship: null,
|
||||
richText: Text,
|
||||
row: Nested,
|
||||
select: Select,
|
||||
tabs: Tabs,
|
||||
text: Text,
|
||||
textarea: Text,
|
||||
upload: Relationship,
|
||||
upload: null,
|
||||
}
|
||||
|
||||
@@ -2,22 +2,22 @@ import type React from 'react'
|
||||
import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
import type { FieldPermissions } from 'payload/auth'
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import { FieldMap, MappedField } from '@payloadcms/ui'
|
||||
import { I18n } from '@payloadcms/translations/types'
|
||||
|
||||
export type FieldComponents = Record<string, React.FC<Props>>
|
||||
export type DiffComponents = Record<string, React.FC<Props>>
|
||||
|
||||
export type Props = {
|
||||
comparison: any
|
||||
diffMethod?: DiffMethod
|
||||
disableGutter?: boolean
|
||||
field: any
|
||||
fieldComponents: FieldComponents
|
||||
field: MappedField
|
||||
isRichText?: boolean
|
||||
locale: string
|
||||
locale?: string
|
||||
locales?: string[]
|
||||
permissions?: Record<string, FieldPermissions>
|
||||
version: any
|
||||
fieldMap: FieldMap
|
||||
i18n: I18n
|
||||
config: SanitizedConfig
|
||||
diffComponents: DiffComponents
|
||||
}
|
||||
|
||||
@@ -1,71 +1,84 @@
|
||||
'use client'
|
||||
import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
import type { Props, FieldDiffProps } from './types'
|
||||
|
||||
import { fieldAffectsData, fieldHasSubFields } from 'payload/types'
|
||||
import Nested from './fields/Nested'
|
||||
import { diffMethods } from './fields/diffMethods'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'render-field-diffs'
|
||||
|
||||
const RenderFieldsToDiff: React.FC<Props> = ({
|
||||
comparison,
|
||||
fieldComponents,
|
||||
fieldPermissions,
|
||||
fields,
|
||||
fieldMap,
|
||||
locales,
|
||||
version,
|
||||
i18n,
|
||||
locale,
|
||||
config,
|
||||
diffComponents,
|
||||
}) => {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{fields.map((field, i) => {
|
||||
const Component = fieldComponents[field.type]
|
||||
{fieldMap?.map((field, i) => {
|
||||
if (field.name === 'id') return null
|
||||
|
||||
const Component = diffComponents[field.type]
|
||||
|
||||
const isRichText = field.type === 'richText'
|
||||
const diffMethod: DiffMethod = diffMethods[field.type] || 'CHARS'
|
||||
|
||||
if (Component) {
|
||||
if (fieldAffectsData(field)) {
|
||||
if (field.isFieldAffectingData) {
|
||||
const valueIsObject = field.type === 'code' || field.type === 'json'
|
||||
|
||||
const versionValue = valueIsObject
|
||||
? JSON.stringify(version?.[field.name])
|
||||
: version?.[field.name]
|
||||
|
||||
const comparisonValue = valueIsObject
|
||||
? JSON.stringify(comparison?.[field.name])
|
||||
: comparison?.[field.name]
|
||||
|
||||
const hasPermission = fieldPermissions?.[field.name]?.read?.permission
|
||||
|
||||
const subFieldPermissions = fieldPermissions?.[field.name]?.fields
|
||||
|
||||
if (hasPermission === false) return null
|
||||
|
||||
const baseCellProps: FieldDiffProps = {
|
||||
diffMethod,
|
||||
field,
|
||||
isRichText,
|
||||
locales: locales,
|
||||
fieldPermissions: subFieldPermissions,
|
||||
i18n,
|
||||
fieldMap: 'subfields' in field ? field.subfields : fieldMap,
|
||||
diffComponents,
|
||||
comparison: comparisonValue,
|
||||
version: versionValue,
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
return (
|
||||
<div className={`${baseClass}__field`} key={i}>
|
||||
{locales.map((locale, index) => {
|
||||
const versionLocaleValue = versionValue?.[locale]
|
||||
const comparisonLocaleValue = comparisonValue?.[locale]
|
||||
|
||||
const cellProps = {
|
||||
...baseCellProps,
|
||||
version: versionLocaleValue,
|
||||
comparison: comparisonLocaleValue,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__locale`} key={[locale, index].join('-')}>
|
||||
<div className={`${baseClass}__locale-value`}>
|
||||
<Component
|
||||
comparison={comparisonLocaleValue}
|
||||
diffMethod={diffMethod}
|
||||
field={field}
|
||||
fieldComponents={fieldComponents}
|
||||
isRichText={isRichText}
|
||||
locale={locale}
|
||||
locales={locales}
|
||||
permissions={subFieldPermissions}
|
||||
version={versionLocaleValue}
|
||||
i18n={i18n}
|
||||
config={config}
|
||||
/>
|
||||
<Component {...cellProps} locale={locale} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -76,56 +89,42 @@ const RenderFieldsToDiff: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__field`} key={i}>
|
||||
<Component
|
||||
comparison={comparisonValue}
|
||||
diffMethod={diffMethod}
|
||||
field={field}
|
||||
fieldComponents={fieldComponents}
|
||||
isRichText={isRichText}
|
||||
locales={locales}
|
||||
permissions={subFieldPermissions}
|
||||
version={versionValue}
|
||||
i18n={i18n}
|
||||
locale={locale}
|
||||
config={config}
|
||||
/>
|
||||
<Component {...baseCellProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (field.type === 'tabs') {
|
||||
const Tabs = fieldComponents.tabs
|
||||
const Tabs = diffComponents.tabs
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
comparison={comparison}
|
||||
field={field}
|
||||
fieldComponents={fieldComponents}
|
||||
key={i}
|
||||
locales={locales}
|
||||
version={version}
|
||||
i18n={i18n}
|
||||
locale={locale}
|
||||
config={config}
|
||||
field={field}
|
||||
fieldMap={field.subfields}
|
||||
diffComponents={diffComponents}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// At this point, we are dealing with a `row` or similar
|
||||
if (fieldHasSubFields(field)) {
|
||||
// At this point, we are dealing with a `row`, etc
|
||||
if (field.subfields) {
|
||||
return (
|
||||
<Nested
|
||||
comparison={comparison}
|
||||
disableGutter
|
||||
field={field}
|
||||
fieldComponents={fieldComponents}
|
||||
fieldMap={field.subfields}
|
||||
key={i}
|
||||
locales={locales}
|
||||
permissions={fieldPermissions}
|
||||
version={version}
|
||||
i18n={i18n}
|
||||
locale={locale}
|
||||
config={config}
|
||||
diffComponents={diffComponents}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import type { FieldPermissions } from 'payload/auth'
|
||||
import type { Field, SanitizedConfig } from 'payload/types'
|
||||
import type { FieldComponents } from './fields/types'
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import { FieldMap, MappedField } from '@payloadcms/ui'
|
||||
import { I18n } from '@payloadcms/translations/types'
|
||||
import { DiffComponents } from './fields/types'
|
||||
import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
export type Props = {
|
||||
comparison: Record<string, any>
|
||||
fieldComponents: FieldComponents
|
||||
fieldPermissions: Record<string, FieldPermissions>
|
||||
fields: Field[]
|
||||
fieldMap: FieldMap
|
||||
locales: string[]
|
||||
version: Record<string, any>
|
||||
i18n: I18n
|
||||
config: SanitizedConfig
|
||||
locale: string
|
||||
diffComponents: DiffComponents
|
||||
}
|
||||
|
||||
export type FieldDiffProps = Props & {
|
||||
field: MappedField
|
||||
diffMethod: DiffMethod
|
||||
isRichText: boolean
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export type Props = {
|
||||
collectionSlug?: SanitizedCollectionConfig['slug']
|
||||
globalSlug?: SanitizedGlobalConfig['slug']
|
||||
label: SanitizedCollectionConfig['labels']['singular'] | SanitizedGlobalConfig['label']
|
||||
originalDocID: string
|
||||
originalDocID: string | number
|
||||
versionDate: string
|
||||
versionID: string
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const maxResultsPerRequest = 10
|
||||
|
||||
const baseOptions = [mostRecentVersionOption]
|
||||
|
||||
const CompareVersion: React.FC<Props> = (props) => {
|
||||
export const SelectComparison: React.FC<Props> = (props) => {
|
||||
const { baseURL, onChange, parentID, publishedDoc, value, versionID } = props
|
||||
|
||||
const {
|
||||
@@ -58,6 +58,7 @@ const CompareVersion: React.FC<Props> = (props) => {
|
||||
}
|
||||
|
||||
const search = qs.stringify(query)
|
||||
|
||||
const response = await fetch(`${baseURL}?${search}`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
@@ -67,6 +68,7 @@ const CompareVersion: React.FC<Props> = (props) => {
|
||||
|
||||
if (response.ok) {
|
||||
const data: PaginatedDocs = await response.json()
|
||||
|
||||
if (data.docs.length > 0) {
|
||||
setOptions((existingOptions) => [
|
||||
...existingOptions,
|
||||
@@ -75,6 +77,7 @@ const CompareVersion: React.FC<Props> = (props) => {
|
||||
value: doc.id,
|
||||
})),
|
||||
])
|
||||
|
||||
setLastLoadedPage(data.page)
|
||||
}
|
||||
} else {
|
||||
@@ -117,5 +120,3 @@ const CompareVersion: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CompareVersion
|
||||
@@ -5,7 +5,7 @@ import type { CompareOption } from '../Default/types'
|
||||
export type Props = {
|
||||
baseURL: string
|
||||
onChange: (val: CompareOption) => void
|
||||
parentID?: string
|
||||
parentID?: string | number
|
||||
publishedDoc: any
|
||||
value: CompareOption
|
||||
versionID: string
|
||||
@@ -8,7 +8,7 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'select-version-locales'
|
||||
|
||||
const SelectLocales: React.FC<Props> = ({ onChange, options, value }) => {
|
||||
export const SelectLocales: React.FC<Props> = ({ onChange, options, value }) => {
|
||||
const { t } = useTranslation()
|
||||
const { code } = useLocale()
|
||||
|
||||
@@ -37,5 +37,3 @@ const SelectLocales: React.FC<Props> = ({ onChange, options, value }) => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SelectLocales
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
|
||||
import { DefaultVersionView } from './Default'
|
||||
import { Document, Field } from 'payload/types'
|
||||
import type { EditViewProps } from '@payloadcms/ui'
|
||||
import { Document } from 'payload/types'
|
||||
import type { Option, ServerSideEditViewProps } from '@payloadcms/ui'
|
||||
import { CollectionPermission, GlobalPermission } from 'payload/auth'
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
export const VersionView: React.FC<EditViewProps> = async (props) => {
|
||||
const { config, permissions, payload, user, params, i18n } = props
|
||||
export const VersionView: React.FC<ServerSideEditViewProps> = async (props) => {
|
||||
const { config, permissions, payload, user, params } = props
|
||||
|
||||
const versionID = params.segments[2]
|
||||
|
||||
@@ -21,17 +21,14 @@ export const VersionView: React.FC<EditViewProps> = async (props) => {
|
||||
const { localization } = config
|
||||
|
||||
let docPermissions: CollectionPermission | GlobalPermission
|
||||
let fields: Field[]
|
||||
let slug: string
|
||||
|
||||
let doc: Document
|
||||
let publishedDoc: Document
|
||||
let mostRecentDoc: Document
|
||||
let compareDoc: Document
|
||||
|
||||
if (collectionSlug) {
|
||||
slug = collectionSlug
|
||||
fields = collectionConfig.fields
|
||||
docPermissions = permissions.collections[collectionSlug]
|
||||
|
||||
try {
|
||||
@@ -57,16 +54,6 @@ export const VersionView: React.FC<EditViewProps> = async (props) => {
|
||||
draft: true,
|
||||
locale: '*',
|
||||
})
|
||||
|
||||
// TODO: this `id` will be dynamic based on the user's selection
|
||||
// Use URL params to achieve this
|
||||
compareDoc = await payload.findByID({
|
||||
collection: slug,
|
||||
id,
|
||||
depth: 1,
|
||||
draft: true,
|
||||
locale: '*',
|
||||
})
|
||||
} catch (error) {
|
||||
return notFound()
|
||||
}
|
||||
@@ -74,7 +61,6 @@ export const VersionView: React.FC<EditViewProps> = async (props) => {
|
||||
|
||||
if (globalSlug) {
|
||||
slug = globalSlug
|
||||
fields = globalConfig.fields
|
||||
docPermissions = permissions.globals[globalSlug]
|
||||
|
||||
try {
|
||||
@@ -98,26 +84,12 @@ export const VersionView: React.FC<EditViewProps> = async (props) => {
|
||||
draft: true,
|
||||
locale: '*',
|
||||
})
|
||||
|
||||
// TODO: this `slug` will be dynamic based on the user's selection
|
||||
// Use URL params to achieve this
|
||||
compareDoc = payload.findGlobal({
|
||||
slug,
|
||||
depth: 1,
|
||||
draft: true,
|
||||
locale: '*',
|
||||
})
|
||||
} catch (error) {
|
||||
return notFound()
|
||||
}
|
||||
}
|
||||
|
||||
// const compareFetchURL =
|
||||
// compareValue?.value === 'mostRecent' || compareValue?.value === 'published'
|
||||
// ? originalDocFetchURL
|
||||
// : `${compareBaseURL}/${compareValue.value}`
|
||||
|
||||
const locales =
|
||||
const localeOptions: Option[] =
|
||||
localization &&
|
||||
localization?.locales &&
|
||||
localization.locales.map(({ code, label }) => ({
|
||||
@@ -131,13 +103,11 @@ export const VersionView: React.FC<EditViewProps> = async (props) => {
|
||||
|
||||
return (
|
||||
<DefaultVersionView
|
||||
collectionConfig={collectionConfig}
|
||||
compareDoc={compareDoc}
|
||||
config={config}
|
||||
collectionSlug={collectionSlug}
|
||||
globalSlug={globalSlug}
|
||||
initialComparisonDoc={mostRecentDoc}
|
||||
doc={doc}
|
||||
fields={fields}
|
||||
globalConfig={globalConfig}
|
||||
locales={locales}
|
||||
localeOptions={localeOptions}
|
||||
mostRecentDoc={mostRecentDoc}
|
||||
id={id}
|
||||
permissions={permissions}
|
||||
@@ -145,8 +115,6 @@ export const VersionView: React.FC<EditViewProps> = async (props) => {
|
||||
user={user}
|
||||
versionID={versionID}
|
||||
docPermissions={docPermissions}
|
||||
locale="" // TODO
|
||||
i18n={i18n}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
66
packages/next/src/pages/Versions/buildColumns.tsx
Normal file
66
packages/next/src/pages/Versions/buildColumns.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
|
||||
import type {
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedConfig,
|
||||
SanitizedGlobalConfig,
|
||||
} from 'payload/types'
|
||||
|
||||
import type { Column } from '@payloadcms/ui'
|
||||
import { SortColumn } from '@payloadcms/ui'
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
import { CreatedAtCell } from './cells/CreatedAt'
|
||||
import { IDCell } from './cells/ID'
|
||||
import { AutosaveCell } from './cells/AutosaveCell'
|
||||
|
||||
export const buildVersionColumns = ({
|
||||
config,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
docID,
|
||||
i18n: { t },
|
||||
i18n,
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
docID?: string | number
|
||||
i18n: I18n
|
||||
}): Column[] => [
|
||||
{
|
||||
name: '',
|
||||
accessor: 'updatedAt',
|
||||
active: true,
|
||||
components: {
|
||||
Heading: <SortColumn label={t('general:updatedAt')} name="updatedAt" />,
|
||||
Cell: (
|
||||
<CreatedAtCell
|
||||
docID={docID}
|
||||
collectionSlug={collectionConfig?.slug}
|
||||
globalSlug={globalConfig?.slug}
|
||||
/>
|
||||
),
|
||||
},
|
||||
label: '',
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
accessor: 'id',
|
||||
active: true,
|
||||
components: {
|
||||
Heading: <SortColumn disable label={t('version:versionID')} name="id" />,
|
||||
Cell: <IDCell />,
|
||||
},
|
||||
label: '',
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
accessor: 'autosave',
|
||||
active: true,
|
||||
components: {
|
||||
Heading: <SortColumn disable label={t('version:type')} name="autosave" />,
|
||||
Cell: <AutosaveCell />,
|
||||
},
|
||||
label: '',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
'use client'
|
||||
import React, { Fragment } from 'react'
|
||||
import { useTranslation, useTableCell, Pill } from '@payloadcms/ui'
|
||||
|
||||
export const AutosaveCell: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { rowData } = useTableCell()
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{rowData?.autosave && (
|
||||
<React.Fragment>
|
||||
<Pill>
|
||||
Autosave
|
||||
{t('version:autosave')}
|
||||
</Pill>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
{rowData?.version._status === 'published' && (
|
||||
<React.Fragment>
|
||||
<Pill pillStyle="success">{t('version:published')}</Pill>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
{rowData?.version._status === 'draft' && <Pill>{t('version:draft')}</Pill>}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
35
packages/next/src/pages/Versions/cells/CreatedAt/index.tsx
Normal file
35
packages/next/src/pages/Versions/cells/CreatedAt/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { formatDate, useConfig, useTranslation, useTableCell } from '@payloadcms/ui'
|
||||
import Link from 'next/link'
|
||||
|
||||
type CreatedAtCellProps = {
|
||||
collectionSlug?: string
|
||||
globalSlug?: string
|
||||
docID?: string | number
|
||||
}
|
||||
|
||||
export const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
docID,
|
||||
}) => {
|
||||
const {
|
||||
routes: { admin },
|
||||
admin: { dateFormat },
|
||||
} = useConfig()
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const { cellData, rowData } = useTableCell()
|
||||
|
||||
const versionID = rowData.id
|
||||
|
||||
let to: string
|
||||
|
||||
if (collectionSlug) to = `${admin}/collections/${collectionSlug}/${docID}/versions/${versionID}`
|
||||
|
||||
if (globalSlug) to = `${admin}/globals/${globalSlug}/versions/${versionID}`
|
||||
|
||||
return <Link href={to}>{cellData && formatDate(cellData, dateFormat, i18n.language)}</Link>
|
||||
}
|
||||
8
packages/next/src/pages/Versions/cells/ID/index.tsx
Normal file
8
packages/next/src/pages/Versions/cells/ID/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
'use client'
|
||||
import React, { Fragment } from 'react'
|
||||
import { useTableCell } from '@payloadcms/ui'
|
||||
|
||||
export const IDCell: React.FC = () => {
|
||||
const { cellData } = useTableCell()
|
||||
return <Fragment>{cellData}</Fragment>
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import type {
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedConfig,
|
||||
SanitizedGlobalConfig,
|
||||
} from 'payload/types'
|
||||
|
||||
import type { Column } from '@payloadcms/ui'
|
||||
import { Pill, formatDate, SortColumn } from '@payloadcms/ui'
|
||||
import Link from 'next/link'
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
|
||||
type CreatedAtCellProps = {
|
||||
config: SanitizedConfig
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
date: string
|
||||
versionID: string
|
||||
docID: string
|
||||
i18n: I18n
|
||||
}
|
||||
|
||||
const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
|
||||
versionID,
|
||||
docID,
|
||||
config,
|
||||
collectionConfig,
|
||||
date,
|
||||
globalConfig,
|
||||
i18n,
|
||||
}) => {
|
||||
const {
|
||||
routes: { admin },
|
||||
admin: { dateFormat },
|
||||
} = config
|
||||
|
||||
let to: string
|
||||
|
||||
if (collectionConfig)
|
||||
to = `${admin}/collections/${collectionConfig.slug}/${docID}/versions/${versionID}`
|
||||
if (globalConfig) to = `${admin}/globals/${globalConfig.slug}/versions/${versionID}`
|
||||
|
||||
return <Link href={to}>{date && formatDate(date, dateFormat, i18n.language)}</Link>
|
||||
}
|
||||
|
||||
const TextCell: React.FC<{ children?: React.ReactNode }> = ({ children }) => <span>{children}</span>
|
||||
|
||||
export const buildVersionColumns = ({
|
||||
config,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
docID,
|
||||
i18n: { t },
|
||||
i18n,
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
docID?: string
|
||||
i18n: I18n
|
||||
}): Column[] => [
|
||||
{
|
||||
name: '',
|
||||
accessor: 'updatedAt',
|
||||
active: true,
|
||||
components: {
|
||||
Heading: <SortColumn label={t('general:updatedAt')} name="updatedAt" />,
|
||||
renderCell: (row, data) => (
|
||||
<CreatedAtCell
|
||||
config={config}
|
||||
collectionConfig={collectionConfig}
|
||||
date={data}
|
||||
globalConfig={globalConfig}
|
||||
versionID={row?.id}
|
||||
docID={docID}
|
||||
i18n={i18n}
|
||||
/>
|
||||
),
|
||||
},
|
||||
label: '',
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
accessor: 'id',
|
||||
active: true,
|
||||
components: {
|
||||
Heading: <SortColumn disable label={t('version:versionID')} name="id" />,
|
||||
renderCell: (row, data) => <TextCell>{data}</TextCell>,
|
||||
},
|
||||
label: '',
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
accessor: 'autosave',
|
||||
active: true,
|
||||
components: {
|
||||
Heading: <SortColumn disable label={t('version:type')} name="autosave" />,
|
||||
renderCell: (row) => (
|
||||
<TextCell>
|
||||
{row?.autosave && (
|
||||
<React.Fragment>
|
||||
<Pill>
|
||||
Autosave
|
||||
{t('version:autosave')}
|
||||
</Pill>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
{row?.version._status === 'published' && (
|
||||
<React.Fragment>
|
||||
<Pill pillStyle="success">{t('version:published')}</Pill>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
{row?.version._status === 'draft' && <Pill>{t('version:draft')}</Pill>}
|
||||
</TextCell>
|
||||
),
|
||||
},
|
||||
label: '',
|
||||
},
|
||||
]
|
||||
132
packages/next/src/pages/Versions/index.client.tsx
Normal file
132
packages/next/src/pages/Versions/index.client.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
'use client'
|
||||
import {
|
||||
Column,
|
||||
LoadingOverlayToggle,
|
||||
Pagination,
|
||||
PerPage,
|
||||
Table,
|
||||
usePayloadAPI,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { PaginatedDocs } from 'payload/database'
|
||||
import { SanitizedCollectionConfig } from 'payload/types'
|
||||
import React, { Fragment, useEffect, useRef } from 'react'
|
||||
|
||||
export const VersionsViewClient: React.FC<{
|
||||
initialData: PaginatedDocs
|
||||
columns: Column[]
|
||||
baseClass: string
|
||||
fetchURL: string
|
||||
collectionSlug?: string
|
||||
globalSlug?: string
|
||||
id?: string | number
|
||||
paginationLimits?: SanitizedCollectionConfig['admin']['pagination']['limits']
|
||||
}> = (props) => {
|
||||
const { initialData, columns, baseClass, collectionSlug, fetchURL, id, paginationLimits } = props
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const limit = searchParams.get('limit')
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const [{ data, isLoading }, { setParams }] = usePayloadAPI(fetchURL, {
|
||||
initialData,
|
||||
initialParams: {
|
||||
depth: 1,
|
||||
limit,
|
||||
page: undefined,
|
||||
sort: undefined,
|
||||
where: {
|
||||
parent: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const hasInitialized = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData && !hasInitialized.current) {
|
||||
hasInitialized.current = true
|
||||
return
|
||||
}
|
||||
|
||||
const page = searchParams.get('page')
|
||||
const sort = searchParams.get('sort')
|
||||
|
||||
const params = {
|
||||
depth: 1,
|
||||
limit,
|
||||
page: undefined,
|
||||
sort: undefined,
|
||||
where: {},
|
||||
}
|
||||
|
||||
if (page) params.page = page
|
||||
if (sort) params.sort = sort
|
||||
|
||||
if (collectionSlug) {
|
||||
params.where = {
|
||||
parent: {
|
||||
equals: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
setParams(params)
|
||||
}, [id, collectionSlug, searchParams, limit])
|
||||
|
||||
// useEffect(() => {
|
||||
// const editConfig = (collection || global)?.admin?.components?.views?.Edit
|
||||
// const versionsActions =
|
||||
// editConfig && 'Versions' in editConfig && 'actions' in editConfig.Versions
|
||||
// ? editConfig.Versions.actions
|
||||
// : []
|
||||
|
||||
// setViewActions(versionsActions)
|
||||
// }, [collection, global, setViewActions])
|
||||
|
||||
const versionCount = data?.totalDocs || 0
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<LoadingOverlayToggle name="versions" show={isLoading} />
|
||||
{versionCount === 0 && (
|
||||
<div className={`${baseClass}__no-versions`}>
|
||||
{i18n.t('version:noFurtherVersionsFound')}
|
||||
</div>
|
||||
)}
|
||||
{versionCount > 0 && (
|
||||
<React.Fragment>
|
||||
<Table columns={columns} data={data?.docs} />
|
||||
<div className={`${baseClass}__page-controls`}>
|
||||
<Pagination
|
||||
hasNextPage={data.hasNextPage}
|
||||
hasPrevPage={data.hasPrevPage}
|
||||
limit={data.limit}
|
||||
nextPage={data.nextPage}
|
||||
numberOfNeighbors={1}
|
||||
page={data.page}
|
||||
prevPage={data.prevPage}
|
||||
totalPages={data.totalPages}
|
||||
/>
|
||||
{data?.totalDocs > 0 && (
|
||||
<React.Fragment>
|
||||
<div className={`${baseClass}__page-info`}>
|
||||
{data.page * data.limit - (data.limit - 1)}-
|
||||
{data.totalPages > 1 && data.totalPages !== data.page
|
||||
? data.limit * data.page
|
||||
: data.totalDocs}{' '}
|
||||
{i18n.t('general:of')} {data.totalDocs}
|
||||
</div>
|
||||
<PerPage limit={limit ? Number(limit) : 10} limits={paginationLimits} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
@@ -1,23 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
import {
|
||||
Gutter,
|
||||
Pagination,
|
||||
PerPage,
|
||||
Table,
|
||||
SetDocumentStepNav as SetStepNav,
|
||||
EditViewProps,
|
||||
} from '@payloadcms/ui'
|
||||
import { buildVersionColumns } from './columns'
|
||||
import './index.scss'
|
||||
|
||||
import { Gutter, SetDocumentStepNav as SetStepNav, ServerSideEditViewProps } from '@payloadcms/ui'
|
||||
import { buildVersionColumns } from './buildColumns'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { VersionsViewClient } from './index.client'
|
||||
|
||||
const baseClass = 'versions'
|
||||
import './index.scss'
|
||||
|
||||
export const VersionsView: React.FC<EditViewProps> = async (props) => {
|
||||
const { config, searchParams, payload, user, i18n } = props
|
||||
export const baseClass = 'versions'
|
||||
|
||||
export const VersionsView: React.FC<ServerSideEditViewProps> = async (props) => {
|
||||
const { user, payload, config, searchParams, i18n } = props
|
||||
|
||||
const id = 'id' in props ? props.id : undefined
|
||||
const collectionConfig = 'collectionConfig' in props && props?.collectionConfig
|
||||
@@ -28,7 +22,7 @@ export const VersionsView: React.FC<EditViewProps> = async (props) => {
|
||||
const { limit, page, sort } = searchParams
|
||||
|
||||
const {
|
||||
routes: { admin, api },
|
||||
routes: { admin: adminRoute, api: apiRoute },
|
||||
serverURL,
|
||||
} = config
|
||||
|
||||
@@ -44,23 +38,22 @@ export const VersionsView: React.FC<EditViewProps> = async (props) => {
|
||||
collection: collectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
page: page ? parseInt(page as string, 10) : undefined,
|
||||
page: page ? parseInt(page.toString(), 10) : undefined,
|
||||
sort: sort as string,
|
||||
// TODO: why won't this work?!
|
||||
// throws an `unsupported BSON` error
|
||||
// where: {
|
||||
// parent: {
|
||||
// equals: id,
|
||||
// },
|
||||
// },
|
||||
limit: limit ? parseInt(limit?.toString(), 10) : undefined,
|
||||
where: {
|
||||
parent: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
docURL = `${serverURL}${api}/${slug}/${id}`
|
||||
docURL = `${serverURL}${apiRoute}/${slug}/${id}`
|
||||
entityLabel = getTranslation(collectionConfig.labels.singular, i18n)
|
||||
editURL = `${admin}/collections/${collectionSlug}/${id}`
|
||||
editURL = `${adminRoute}/collections/${collectionSlug}/${id}`
|
||||
}
|
||||
|
||||
if (globalSlug) {
|
||||
@@ -85,22 +78,24 @@ export const VersionsView: React.FC<EditViewProps> = async (props) => {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
docURL = `${serverURL}${api}/globals/${globalSlug}`
|
||||
docURL = `${serverURL}${apiRoute}/globals/${globalSlug}`
|
||||
entityLabel = getTranslation(globalConfig.label, i18n)
|
||||
editURL = `${admin}/globals/${globalSlug}`
|
||||
editURL = `${adminRoute}/globals/${globalSlug}`
|
||||
}
|
||||
|
||||
// useEffect(() => {
|
||||
// const editConfig = (collection || global)?.admin?.components?.views?.Edit
|
||||
// const versionsActions =
|
||||
// editConfig && 'Versions' in editConfig && 'actions' in editConfig.Versions
|
||||
// ? editConfig.Versions.actions
|
||||
// : []
|
||||
const columns = buildVersionColumns({
|
||||
config,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
docID: id,
|
||||
i18n,
|
||||
})
|
||||
|
||||
// setViewActions(versionsActions)
|
||||
// }, [collection, global, setViewActions])
|
||||
|
||||
const versionCount = versionsData?.totalDocs || 0
|
||||
const fetchURL = collectionSlug
|
||||
? `${serverURL}${apiRoute}/${collectionSlug}/versions`
|
||||
: globalSlug
|
||||
? `${serverURL}${apiRoute}/globals/${globalSlug}/versions`
|
||||
: ''
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -112,64 +107,18 @@ export const VersionsView: React.FC<EditViewProps> = async (props) => {
|
||||
pluralLabel={collectionConfig?.labels?.plural}
|
||||
view={i18n.t('version:versions')}
|
||||
/>
|
||||
{/* <LoadingOverlayToggle name="versions" show={isLoadingVersions} /> */}
|
||||
<main className={baseClass}>
|
||||
{/* <Meta description={metaDesc} title={metaTitle} /> */}
|
||||
<Gutter className={`${baseClass}__wrap`}>
|
||||
{versionCount === 0 && (
|
||||
<div className={`${baseClass}__no-versions`}>
|
||||
{i18n.t('version:noFurtherVersionsFound')}
|
||||
</div>
|
||||
)}
|
||||
{versionCount > 0 && (
|
||||
<React.Fragment>
|
||||
<div className={`${baseClass}__version-count`}>
|
||||
{i18n.t(
|
||||
versionCount === 1 ? 'version:versionCount_one' : 'version:versionCount_many',
|
||||
{
|
||||
count: versionCount,
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
<Table
|
||||
columns={buildVersionColumns({
|
||||
config,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
docID: id,
|
||||
i18n,
|
||||
})}
|
||||
data={versionsData?.docs}
|
||||
/>
|
||||
<div className={`${baseClass}__page-controls`}>
|
||||
<Pagination
|
||||
hasNextPage={versionsData.hasNextPage}
|
||||
hasPrevPage={versionsData.hasPrevPage}
|
||||
limit={versionsData.limit}
|
||||
nextPage={versionsData.nextPage}
|
||||
numberOfNeighbors={1}
|
||||
page={versionsData.page}
|
||||
prevPage={versionsData.prevPage}
|
||||
totalPages={versionsData.totalPages}
|
||||
/>
|
||||
{versionsData?.totalDocs > 0 && (
|
||||
<React.Fragment>
|
||||
<div className={`${baseClass}__page-info`}>
|
||||
{versionsData.page * versionsData.limit - (versionsData.limit - 1)}-
|
||||
{versionsData.totalPages > 1 && versionsData.totalPages !== versionsData.page
|
||||
? versionsData.limit * versionsData.page
|
||||
: versionsData.totalDocs}{' '}
|
||||
{i18n.t('general:of')} {versionsData.totalDocs}
|
||||
</div>
|
||||
<PerPage
|
||||
limit={limit ? Number(limit) : 10}
|
||||
limits={collectionConfig?.admin?.pagination?.limits}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<VersionsViewClient
|
||||
initialData={versionsData}
|
||||
columns={columns}
|
||||
fetchURL={fetchURL}
|
||||
baseClass={baseClass}
|
||||
collectionSlug={collectionSlug}
|
||||
globalSlug={globalSlug}
|
||||
id={id}
|
||||
paginationLimits={collectionConfig?.admin?.pagination?.limits}
|
||||
/>
|
||||
</Gutter>
|
||||
</main>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -4,7 +4,6 @@ import type {
|
||||
SanitizedGlobalConfig,
|
||||
} from 'payload/types'
|
||||
import type { PaginatedDocs } from 'payload/database'
|
||||
import type { Version } from '@payloadcms/ui'
|
||||
import { User } from 'payload/auth'
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
|
||||
@@ -13,11 +12,11 @@ export type DefaultVersionsViewProps = {
|
||||
config: SanitizedConfig
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
data: Version
|
||||
versionsData: PaginatedDocs<Version>
|
||||
data: Document
|
||||
versionsData: PaginatedDocs<Document>
|
||||
editURL: string
|
||||
entityLabel: string
|
||||
id: string
|
||||
id: string | number
|
||||
user: User
|
||||
limit: number
|
||||
i18n: I18n
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import queryString from 'qs'
|
||||
import React from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import type { Node, Props } from './types'
|
||||
|
||||
@@ -9,6 +8,7 @@ import { useSearchParams } from '../../providers/SearchParams'
|
||||
import ClickableArrow from './ClickableArrow'
|
||||
import Page from './Page'
|
||||
import Separator from './Separator'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import './index.scss'
|
||||
|
||||
const nodeTypes = {
|
||||
@@ -20,8 +20,9 @@ const nodeTypes = {
|
||||
const baseClass = 'paginator'
|
||||
|
||||
export const Pagination: React.FC<Props> = (props) => {
|
||||
const history = useHistory()
|
||||
const router = useRouter()
|
||||
const params = useSearchParams()
|
||||
const pathname = usePathname()
|
||||
|
||||
const {
|
||||
disableHistoryChange = false,
|
||||
@@ -37,7 +38,6 @@ export const Pagination: React.FC<Props> = (props) => {
|
||||
|
||||
if (!totalPages || totalPages <= 1) return null
|
||||
|
||||
// uses react router to set the current page
|
||||
const updatePage = (page) => {
|
||||
if (!disableHistoryChange) {
|
||||
const newParams = {
|
||||
@@ -45,7 +45,7 @@ export const Pagination: React.FC<Props> = (props) => {
|
||||
}
|
||||
|
||||
newParams.page = page
|
||||
history.push({ search: queryString.stringify(newParams, { addQueryPrefix: true }) })
|
||||
router.push(pathname + queryString.stringify(newParams, { addQueryPrefix: true }))
|
||||
}
|
||||
|
||||
if (typeof onChange === 'function') onChange(page)
|
||||
|
||||
@@ -35,3 +35,4 @@ export { default as Popup } from '../elements/Popup'
|
||||
// export { useThumbnail } from '../elements/Upload'
|
||||
export { Translation } from '../elements/Translation'
|
||||
export { Tooltip } from '../elements/Tooltip'
|
||||
export { useTableCell } from '../elements/Table/TableCellProvider'
|
||||
|
||||
@@ -4,3 +4,4 @@ export { formatDate } from '../utilities/formatDate'
|
||||
export type { EntityToGroup, Group } from '../utilities/groupNavItems'
|
||||
export { EntityType, groupNavItems } from '../utilities/groupNavItems'
|
||||
export { withMergedProps } from '../utilities/withMergedProps'
|
||||
export type { FieldMap, MappedField } from '../utilities/buildComponentMap/types'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export { DefaultList } from '../views/List'
|
||||
export { DefaultEditView } from '../views/Edit'
|
||||
export type { DefaultEditViewProps } from '../views/Edit/types'
|
||||
export type { DefaultListViewProps } from '../views/List/types'
|
||||
export { default as Auth } from '../views/Edit/Auth'
|
||||
export type { EditViewProps } from '../views/types'
|
||||
export type { ServerSideEditViewProps } from '../views/types'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import queryString from 'qs'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from '../providers/Translation'
|
||||
|
||||
import { requests } from '../utilities/api'
|
||||
@@ -25,14 +25,15 @@ type Options = {
|
||||
type UsePayloadAPI = (url: string, options?: Options) => Result
|
||||
|
||||
const usePayloadAPI: UsePayloadAPI = (url, options = {}) => {
|
||||
const { initialData = {}, initialParams = {} } = options
|
||||
const { initialData, initialParams = {} } = options
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const [data, setData] = useState(initialData)
|
||||
const [data, setData] = useState(initialData || {})
|
||||
const [params, setParams] = useState(initialParams)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isError, setIsError] = useState(false)
|
||||
const { code: locale } = useLocale()
|
||||
const hasInitialized = useRef(false)
|
||||
|
||||
const search = queryString.stringify(
|
||||
{
|
||||
@@ -45,6 +46,11 @@ const usePayloadAPI: UsePayloadAPI = (url, options = {}) => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData && !hasInitialized.current) {
|
||||
hasInitialized.current = true
|
||||
return
|
||||
}
|
||||
|
||||
const abortController = new AbortController()
|
||||
|
||||
const fetchData = async () => {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
'use client'
|
||||
import { useSearchParams as useNextSearchParams } from 'next/navigation'
|
||||
import qs from 'qs'
|
||||
import React, { createContext, useContext } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
const Context = createContext({})
|
||||
|
||||
export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const location = useLocation()
|
||||
const searchParams = useNextSearchParams()
|
||||
|
||||
const params = qs.parse(location.search, { depth: 10, ignoreQueryPrefix: true })
|
||||
const params = qs.parse(searchParams.toString(), { depth: 10, ignoreQueryPrefix: true })
|
||||
|
||||
return <Context.Provider value={params}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@ export const mapFields = (args: {
|
||||
const reducedField: MappedField = {
|
||||
name: 'name' in field ? field.name : '',
|
||||
label: 'label' in field && typeof field.label !== 'function' ? field.label : undefined,
|
||||
labels: 'labels' in field ? field.labels : undefined,
|
||||
type: field.type,
|
||||
Field,
|
||||
Cell: (
|
||||
@@ -283,6 +284,9 @@ export const mapFields = (args: {
|
||||
isSidebar: field.admin?.position === 'sidebar',
|
||||
subfields: nestedFieldMap,
|
||||
tabs,
|
||||
options: 'options' in field ? field.options : undefined,
|
||||
hasMany: 'hasMany' in field ? field.hasMany : undefined,
|
||||
localized: 'localized' in field ? field.localized : false,
|
||||
}
|
||||
|
||||
if (FieldComponent) {
|
||||
@@ -294,7 +298,8 @@ export const mapFields = (args: {
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
const hasID = result.findIndex((field) => fieldAffectsData(field) && field.name === 'id') > -1
|
||||
const hasID =
|
||||
result.findIndex(({ isFieldAffectingData, name }) => isFieldAffectingData && name === 'id') > -1
|
||||
|
||||
if (!hasID) {
|
||||
result.push({
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedGlobalConfig,
|
||||
TabsField,
|
||||
Option,
|
||||
Labels,
|
||||
} from 'payload/types'
|
||||
import { fieldTypes } from '../../forms/fields'
|
||||
|
||||
@@ -34,6 +36,9 @@ export type MappedField = {
|
||||
readOnly: boolean
|
||||
isSidebar: boolean
|
||||
label: FieldBase['label']
|
||||
labels: Labels
|
||||
fieldMap?: FieldMap
|
||||
localized: boolean
|
||||
/**
|
||||
* On `array`, `blocks`, `group`, `collapsible`, and `tabs` fields only
|
||||
*/
|
||||
@@ -42,7 +47,11 @@ export type MappedField = {
|
||||
* On `tabs` fields only
|
||||
*/
|
||||
tabs?: MappedTab[]
|
||||
fieldMap?: FieldMap
|
||||
/**
|
||||
* On `select` fields only
|
||||
*/
|
||||
options?: Option[]
|
||||
hasMany?: boolean
|
||||
}
|
||||
|
||||
export type FieldMap = MappedField[]
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import type { CollectionPermission, GlobalPermission, Permissions, User } from 'payload/auth'
|
||||
import type {
|
||||
Document,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedConfig,
|
||||
SanitizedGlobalConfig,
|
||||
Payload,
|
||||
DocumentPreferences,
|
||||
} from 'payload/types'
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type { Document, DocumentPreferences, Payload, SanitizedConfig } from 'payload/types'
|
||||
import type { FormState } from '../forms/Form/types'
|
||||
import type { FieldTypes, Locale } from 'payload/config'
|
||||
import { FieldMap } from '../utilities/buildComponentMap/types'
|
||||
import type { Locale } from 'payload/config'
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
|
||||
export type EditViewProps = (
|
||||
| {
|
||||
@@ -43,3 +35,18 @@ export type EditViewProps = (
|
||||
AfterDocument?: React.ReactNode
|
||||
AfterFields?: React.ReactNode
|
||||
}
|
||||
|
||||
export type ServerSideEditViewProps = EditViewProps & {
|
||||
payload: Payload
|
||||
config: SanitizedConfig
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
i18n: I18n
|
||||
collectionConfig?: SanitizedConfig['collections'][0]
|
||||
globalConfig?: SanitizedConfig['globals'][0]
|
||||
params: {
|
||||
segments: string[]
|
||||
collection?: string
|
||||
global?: string
|
||||
}
|
||||
permissions: Permissions
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user