perf(ui): speed up list view rendering, ensure root layout does not re-render when navigating to list view (#10607)
Data for the EditMany view was fetched even though the EditMany Drawer was not open. This, in combination with the router.replace call to add the default limit query param, caused the root layout to re-render
This commit is contained in:
@@ -204,12 +204,10 @@ export const renderListView = async (
|
||||
<Fragment>
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<ListQueryProvider
|
||||
collectionSlug={collectionSlug}
|
||||
data={data}
|
||||
defaultLimit={limit}
|
||||
defaultSort={sort}
|
||||
modifySearchParams={!isInDrawer}
|
||||
preferenceKey={preferenceKey}
|
||||
>
|
||||
{RenderServerComponent({
|
||||
clientProps,
|
||||
|
||||
@@ -194,7 +194,6 @@ export const VersionsView: PayloadServerReactComponent<EditViewComponent> = asyn
|
||||
<main className={baseClass}>
|
||||
<Gutter className={`${baseClass}__wrap`}>
|
||||
<ListQueryProvider
|
||||
collectionSlug={collectionSlug}
|
||||
data={versionsData}
|
||||
defaultLimit={limitToUse}
|
||||
defaultSort={sort as string}
|
||||
|
||||
331
packages/ui/src/elements/EditMany/DrawerContent.tsx
Normal file
331
packages/ui/src/elements/EditMany/DrawerContent.tsx
Normal file
@@ -0,0 +1,331 @@
|
||||
'use client'
|
||||
|
||||
import type { FieldWithPathClient, FormState } from 'payload'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||
import * as qs from 'qs-esm'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import type { FormProps } from '../../forms/Form/index.js'
|
||||
|
||||
import { useForm } from '../../forms/Form/context.js'
|
||||
import { Form } from '../../forms/Form/index.js'
|
||||
import { RenderFields } from '../../forms/RenderFields/index.js'
|
||||
import { FormSubmit } from '../../forms/Submit/index.js'
|
||||
import { XIcon } from '../../icons/X/index.js'
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { DocumentInfoProvider } from '../../providers/DocumentInfo/index.js'
|
||||
import { OperationContext } from '../../providers/Operation/index.js'
|
||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
||||
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { abortAndIgnore, handleAbortRef } from '../../utilities/abortAndIgnore.js'
|
||||
import { mergeListSearchAndWhere } from '../../utilities/mergeListSearchAndWhere.js'
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
import './index.scss'
|
||||
import { FieldSelect } from '../FieldSelect/index.js'
|
||||
import { baseClass, type EditManyProps } from './index.js'
|
||||
|
||||
const sanitizeUnselectedFields = (formState: FormState, selected: FieldWithPathClient[]) => {
|
||||
const filteredData = selected.reduce((acc, field) => {
|
||||
const foundState = formState?.[field.path]
|
||||
|
||||
if (foundState) {
|
||||
acc[field.path] = formState?.[field.path]?.value
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {} as FormData)
|
||||
|
||||
return filteredData
|
||||
}
|
||||
|
||||
const Submit: React.FC<{
|
||||
readonly action: string
|
||||
readonly disabled: boolean
|
||||
readonly selected?: FieldWithPathClient[]
|
||||
}> = ({ action, disabled, selected }) => {
|
||||
const { submit } = useForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const save = useCallback(() => {
|
||||
void submit({
|
||||
action,
|
||||
method: 'PATCH',
|
||||
overrides: (formState) => sanitizeUnselectedFields(formState, selected),
|
||||
skipValidation: true,
|
||||
})
|
||||
}, [action, submit, selected])
|
||||
|
||||
return (
|
||||
<FormSubmit className={`${baseClass}__save`} disabled={disabled} onClick={save}>
|
||||
{t('general:save')}
|
||||
</FormSubmit>
|
||||
)
|
||||
}
|
||||
|
||||
const PublishButton: React.FC<{
|
||||
action: string
|
||||
disabled: boolean
|
||||
selected?: FieldWithPathClient[]
|
||||
}> = ({ action, disabled, selected }) => {
|
||||
const { submit } = useForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const save = useCallback(() => {
|
||||
void submit({
|
||||
action,
|
||||
method: 'PATCH',
|
||||
overrides: (formState) => ({
|
||||
...sanitizeUnselectedFields(formState, selected),
|
||||
_status: 'published',
|
||||
}),
|
||||
skipValidation: true,
|
||||
})
|
||||
}, [action, submit, selected])
|
||||
|
||||
return (
|
||||
<FormSubmit className={`${baseClass}__publish`} disabled={disabled} onClick={save}>
|
||||
{t('version:publishChanges')}
|
||||
</FormSubmit>
|
||||
)
|
||||
}
|
||||
|
||||
const SaveDraftButton: React.FC<{
|
||||
action: string
|
||||
disabled: boolean
|
||||
selected?: FieldWithPathClient[]
|
||||
}> = ({ action, disabled, selected }) => {
|
||||
const { submit } = useForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const save = useCallback(() => {
|
||||
void submit({
|
||||
action,
|
||||
method: 'PATCH',
|
||||
overrides: (formState) => ({
|
||||
...sanitizeUnselectedFields(formState, selected),
|
||||
_status: 'draft',
|
||||
}),
|
||||
skipValidation: true,
|
||||
})
|
||||
}, [action, submit, selected])
|
||||
|
||||
return (
|
||||
<FormSubmit
|
||||
buttonStyle="secondary"
|
||||
className={`${baseClass}__draft`}
|
||||
disabled={disabled}
|
||||
onClick={save}
|
||||
>
|
||||
{t('version:saveDraft')}
|
||||
</FormSubmit>
|
||||
)
|
||||
}
|
||||
|
||||
export const EditManyDrawerContent: React.FC<
|
||||
{
|
||||
drawerSlug: string
|
||||
selected: FieldWithPathClient[]
|
||||
} & EditManyProps
|
||||
> = (props) => {
|
||||
const {
|
||||
collection: { slug, fields, labels: { plural } } = {},
|
||||
collection,
|
||||
drawerSlug,
|
||||
selected: selectedFromProps,
|
||||
} = props
|
||||
|
||||
const { permissions, user } = useAuth()
|
||||
|
||||
const { closeModal } = useModal()
|
||||
|
||||
const {
|
||||
config: {
|
||||
routes: { api: apiRoute },
|
||||
serverURL,
|
||||
},
|
||||
} = useConfig()
|
||||
|
||||
const { getFormState } = useServerFunctions()
|
||||
|
||||
const { count, getQueryParams, selectAll } = useSelection()
|
||||
const { i18n, t } = useTranslation()
|
||||
const [selected, setSelected] = useState<FieldWithPathClient[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(selectedFromProps)
|
||||
}, [selectedFromProps])
|
||||
|
||||
const router = useRouter()
|
||||
const [initialState, setInitialState] = useState<FormState>()
|
||||
const hasInitializedState = React.useRef(false)
|
||||
const abortFormStateRef = React.useRef<AbortController>(null)
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
React.useEffect(() => {
|
||||
const controller = new AbortController()
|
||||
|
||||
if (!hasInitializedState.current) {
|
||||
const getInitialState = async () => {
|
||||
const { state: result } = await getFormState({
|
||||
collectionSlug: slug,
|
||||
data: {},
|
||||
docPermissions: collectionPermissions,
|
||||
docPreferences: null,
|
||||
operation: 'update',
|
||||
schemaPath: slug,
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
setInitialState(result)
|
||||
hasInitializedState.current = true
|
||||
}
|
||||
|
||||
void getInitialState()
|
||||
}
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(controller)
|
||||
}
|
||||
}, [apiRoute, hasInitializedState, serverURL, slug, getFormState, user, collectionPermissions])
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
const controller = handleAbortRef(abortFormStateRef)
|
||||
|
||||
const { state } = await getFormState({
|
||||
collectionSlug: slug,
|
||||
docPermissions: collectionPermissions,
|
||||
docPreferences: null,
|
||||
formState: prevFormState,
|
||||
operation: 'update',
|
||||
schemaPath: slug,
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
abortFormStateRef.current = null
|
||||
|
||||
return state
|
||||
},
|
||||
[getFormState, slug, collectionPermissions],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const abortFormState = abortFormStateRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(abortFormState)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const queryString = useMemo(() => {
|
||||
const queryWithSearch = mergeListSearchAndWhere({
|
||||
collectionConfig: collection,
|
||||
search: searchParams.get('search'),
|
||||
})
|
||||
|
||||
return getQueryParams(queryWithSearch)
|
||||
}, [collection, searchParams, getQueryParams])
|
||||
|
||||
const onSuccess = () => {
|
||||
router.replace(
|
||||
qs.stringify(
|
||||
{
|
||||
...parseSearchParams(searchParams),
|
||||
page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
),
|
||||
)
|
||||
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
||||
closeModal(drawerSlug)
|
||||
}
|
||||
|
||||
return (
|
||||
<DocumentInfoProvider
|
||||
collectionSlug={slug}
|
||||
currentEditor={user}
|
||||
hasPublishedDoc={false}
|
||||
id={null}
|
||||
initialData={{}}
|
||||
initialState={initialState}
|
||||
isLocked={false}
|
||||
lastUpdateTime={0}
|
||||
mostRecentVersionIsAutosaved={false}
|
||||
unpublishedVersionCount={0}
|
||||
versionCount={0}
|
||||
>
|
||||
<OperationContext.Provider value="update">
|
||||
<div className={`${baseClass}__main`}>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<h2 className={`${baseClass}__header__title`}>
|
||||
{t('general:editingLabel', { count, label: getTranslation(plural, i18n) })}
|
||||
</h2>
|
||||
<button
|
||||
aria-label={t('general:close')}
|
||||
className={`${baseClass}__header__close`}
|
||||
id={`close-drawer__${drawerSlug}`}
|
||||
onClick={() => closeModal(drawerSlug)}
|
||||
type="button"
|
||||
>
|
||||
<XIcon />
|
||||
</button>
|
||||
</div>
|
||||
<Form
|
||||
className={`${baseClass}__form`}
|
||||
initialState={initialState}
|
||||
onChange={[onChange]}
|
||||
onSuccess={onSuccess}
|
||||
>
|
||||
<FieldSelect fields={fields} setSelected={setSelected} />
|
||||
{selected.length === 0 ? null : (
|
||||
<RenderFields
|
||||
fields={selected}
|
||||
parentIndexPath=""
|
||||
parentPath=""
|
||||
parentSchemaPath={slug}
|
||||
permissions={collectionPermissions?.fields}
|
||||
readOnly={false}
|
||||
/>
|
||||
)}
|
||||
<div className={`${baseClass}__sidebar-wrap`}>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__sidebar-sticky-wrap`}>
|
||||
<div className={`${baseClass}__document-actions`}>
|
||||
{collection?.versions?.drafts ? (
|
||||
<React.Fragment>
|
||||
<SaveDraftButton
|
||||
action={`${serverURL}${apiRoute}/${slug}${queryString}&draft=true`}
|
||||
disabled={selected.length === 0}
|
||||
selected={selected}
|
||||
/>
|
||||
<PublishButton
|
||||
action={`${serverURL}${apiRoute}/${slug}${queryString}&draft=true`}
|
||||
disabled={selected.length === 0}
|
||||
selected={selected}
|
||||
/>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Submit
|
||||
action={`${serverURL}${apiRoute}/${slug}${queryString}`}
|
||||
disabled={selected.length === 0}
|
||||
selected={selected}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</OperationContext.Provider>
|
||||
</DocumentInfoProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,248 +1,38 @@
|
||||
'use client'
|
||||
import type { ClientCollectionConfig, FieldWithPathClient, FormState } from 'payload'
|
||||
import type { ClientCollectionConfig, FieldWithPathClient } from 'payload'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||
import * as qs from 'qs-esm'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import type { FormProps } from '../../forms/Form/index.js'
|
||||
|
||||
import { useForm } from '../../forms/Form/context.js'
|
||||
import { Form } from '../../forms/Form/index.js'
|
||||
import { RenderFields } from '../../forms/RenderFields/index.js'
|
||||
import { FormSubmit } from '../../forms/Submit/index.js'
|
||||
import { XIcon } from '../../icons/X/index.js'
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { DocumentInfoProvider } from '../../providers/DocumentInfo/index.js'
|
||||
import { EditDepthProvider } from '../../providers/EditDepth/index.js'
|
||||
import { OperationContext } from '../../providers/Operation/index.js'
|
||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
||||
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { abortAndIgnore, handleAbortRef } from '../../utilities/abortAndIgnore.js'
|
||||
import { mergeListSearchAndWhere } from '../../utilities/mergeListSearchAndWhere.js'
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
import './index.scss'
|
||||
import { Drawer, DrawerToggler } from '../Drawer/index.js'
|
||||
import { FieldSelect } from '../FieldSelect/index.js'
|
||||
import { EditManyDrawerContent } from './DrawerContent.js'
|
||||
|
||||
const baseClass = 'edit-many'
|
||||
export const baseClass = 'edit-many'
|
||||
|
||||
export type EditManyProps = {
|
||||
readonly collection: ClientCollectionConfig
|
||||
}
|
||||
|
||||
const sanitizeUnselectedFields = (formState: FormState, selected: FieldWithPathClient[]) => {
|
||||
const filteredData = selected.reduce((acc, field) => {
|
||||
const foundState = formState?.[field.path]
|
||||
|
||||
if (foundState) {
|
||||
acc[field.path] = formState?.[field.path]?.value
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {} as FormData)
|
||||
|
||||
return filteredData
|
||||
}
|
||||
|
||||
const Submit: React.FC<{
|
||||
readonly action: string
|
||||
readonly disabled: boolean
|
||||
readonly selected?: FieldWithPathClient[]
|
||||
}> = ({ action, disabled, selected }) => {
|
||||
const { submit } = useForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const save = useCallback(() => {
|
||||
void submit({
|
||||
action,
|
||||
method: 'PATCH',
|
||||
overrides: (formState) => sanitizeUnselectedFields(formState, selected),
|
||||
skipValidation: true,
|
||||
})
|
||||
}, [action, submit, selected])
|
||||
|
||||
return (
|
||||
<FormSubmit className={`${baseClass}__save`} disabled={disabled} onClick={save}>
|
||||
{t('general:save')}
|
||||
</FormSubmit>
|
||||
)
|
||||
}
|
||||
|
||||
const PublishButton: React.FC<{
|
||||
action: string
|
||||
disabled: boolean
|
||||
selected?: FieldWithPathClient[]
|
||||
}> = ({ action, disabled, selected }) => {
|
||||
const { submit } = useForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const save = useCallback(() => {
|
||||
void submit({
|
||||
action,
|
||||
method: 'PATCH',
|
||||
overrides: (formState) => ({
|
||||
...sanitizeUnselectedFields(formState, selected),
|
||||
_status: 'published',
|
||||
}),
|
||||
skipValidation: true,
|
||||
})
|
||||
}, [action, submit, selected])
|
||||
|
||||
return (
|
||||
<FormSubmit className={`${baseClass}__publish`} disabled={disabled} onClick={save}>
|
||||
{t('version:publishChanges')}
|
||||
</FormSubmit>
|
||||
)
|
||||
}
|
||||
|
||||
const SaveDraftButton: React.FC<{
|
||||
action: string
|
||||
disabled: boolean
|
||||
selected?: FieldWithPathClient[]
|
||||
}> = ({ action, disabled, selected }) => {
|
||||
const { submit } = useForm()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const save = useCallback(() => {
|
||||
void submit({
|
||||
action,
|
||||
method: 'PATCH',
|
||||
overrides: (formState) => ({
|
||||
...sanitizeUnselectedFields(formState, selected),
|
||||
_status: 'draft',
|
||||
}),
|
||||
skipValidation: true,
|
||||
})
|
||||
}, [action, submit, selected])
|
||||
|
||||
return (
|
||||
<FormSubmit
|
||||
buttonStyle="secondary"
|
||||
className={`${baseClass}__draft`}
|
||||
disabled={disabled}
|
||||
onClick={save}
|
||||
>
|
||||
{t('version:saveDraft')}
|
||||
</FormSubmit>
|
||||
)
|
||||
}
|
||||
|
||||
export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
const { collection: { slug, fields, labels: { plural } } = {}, collection } = props
|
||||
|
||||
const { permissions, user } = useAuth()
|
||||
|
||||
const { closeModal } = useModal()
|
||||
|
||||
const {
|
||||
config: {
|
||||
routes: { api: apiRoute },
|
||||
serverURL,
|
||||
},
|
||||
} = useConfig()
|
||||
collection: { slug },
|
||||
} = props
|
||||
|
||||
const { getFormState } = useServerFunctions()
|
||||
const { permissions } = useAuth()
|
||||
|
||||
const { count, getQueryParams, selectAll } = useSelection()
|
||||
const { i18n, t } = useTranslation()
|
||||
const { selectAll } = useSelection()
|
||||
const { t } = useTranslation()
|
||||
const [selected, setSelected] = useState<FieldWithPathClient[]>([])
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const [initialState, setInitialState] = useState<FormState>()
|
||||
const hasInitializedState = React.useRef(false)
|
||||
const abortFormStateRef = React.useRef<AbortController>(null)
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const hasUpdatePermission = collectionPermissions?.update
|
||||
|
||||
const drawerSlug = `edit-${slug}`
|
||||
|
||||
React.useEffect(() => {
|
||||
const controller = new AbortController()
|
||||
|
||||
if (!hasInitializedState.current) {
|
||||
const getInitialState = async () => {
|
||||
const { state: result } = await getFormState({
|
||||
collectionSlug: slug,
|
||||
data: {},
|
||||
docPermissions: collectionPermissions,
|
||||
docPreferences: null,
|
||||
operation: 'update',
|
||||
schemaPath: slug,
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
setInitialState(result)
|
||||
hasInitializedState.current = true
|
||||
}
|
||||
|
||||
void getInitialState()
|
||||
}
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(controller)
|
||||
}
|
||||
}, [apiRoute, hasInitializedState, serverURL, slug, getFormState, user, collectionPermissions])
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
const controller = handleAbortRef(abortFormStateRef)
|
||||
|
||||
const { state } = await getFormState({
|
||||
collectionSlug: slug,
|
||||
docPermissions: collectionPermissions,
|
||||
docPreferences: null,
|
||||
formState: prevFormState,
|
||||
operation: 'update',
|
||||
schemaPath: slug,
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
abortFormStateRef.current = null
|
||||
|
||||
return state
|
||||
},
|
||||
[getFormState, slug, collectionPermissions],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const abortFormState = abortFormStateRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(abortFormState)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const queryString = useMemo(() => {
|
||||
const queryWithSearch = mergeListSearchAndWhere({
|
||||
collectionConfig: collection,
|
||||
search: searchParams.get('search'),
|
||||
})
|
||||
|
||||
return getQueryParams(queryWithSearch)
|
||||
}, [collection, searchParams, getQueryParams])
|
||||
|
||||
const onSuccess = () => {
|
||||
router.replace(
|
||||
qs.stringify(
|
||||
{
|
||||
...parseSearchParams(searchParams),
|
||||
page: selectAll === SelectAllStatus.AllAvailable ? '1' : undefined,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
),
|
||||
)
|
||||
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
||||
closeModal(drawerSlug)
|
||||
}
|
||||
|
||||
if (selectAll === SelectAllStatus.None || !hasUpdatePermission) {
|
||||
return null
|
||||
}
|
||||
@@ -261,84 +51,11 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
</DrawerToggler>
|
||||
<EditDepthProvider>
|
||||
<Drawer Header={null} slug={drawerSlug}>
|
||||
<DocumentInfoProvider
|
||||
collectionSlug={slug}
|
||||
currentEditor={user}
|
||||
hasPublishedDoc={false}
|
||||
id={null}
|
||||
initialData={{}}
|
||||
initialState={initialState}
|
||||
isLocked={false}
|
||||
lastUpdateTime={0}
|
||||
mostRecentVersionIsAutosaved={false}
|
||||
unpublishedVersionCount={0}
|
||||
versionCount={0}
|
||||
>
|
||||
<OperationContext.Provider value="update">
|
||||
<div className={`${baseClass}__main`}>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<h2 className={`${baseClass}__header__title`}>
|
||||
{t('general:editingLabel', { count, label: getTranslation(plural, i18n) })}
|
||||
</h2>
|
||||
<button
|
||||
aria-label={t('general:close')}
|
||||
className={`${baseClass}__header__close`}
|
||||
id={`close-drawer__${drawerSlug}`}
|
||||
onClick={() => closeModal(drawerSlug)}
|
||||
type="button"
|
||||
>
|
||||
<XIcon />
|
||||
</button>
|
||||
</div>
|
||||
<Form
|
||||
className={`${baseClass}__form`}
|
||||
initialState={initialState}
|
||||
onChange={[onChange]}
|
||||
onSuccess={onSuccess}
|
||||
>
|
||||
<FieldSelect fields={fields} setSelected={setSelected} />
|
||||
{selected.length === 0 ? null : (
|
||||
<RenderFields
|
||||
fields={selected}
|
||||
parentIndexPath=""
|
||||
parentPath=""
|
||||
parentSchemaPath={slug}
|
||||
permissions={collectionPermissions?.fields}
|
||||
readOnly={false}
|
||||
/>
|
||||
)}
|
||||
<div className={`${baseClass}__sidebar-wrap`}>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__sidebar-sticky-wrap`}>
|
||||
<div className={`${baseClass}__document-actions`}>
|
||||
{collection?.versions?.drafts ? (
|
||||
<React.Fragment>
|
||||
<SaveDraftButton
|
||||
action={`${serverURL}${apiRoute}/${slug}${queryString}&draft=true`}
|
||||
disabled={selected.length === 0}
|
||||
selected={selected}
|
||||
/>
|
||||
<PublishButton
|
||||
action={`${serverURL}${apiRoute}/${slug}${queryString}&draft=true`}
|
||||
disabled={selected.length === 0}
|
||||
selected={selected}
|
||||
/>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Submit
|
||||
action={`${serverURL}${apiRoute}/${slug}${queryString}`}
|
||||
disabled={selected.length === 0}
|
||||
selected={selected}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</OperationContext.Provider>
|
||||
</DocumentInfoProvider>
|
||||
<EditManyDrawerContent
|
||||
collection={props.collection}
|
||||
drawerSlug={drawerSlug}
|
||||
selected={selected}
|
||||
/>
|
||||
</Drawer>
|
||||
</EditDepthProvider>
|
||||
</div>
|
||||
|
||||
@@ -254,14 +254,12 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
|
||||
{data.docs && data.docs.length > 0 && (
|
||||
<RelationshipProvider>
|
||||
<ListQueryProvider
|
||||
collectionSlug={relationTo}
|
||||
data={data}
|
||||
defaultLimit={
|
||||
field.defaultLimit ?? collectionConfig?.admin?.pagination?.defaultLimit
|
||||
}
|
||||
modifySearchParams={false}
|
||||
onQueryChange={setQuery}
|
||||
preferenceKey={preferenceKey}
|
||||
>
|
||||
<TableColumnsProvider
|
||||
collectionSlug={relationTo}
|
||||
|
||||
@@ -8,7 +8,6 @@ import React, { createContext, useCallback, useContext, useEffect, useMemo, useS
|
||||
|
||||
import { useListDrawerContext } from '../../elements/ListDrawer/Provider.js'
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
import { usePreferences } from '../Preferences/index.js'
|
||||
|
||||
type ContextHandlers = {
|
||||
handlePageChange?: (page: number) => Promise<void>
|
||||
@@ -20,7 +19,7 @@ type ContextHandlers = {
|
||||
|
||||
export type ListQueryProps = {
|
||||
readonly children: React.ReactNode
|
||||
readonly collectionSlug: string
|
||||
readonly collectionSlug?: string
|
||||
readonly data: PaginatedDocs
|
||||
readonly defaultLimit?: number
|
||||
readonly defaultSort?: Sort
|
||||
@@ -43,17 +42,14 @@ export const useListQuery = (): ListQueryContext => useContext(Context)
|
||||
|
||||
export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
children,
|
||||
collectionSlug,
|
||||
data,
|
||||
defaultLimit,
|
||||
defaultSort,
|
||||
modifySearchParams,
|
||||
onQueryChange: onQueryChangeFromProps,
|
||||
preferenceKey,
|
||||
}) => {
|
||||
'use no memo'
|
||||
const router = useRouter()
|
||||
const { setPreference } = usePreferences()
|
||||
const rawSearchParams = useSearchParams()
|
||||
const searchParams = useMemo(() => parseSearchParams(rawSearchParams), [rawSearchParams])
|
||||
|
||||
@@ -176,7 +172,8 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
|
||||
if (shouldUpdateQueryString) {
|
||||
setCurrentQuery(newQuery)
|
||||
router.replace(`?${qs.stringify(newQuery)}`)
|
||||
// Do not use router.replace here to avoid re-rendering on initial load
|
||||
window.history.pushState(null, '', `?${qs.stringify(newQuery)}`)
|
||||
}
|
||||
}
|
||||
}, [defaultSort, defaultLimit, router, modifySearchParams])
|
||||
|
||||
Reference in New Issue
Block a user