feat(ui): moves folder rendering from the client to the server (#12710)
This commit is contained in:
@@ -3,6 +3,7 @@ import type { ServerFunction, ServerFunctionHandler } from 'payload'
|
||||
import { copyDataFromLocaleHandler } from '@payloadcms/ui/rsc'
|
||||
import { buildFormStateHandler } from '@payloadcms/ui/utilities/buildFormState'
|
||||
import { buildTableStateHandler } from '@payloadcms/ui/utilities/buildTableState'
|
||||
import { getFolderResultsComponentAndDataHandler } from '@payloadcms/ui/utilities/getFolderResultsComponentAndData'
|
||||
import { schedulePublishHandler } from '@payloadcms/ui/utilities/schedulePublishHandler'
|
||||
|
||||
import { renderDocumentHandler } from '../views/Document/handleServerFunction.js'
|
||||
@@ -28,6 +29,8 @@ export const handleServerFunctions: ServerFunctionHandler = async (args) => {
|
||||
const serverFunctions = {
|
||||
'copy-data-from-locale': copyDataFromLocaleHandler as any as ServerFunction,
|
||||
'form-state': buildFormStateHandler as any as ServerFunction,
|
||||
'get-folder-results-component-and-data':
|
||||
getFolderResultsComponentAndDataHandler as any as ServerFunction,
|
||||
'render-document': renderDocumentHandler as any as ServerFunction,
|
||||
'render-document-slots': renderDocumentSlotsHandler as any as ServerFunction,
|
||||
'render-list': renderListHandler as any as ServerFunction,
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import type {
|
||||
AdminViewServerProps,
|
||||
BuildCollectionFolderViewResult,
|
||||
FolderListViewClientProps,
|
||||
FolderListViewServerPropsOnly,
|
||||
FolderSortKeys,
|
||||
ListQuery,
|
||||
Where,
|
||||
} from 'payload'
|
||||
|
||||
import { DefaultBrowseByFolderView, FolderProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { DefaultBrowseByFolderView, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { getFolderResultsComponentAndData, upsertPreferences } from '@payloadcms/ui/rsc'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { redirect } from 'next/navigation.js'
|
||||
import { getFolderData } from 'payload'
|
||||
import { buildFolderWhereConstraints } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
import { getPreferences } from '../../utilities/getPreferences.js'
|
||||
|
||||
export type BuildFolderViewArgs = {
|
||||
customCellProps?: Record<string, any>
|
||||
disableBulkDelete?: boolean
|
||||
@@ -56,18 +54,18 @@ export const buildBrowseByFolderView = async (
|
||||
visibleEntities,
|
||||
} = initPageResult
|
||||
|
||||
if (config.folders === false || config.folders.browseByFolder === false) {
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
const browseByFolderSlugs = browseByFolderSlugsFromArgs.filter(
|
||||
(collectionSlug) =>
|
||||
permissions?.collections?.[collectionSlug]?.read &&
|
||||
visibleEntities.collections.includes(collectionSlug),
|
||||
)
|
||||
|
||||
if (config.folders === false || config.folders.browseByFolder === false) {
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
const query = queryFromArgs || queryFromReq
|
||||
const selectedCollectionSlugs: string[] =
|
||||
const activeCollectionFolderSlugs: string[] =
|
||||
Array.isArray(query?.relationTo) && query.relationTo.length
|
||||
? query.relationTo.filter(
|
||||
(slug) =>
|
||||
@@ -79,62 +77,35 @@ export const buildBrowseByFolderView = async (
|
||||
routes: { admin: adminRoute },
|
||||
} = config
|
||||
|
||||
const folderCollectionConfig = payload.collections[config.folders.slug].config
|
||||
|
||||
const browseByFolderPreferences = await getPreferences<{ viewPreference: string }>(
|
||||
'browse-by-folder',
|
||||
payload,
|
||||
user.id,
|
||||
user.collection,
|
||||
)
|
||||
|
||||
let documentWhere: undefined | Where = undefined
|
||||
let folderWhere: undefined | Where = undefined
|
||||
// if folderID, dont make a documentWhere since it only queries root folders
|
||||
for (const collectionSlug of selectedCollectionSlugs) {
|
||||
if (collectionSlug === config.folders.slug) {
|
||||
const folderCollectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig: folderCollectionConfig,
|
||||
folderID,
|
||||
localeCode: fullLocale?.code,
|
||||
req: initPageResult.req,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
})
|
||||
|
||||
if (folderCollectionConstraints) {
|
||||
folderWhere = folderCollectionConstraints
|
||||
}
|
||||
} else if (folderID) {
|
||||
if (!documentWhere) {
|
||||
documentWhere = {
|
||||
or: [],
|
||||
}
|
||||
}
|
||||
|
||||
const collectionConfig = payload.collections[collectionSlug].config
|
||||
if (collectionConfig.folders && collectionConfig.folders.browseByFolder === true) {
|
||||
const collectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig,
|
||||
folderID,
|
||||
localeCode: fullLocale?.code,
|
||||
req: initPageResult.req,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
})
|
||||
|
||||
if (collectionConstraints) {
|
||||
documentWhere.or.push(collectionConstraints)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { breadcrumbs, documents, subfolders } = await getFolderData({
|
||||
documentWhere,
|
||||
folderID,
|
||||
folderWhere,
|
||||
/**
|
||||
* @todo: find a pattern to avoid setting preferences on hard navigation, i.e. direct links, page refresh, etc.
|
||||
* This will ensure that prefs are only updated when explicitly set by the user
|
||||
* This could potentially be done by injecting a `sessionID` into the params and comparing it against a session cookie
|
||||
*/
|
||||
const browseByFolderPreferences = await upsertPreferences<{
|
||||
sort?: FolderSortKeys
|
||||
viewPreference?: 'grid' | 'list'
|
||||
}>({
|
||||
key: 'browse-by-folder',
|
||||
req: initPageResult.req,
|
||||
value: {
|
||||
sort: query?.sort as FolderSortKeys,
|
||||
},
|
||||
})
|
||||
|
||||
const sortPreference: FolderSortKeys = browseByFolderPreferences?.sort || '_folderOrDocumentTitle'
|
||||
const viewPreference = browseByFolderPreferences?.viewPreference || 'grid'
|
||||
|
||||
const { breadcrumbs, documents, FolderResultsComponent, subfolders } =
|
||||
await getFolderResultsComponentAndData({
|
||||
activeCollectionSlugs: activeCollectionFolderSlugs,
|
||||
browseByFolder: false,
|
||||
displayAs: viewPreference,
|
||||
folderID,
|
||||
req: initPageResult.req,
|
||||
sort: sortPreference,
|
||||
})
|
||||
|
||||
const resolvedFolderID = breadcrumbs[breadcrumbs.length - 1]?.id
|
||||
|
||||
if (
|
||||
@@ -172,38 +143,39 @@ export const buildBrowseByFolderView = async (
|
||||
// })
|
||||
|
||||
// documents cannot be created without a parent folder in this view
|
||||
const hasCreatePermissionCollectionSlugs = folderID
|
||||
const allowCreateCollectionSlugs = resolvedFolderID
|
||||
? [config.folders.slug, ...browseByFolderSlugs]
|
||||
: [config.folders.slug]
|
||||
|
||||
return {
|
||||
View: (
|
||||
<FolderProvider
|
||||
breadcrumbs={breadcrumbs}
|
||||
documents={documents}
|
||||
filteredCollectionSlugs={selectedCollectionSlugs}
|
||||
folderCollectionSlugs={browseByFolderSlugs}
|
||||
folderFieldName={config.folders.fieldName}
|
||||
folderID={folderID}
|
||||
subfolders={subfolders}
|
||||
>
|
||||
<>
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
{RenderServerComponent({
|
||||
clientProps: {
|
||||
// ...folderViewSlots,
|
||||
activeCollectionFolderSlugs,
|
||||
allCollectionFolderSlugs: browseByFolderSlugs,
|
||||
allowCreateCollectionSlugs,
|
||||
baseFolderPath: `/browse-by-folder`,
|
||||
breadcrumbs,
|
||||
disableBulkDelete,
|
||||
disableBulkEdit,
|
||||
documents,
|
||||
enableRowSelections,
|
||||
hasCreatePermissionCollectionSlugs,
|
||||
selectedCollectionSlugs,
|
||||
viewPreference: browseByFolderPreferences?.value?.viewPreference,
|
||||
},
|
||||
// Component:config.folders?.components?.views?.list?.Component,
|
||||
folderFieldName: config.folders.fieldName,
|
||||
folderID: resolvedFolderID || null,
|
||||
FolderResultsComponent,
|
||||
sort: sortPreference,
|
||||
subfolders,
|
||||
viewPreference,
|
||||
} satisfies FolderListViewClientProps,
|
||||
// Component:config.folders?.components?.views?.BrowseByFolders?.Component,
|
||||
Fallback: DefaultBrowseByFolderView,
|
||||
importMap: payload.importMap,
|
||||
serverProps,
|
||||
})}
|
||||
</FolderProvider>
|
||||
</>
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import type {
|
||||
AdminViewServerProps,
|
||||
BuildCollectionFolderViewResult,
|
||||
FolderListViewClientProps,
|
||||
FolderListViewServerPropsOnly,
|
||||
FolderSortKeys,
|
||||
ListQuery,
|
||||
Where,
|
||||
} from 'payload'
|
||||
|
||||
import { DefaultCollectionFolderView, FolderProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { DefaultCollectionFolderView, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { getFolderResultsComponentAndData, upsertPreferences } from '@payloadcms/ui/rsc'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { redirect } from 'next/navigation.js'
|
||||
import { getFolderData } from 'payload'
|
||||
import { buildFolderWhereConstraints } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
import { getPreferences } from '../../utilities/getPreferences.js'
|
||||
|
||||
// import { renderFolderViewSlots } from './renderFolderViewSlots.js'
|
||||
|
||||
export type BuildCollectionFolderViewStateArgs = {
|
||||
@@ -62,24 +60,18 @@ export const buildCollectionFolderView = async (
|
||||
visibleEntities,
|
||||
} = initPageResult
|
||||
|
||||
if (!permissions?.collections?.[collectionSlug]?.read) {
|
||||
if (!config.folders) {
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
if (
|
||||
!permissions?.collections?.[collectionSlug]?.read ||
|
||||
!permissions?.collections?.[config.folders.slug].read
|
||||
) {
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
if (collectionConfig) {
|
||||
const query = queryFromArgs || queryFromReq
|
||||
|
||||
const collectionFolderPreferences = await getPreferences<{
|
||||
sort?: string
|
||||
viewPreference: string
|
||||
}>(`${collectionSlug}-collection-folder`, payload, user.id, user.collection)
|
||||
|
||||
const sortPreference = collectionFolderPreferences?.value.sort
|
||||
|
||||
const {
|
||||
routes: { admin: adminRoute },
|
||||
} = config
|
||||
|
||||
if (
|
||||
(!visibleEntities.collections.includes(collectionSlug) && !overrideEntityVisibility) ||
|
||||
!config.folders
|
||||
@@ -87,41 +79,41 @@ export const buildCollectionFolderView = async (
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
let folderWhere: undefined | Where
|
||||
const folderCollectionConfig = payload.collections[config.folders.slug].config
|
||||
const folderCollectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig: folderCollectionConfig,
|
||||
folderID,
|
||||
localeCode: fullLocale?.code,
|
||||
const query = queryFromArgs || queryFromReq
|
||||
|
||||
/**
|
||||
* @todo: find a pattern to avoid setting preferences on hard navigation, i.e. direct links, page refresh, etc.
|
||||
* This will ensure that prefs are only updated when explicitly set by the user
|
||||
* This could potentially be done by injecting a `sessionID` into the params and comparing it against a session cookie
|
||||
*/
|
||||
const collectionFolderPreferences = await upsertPreferences<{
|
||||
sort?: FolderSortKeys
|
||||
viewPreference?: 'grid' | 'list'
|
||||
}>({
|
||||
key: `${collectionSlug}-collection-folder`,
|
||||
req: initPageResult.req,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
sort: sortPreference,
|
||||
value: {
|
||||
sort: query?.sort as FolderSortKeys,
|
||||
},
|
||||
})
|
||||
|
||||
if (folderCollectionConstraints) {
|
||||
folderWhere = folderCollectionConstraints
|
||||
}
|
||||
const sortPreference: FolderSortKeys =
|
||||
collectionFolderPreferences?.sort || '_folderOrDocumentTitle'
|
||||
const viewPreference = collectionFolderPreferences?.viewPreference || 'grid'
|
||||
|
||||
let documentWhere: undefined | Where
|
||||
const collectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig,
|
||||
folderID,
|
||||
localeCode: fullLocale?.code,
|
||||
req: initPageResult.req,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
sort: sortPreference,
|
||||
})
|
||||
if (collectionConstraints) {
|
||||
documentWhere = collectionConstraints
|
||||
}
|
||||
const {
|
||||
routes: { admin: adminRoute },
|
||||
} = config
|
||||
|
||||
const { breadcrumbs, documents, subfolders } = await getFolderData({
|
||||
collectionSlug,
|
||||
documentWhere,
|
||||
folderID,
|
||||
folderWhere,
|
||||
req: initPageResult.req,
|
||||
})
|
||||
const { breadcrumbs, documents, FolderResultsComponent, subfolders } =
|
||||
await getFolderResultsComponentAndData({
|
||||
activeCollectionSlugs: [config.folders.slug, collectionSlug],
|
||||
browseByFolder: false,
|
||||
displayAs: viewPreference,
|
||||
folderID,
|
||||
req: initPageResult.req,
|
||||
sort: sortPreference,
|
||||
})
|
||||
|
||||
const resolvedFolderID = breadcrumbs[breadcrumbs.length - 1]?.id
|
||||
|
||||
@@ -139,13 +131,6 @@ export const buildCollectionFolderView = async (
|
||||
)
|
||||
}
|
||||
|
||||
const newDocumentURL = formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/collections/${collectionSlug}/create`,
|
||||
})
|
||||
|
||||
const hasCreatePermission = permissions?.collections?.[collectionSlug]?.create
|
||||
|
||||
const serverProps: FolderListViewServerPropsOnly = {
|
||||
collectionConfig,
|
||||
documents,
|
||||
@@ -178,34 +163,39 @@ export const buildCollectionFolderView = async (
|
||||
|
||||
return {
|
||||
View: (
|
||||
<FolderProvider
|
||||
breadcrumbs={breadcrumbs}
|
||||
collectionSlug={collectionSlug}
|
||||
documents={documents}
|
||||
folderCollectionSlugs={[collectionSlug]}
|
||||
folderFieldName={config.folders.fieldName}
|
||||
folderID={folderID}
|
||||
search={search}
|
||||
subfolders={subfolders}
|
||||
>
|
||||
<>
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
{RenderServerComponent({
|
||||
clientProps: {
|
||||
// ...folderViewSlots,
|
||||
allCollectionFolderSlugs: [config.folders.slug, collectionSlug],
|
||||
allowCreateCollectionSlugs: [
|
||||
permissions?.collections?.[config.folders.slug]?.create
|
||||
? config.folders.slug
|
||||
: null,
|
||||
permissions?.collections?.[collectionSlug]?.create ? collectionSlug : null,
|
||||
].filter(Boolean),
|
||||
baseFolderPath: `/collections/${collectionSlug}/${config.folders.slug}`,
|
||||
breadcrumbs,
|
||||
collectionSlug,
|
||||
disableBulkDelete,
|
||||
disableBulkEdit,
|
||||
documents,
|
||||
enableRowSelections,
|
||||
hasCreatePermission,
|
||||
newDocumentURL,
|
||||
viewPreference: collectionFolderPreferences?.value?.viewPreference,
|
||||
},
|
||||
Component: collectionConfig?.admin?.components?.views?.list?.Component,
|
||||
folderFieldName: config.folders.fieldName,
|
||||
folderID: resolvedFolderID || null,
|
||||
FolderResultsComponent,
|
||||
search,
|
||||
sort: sortPreference,
|
||||
subfolders,
|
||||
viewPreference,
|
||||
} satisfies FolderListViewClientProps,
|
||||
// Component: collectionConfig?.admin?.components?.views?.Folders?.Component,
|
||||
Fallback: DefaultCollectionFolderView,
|
||||
importMap: payload.importMap,
|
||||
serverProps,
|
||||
})}
|
||||
</FolderProvider>
|
||||
</>
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ const PreviewView: React.FC<Props> = ({
|
||||
: currentEditor !== user?.id) &&
|
||||
!isReadOnlyForIncomingUser &&
|
||||
!showTakeOverModal &&
|
||||
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
!documentLockStateRef.current?.hasShownLockedModal &&
|
||||
!isLockExpired
|
||||
|
||||
|
||||
@@ -75,3 +75,12 @@ export type BuildTableStateArgs = {
|
||||
export type BuildCollectionFolderViewResult = {
|
||||
View: React.ReactNode
|
||||
}
|
||||
|
||||
export type GetFolderResultsComponentAndDataArgs = {
|
||||
activeCollectionSlugs: CollectionSlug[]
|
||||
browseByFolder: boolean
|
||||
displayAs: 'grid' | 'list'
|
||||
folderID: number | string | undefined
|
||||
req: PayloadRequest
|
||||
sort: string
|
||||
}
|
||||
|
||||
@@ -572,6 +572,7 @@ export type {
|
||||
BuildCollectionFolderViewResult,
|
||||
BuildTableStateArgs,
|
||||
DefaultServerFunctionArgs,
|
||||
GetFolderResultsComponentAndDataArgs,
|
||||
ListQuery,
|
||||
ServerFunction,
|
||||
ServerFunctionArgs,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ServerProps } from '../../config/types.js'
|
||||
import type { FolderOrDocument } from '../../folders/types.js'
|
||||
import type { FolderBreadcrumb, FolderOrDocument, FolderSortKeys } from '../../folders/types.js'
|
||||
import type { SanitizedCollectionConfig } from '../../index.js'
|
||||
export type FolderListViewSlots = {
|
||||
AfterFolderList?: React.ReactNode
|
||||
@@ -8,7 +8,6 @@ export type FolderListViewSlots = {
|
||||
BeforeFolderListTable?: React.ReactNode
|
||||
Description?: React.ReactNode
|
||||
listMenuItems?: React.ReactNode[]
|
||||
Table: React.ReactNode
|
||||
}
|
||||
|
||||
export type FolderListViewServerPropsOnly = {
|
||||
@@ -20,13 +19,23 @@ export type FolderListViewServerPropsOnly = {
|
||||
export type FolderListViewServerProps = FolderListViewClientProps & FolderListViewServerPropsOnly
|
||||
|
||||
export type FolderListViewClientProps = {
|
||||
activeCollectionFolderSlugs?: SanitizedCollectionConfig['slug'][]
|
||||
allCollectionFolderSlugs: SanitizedCollectionConfig['slug'][]
|
||||
allowCreateCollectionSlugs: SanitizedCollectionConfig['slug'][]
|
||||
baseFolderPath: `/${string}`
|
||||
beforeActions?: React.ReactNode[]
|
||||
collectionSlug: SanitizedCollectionConfig['slug']
|
||||
breadcrumbs: FolderBreadcrumb[]
|
||||
collectionSlug?: SanitizedCollectionConfig['slug']
|
||||
disableBulkDelete?: boolean
|
||||
disableBulkEdit?: boolean
|
||||
documents: FolderOrDocument[]
|
||||
enableRowSelections?: boolean
|
||||
hasCreatePermission: boolean
|
||||
newDocumentURL: string
|
||||
folderFieldName: string
|
||||
folderID: null | number | string
|
||||
FolderResultsComponent: React.ReactNode
|
||||
search?: string
|
||||
sort?: FolderSortKeys
|
||||
subfolders: FolderOrDocument[]
|
||||
viewPreference: 'grid' | 'list'
|
||||
} & FolderListViewSlots
|
||||
|
||||
|
||||
@@ -113,3 +113,10 @@ export type CollectionFoldersConfiguration = {
|
||||
*/
|
||||
browseByFolder?: boolean
|
||||
}
|
||||
|
||||
type BaseFolderSortKeys = keyof Pick<
|
||||
FolderOrDocument['value'],
|
||||
'_folderOrDocumentTitle' | 'createdAt' | 'updatedAt'
|
||||
>
|
||||
|
||||
export type FolderSortKeys = `-${BaseFolderSortKeys}` | BaseFolderSortKeys
|
||||
|
||||
@@ -1398,6 +1398,7 @@ export type {
|
||||
UploadFieldValidation,
|
||||
UsernameFieldValidation,
|
||||
} from './fields/validations.js'
|
||||
export type { FolderSortKeys } from './folders/types.js'
|
||||
export { getFolderData } from './folders/utils/getFolderData.js'
|
||||
export {
|
||||
type ClientGlobalConfig,
|
||||
|
||||
@@ -56,6 +56,11 @@
|
||||
"types": "./src/utilities/buildTableState.ts",
|
||||
"default": "./src/utilities/buildTableState.ts"
|
||||
},
|
||||
"./utilities/getFolderResultsComponentAndData": {
|
||||
"import": "./src/utilities/getFolderResultsComponentAndData.tsx",
|
||||
"types": "./src/utilities/getFolderResultsComponentAndData.tsx",
|
||||
"default": "./src/utilities/getFolderResultsComponentAndData.tsx"
|
||||
},
|
||||
"./utilities/getClientSchemaMap": {
|
||||
"import": "./src/utilities/getClientSchemaMap.ts",
|
||||
"types": "./src/utilities/getClientSchemaMap.ts",
|
||||
|
||||
@@ -13,8 +13,12 @@ import './index.scss'
|
||||
const baseClass = 'collection-type'
|
||||
|
||||
export function CollectionTypePill() {
|
||||
const { filterItems, folderCollectionSlug, folderCollectionSlugs, visibleCollectionSlugs } =
|
||||
useFolder()
|
||||
const {
|
||||
activeCollectionFolderSlugs: visibleCollectionSlugs,
|
||||
allCollectionFolderSlugs: folderCollectionSlugs,
|
||||
folderCollectionSlug,
|
||||
refineFolderData,
|
||||
} = useFolder()
|
||||
const { i18n, t } = useTranslation()
|
||||
const { config, getEntityConfig } = useConfig()
|
||||
|
||||
@@ -53,8 +57,8 @@ export function CollectionTypePill() {
|
||||
</Button>
|
||||
}
|
||||
key="relation-to-selection-popup"
|
||||
onChange={({ selectedValues }) => {
|
||||
void filterItems({ relationTo: selectedValues })
|
||||
onChange={({ selectedValues: relationTo }) => {
|
||||
void refineFolderData({ query: { relationTo }, updateURL: true })
|
||||
}}
|
||||
options={allCollectionOptions}
|
||||
selectedValues={visibleCollectionSlugs}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Dots } from '../../../icons/Dots/index.js'
|
||||
import { useConfig } from '../../../providers/Config/index.js'
|
||||
import { useFolder } from '../../../providers/Folders/index.js'
|
||||
import { useRouteCache } from '../../../providers/RouteCache/index.js'
|
||||
import { useRouteTransition } from '../../../providers/RouteTransition/index.js'
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
import { ConfirmationModal } from '../../ConfirmationModal/index.js'
|
||||
import { useDocumentDrawer } from '../../DocumentDrawer/index.js'
|
||||
@@ -22,6 +25,8 @@ type Props = {
|
||||
className?: string
|
||||
}
|
||||
export function CurrentFolderActions({ className }: Props) {
|
||||
const router = useRouter()
|
||||
const { startRouteTransition } = useRouteTransition()
|
||||
const {
|
||||
breadcrumbs,
|
||||
currentFolder,
|
||||
@@ -29,15 +34,15 @@ export function CurrentFolderActions({ className }: Props) {
|
||||
folderCollectionSlug,
|
||||
folderFieldName,
|
||||
folderID,
|
||||
getFolderRoute,
|
||||
moveToFolder,
|
||||
renameFolder,
|
||||
setFolderID,
|
||||
} = useFolder()
|
||||
const [FolderDocumentDrawer, , { closeDrawer: closeFolderDrawer, openDrawer: openFolderDrawer }] =
|
||||
useDocumentDrawer({
|
||||
id: folderID,
|
||||
collectionSlug: folderCollectionSlug,
|
||||
})
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
const { config } = useConfig()
|
||||
const { routes, serverURL } = config
|
||||
const { closeModal, openModal } = useModal()
|
||||
@@ -48,8 +53,19 @@ export function CurrentFolderActions({ className }: Props) {
|
||||
credentials: 'include',
|
||||
method: 'DELETE',
|
||||
})
|
||||
await setFolderID({ folderID: breadcrumbs[breadcrumbs.length - 2]?.id || null })
|
||||
}, [breadcrumbs, folderCollectionSlug, folderID, routes.api, serverURL, setFolderID])
|
||||
startRouteTransition(() => {
|
||||
router.push(getFolderRoute(breadcrumbs[breadcrumbs.length - 2]?.id || null))
|
||||
})
|
||||
}, [
|
||||
breadcrumbs,
|
||||
folderCollectionSlug,
|
||||
folderID,
|
||||
getFolderRoute,
|
||||
router,
|
||||
serverURL,
|
||||
routes.api,
|
||||
startRouteTransition,
|
||||
])
|
||||
|
||||
if (!folderID) {
|
||||
return null
|
||||
@@ -143,12 +159,9 @@ export function CurrentFolderActions({ className }: Props) {
|
||||
/>
|
||||
|
||||
<FolderDocumentDrawer
|
||||
onSave={(result) => {
|
||||
renameFolder({
|
||||
folderID: result.doc.id,
|
||||
newName: result.doc[folderCollectionConfig.admin.useAsTitle],
|
||||
})
|
||||
onSave={() => {
|
||||
closeFolderDrawer()
|
||||
clearRouteCache()
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { DocumentDrawerContextProps } from '../../../DocumentDrawer/Provider.js'
|
||||
|
||||
import { useRouteCache } from '../../../../providers/RouteCache/index.js'
|
||||
import { useTranslation } from '../../../../providers/Translation/index.js'
|
||||
import { useDocumentDrawer } from '../../../DocumentDrawer/index.js'
|
||||
import { ListSelectionButton } from '../../../ListSelection/index.js'
|
||||
@@ -7,9 +6,9 @@ import { ListSelectionButton } from '../../../ListSelection/index.js'
|
||||
type EditFolderActionProps = {
|
||||
folderCollectionSlug: string
|
||||
id: number | string
|
||||
onSave: DocumentDrawerContextProps['onSave']
|
||||
}
|
||||
export const EditFolderAction = ({ id, folderCollectionSlug, onSave }: EditFolderActionProps) => {
|
||||
export const EditFolderAction = ({ id, folderCollectionSlug }: EditFolderActionProps) => {
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
const { t } = useTranslation()
|
||||
const [FolderDocumentDrawer, , { closeDrawer, openDrawer }] = useDocumentDrawer({
|
||||
id,
|
||||
@@ -27,9 +26,9 @@ export const EditFolderAction = ({ id, folderCollectionSlug, onSave }: EditFolde
|
||||
</ListSelectionButton>
|
||||
|
||||
<FolderDocumentDrawer
|
||||
onSave={async (args) => {
|
||||
await onSave(args)
|
||||
onSave={() => {
|
||||
closeDrawer()
|
||||
clearRouteCache()
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import type { CollectionSlug, Document } from 'payload'
|
||||
import type {
|
||||
FolderBreadcrumb,
|
||||
FolderDocumentItemKey,
|
||||
FolderOrDocument,
|
||||
GetFolderDataResult,
|
||||
} from 'payload/shared'
|
||||
import type { FolderBreadcrumb, FolderOrDocument } from 'payload/shared'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { formatFolderOrDocumentItem } from 'payload/shared'
|
||||
import { extractID } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
import { useConfig } from '../../../../providers/Config/index.js'
|
||||
import { useAuth } from '../../../../providers/Auth/index.js'
|
||||
import { FolderProvider, useFolder } from '../../../../providers/Folders/index.js'
|
||||
import { useRouteCache } from '../../../../providers/RouteCache/index.js'
|
||||
import { useServerFunctions } from '../../../../providers/ServerFunctions/index.js'
|
||||
import { useTranslation } from '../../../../providers/Translation/index.js'
|
||||
import { Button } from '../../../Button/index.js'
|
||||
import { ConfirmationModal } from '../../../ConfirmationModal/index.js'
|
||||
@@ -28,7 +25,6 @@ import { NoListResults } from '../../../NoListResults/index.js'
|
||||
import { Translation } from '../../../Translation/index.js'
|
||||
import { FolderBreadcrumbs } from '../../Breadcrumbs/index.js'
|
||||
import { ColoredFolderIcon } from '../../ColoredFolderIcon/index.js'
|
||||
import { ItemCardGrid } from '../../ItemCardGrid/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'move-folder-drawer'
|
||||
@@ -59,6 +55,7 @@ export type MoveToFolderDrawerProps = {
|
||||
id: null | number | string
|
||||
name: null | string
|
||||
}) => Promise<void> | void
|
||||
readonly populateMoveToFolderDrawer?: (folderID: null | number | string) => Promise<void> | void
|
||||
/**
|
||||
* Set to `true` to skip the confirmation modal
|
||||
* @default false
|
||||
@@ -75,51 +72,49 @@ export function MoveItemsToFolderDrawer(props: MoveToFolderDrawerProps) {
|
||||
}
|
||||
|
||||
function LoadFolderData(props: MoveToFolderDrawerProps) {
|
||||
const {
|
||||
config: { routes, serverURL },
|
||||
} = useConfig()
|
||||
const { permissions } = useAuth()
|
||||
const [subfolders, setSubfolders] = React.useState<FolderOrDocument[]>([])
|
||||
const [documents, setDocuments] = React.useState<FolderOrDocument[]>([])
|
||||
const [breadcrumbs, setBreadcrumbs] = React.useState<FolderBreadcrumb[]>([])
|
||||
const [FolderResultsComponent, setFolderResultsComponent] = React.useState<React.ReactNode>(null)
|
||||
const [hasLoaded, setHasLoaded] = React.useState(false)
|
||||
const [folderID, setFolderID] = React.useState<null | number | string>(props.fromFolderID || null)
|
||||
const hasLoadedRef = React.useRef(false)
|
||||
const { getFolderResultsComponentAndData } = useServerFunctions()
|
||||
|
||||
React.useEffect(() => {
|
||||
const onLoad = async () => {
|
||||
// call some endpoint to load the data
|
||||
|
||||
const populateMoveToFolderDrawer = React.useCallback(
|
||||
async (folderIDToPopulate: null | number | string) => {
|
||||
try {
|
||||
const folderDataReq = await fetch(
|
||||
`${serverURL}${routes.api}/${props.folderCollectionSlug}/populate-folder-data${props.fromFolderID ? `?folderID=${props.fromFolderID}` : ''}`,
|
||||
{
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
},
|
||||
)
|
||||
const result = await getFolderResultsComponentAndData({
|
||||
activeCollectionSlugs: [props.folderCollectionSlug],
|
||||
browseByFolder: false,
|
||||
displayAs: 'grid',
|
||||
folderID: folderIDToPopulate,
|
||||
sort: '_folderOrDocumentTitle',
|
||||
})
|
||||
|
||||
if (folderDataReq.status === 200) {
|
||||
const folderDataRes: GetFolderDataResult = await folderDataReq.json()
|
||||
setBreadcrumbs(folderDataRes?.breadcrumbs || [])
|
||||
setSubfolders(folderDataRes?.subfolders || [])
|
||||
setDocuments(folderDataRes?.documents || [])
|
||||
} else {
|
||||
setBreadcrumbs([])
|
||||
setSubfolders([])
|
||||
setDocuments([])
|
||||
}
|
||||
setBreadcrumbs(result.breadcrumbs || [])
|
||||
setSubfolders(result?.subfolders || [])
|
||||
setDocuments(result?.documents || [])
|
||||
setFolderResultsComponent(result.FolderResultsComponent || null)
|
||||
setFolderID(folderIDToPopulate)
|
||||
setHasLoaded(true)
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e)
|
||||
setBreadcrumbs([])
|
||||
setSubfolders([])
|
||||
setDocuments([])
|
||||
}
|
||||
|
||||
setHasLoaded(true)
|
||||
}
|
||||
hasLoadedRef.current = true
|
||||
},
|
||||
[getFolderResultsComponentAndData, props.folderCollectionSlug],
|
||||
)
|
||||
|
||||
if (!hasLoaded) {
|
||||
void onLoad()
|
||||
React.useEffect(() => {
|
||||
if (!hasLoadedRef.current) {
|
||||
void populateMoveToFolderDrawer(props.fromFolderID)
|
||||
}
|
||||
}, [props.folderCollectionSlug, routes.api, serverURL, hasLoaded, props.fromFolderID])
|
||||
}, [populateMoveToFolderDrawer, props.fromFolderID])
|
||||
|
||||
if (!hasLoaded) {
|
||||
return <LoadingOverlay />
|
||||
@@ -127,46 +122,58 @@ function LoadFolderData(props: MoveToFolderDrawerProps) {
|
||||
|
||||
return (
|
||||
<FolderProvider
|
||||
allCollectionFolderSlugs={[props.folderCollectionSlug]}
|
||||
allowCreateCollectionSlugs={
|
||||
permissions.collections[props.folderCollectionSlug]?.create
|
||||
? [props.folderCollectionSlug]
|
||||
: []
|
||||
}
|
||||
allowMultiSelection={false}
|
||||
breadcrumbs={breadcrumbs}
|
||||
documents={documents}
|
||||
folderCollectionSlugs={[]}
|
||||
folderFieldName={props.folderFieldName}
|
||||
folderID={props.fromFolderID}
|
||||
folderID={folderID}
|
||||
FolderResultsComponent={FolderResultsComponent}
|
||||
key={folderID}
|
||||
onItemClick={async (item) => {
|
||||
await populateMoveToFolderDrawer(item.value.id)
|
||||
}}
|
||||
subfolders={subfolders}
|
||||
>
|
||||
<Content {...props} />
|
||||
<Content {...props} populateMoveToFolderDrawer={populateMoveToFolderDrawer} />
|
||||
</FolderProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function Content({
|
||||
drawerSlug,
|
||||
fromFolderID,
|
||||
fromFolderName,
|
||||
itemsToMove,
|
||||
onConfirm,
|
||||
populateMoveToFolderDrawer,
|
||||
skipConfirmModal,
|
||||
...props
|
||||
}: MoveToFolderDrawerProps) {
|
||||
const { closeModal, openModal } = useModal()
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
const { closeModal, isModalOpen, openModal } = useModal()
|
||||
const [count] = React.useState(() => itemsToMove.length)
|
||||
const [folderAddedToUnderlyingFolder, setFolderAddedToUnderlyingFolder] = React.useState(false)
|
||||
const { i18n, t } = useTranslation()
|
||||
const {
|
||||
addItems,
|
||||
breadcrumbs,
|
||||
folderCollectionConfig,
|
||||
folderCollectionSlug,
|
||||
folderFieldName,
|
||||
folderID,
|
||||
FolderResultsComponent,
|
||||
getSelectedItems,
|
||||
setFolderID,
|
||||
subfolders,
|
||||
} = useFolder()
|
||||
const [FolderDocumentDrawer, , { closeDrawer: closeFolderDrawer, openDrawer: openFolderDrawer }] =
|
||||
useDocumentDrawer({
|
||||
collectionSlug: folderCollectionSlug,
|
||||
})
|
||||
const { getEntityConfig } = useConfig()
|
||||
|
||||
const getSelectedFolder = React.useCallback((): {
|
||||
id: null | number | string
|
||||
@@ -191,19 +198,19 @@ function Content({
|
||||
}, [breadcrumbs, getSelectedItems])
|
||||
|
||||
const onCreateSuccess = React.useCallback(
|
||||
({ collectionSlug, doc }: { collectionSlug: CollectionSlug; doc: Document }) => {
|
||||
const collectionConfig = getEntityConfig({ collectionSlug })
|
||||
void addItems([
|
||||
formatFolderOrDocumentItem({
|
||||
folderFieldName,
|
||||
isUpload: Boolean(collectionConfig?.upload),
|
||||
relationTo: collectionSlug,
|
||||
useAsTitle: collectionConfig.admin.useAsTitle,
|
||||
value: doc,
|
||||
}),
|
||||
])
|
||||
async ({ collectionSlug, doc }: { collectionSlug: CollectionSlug; doc: Document }) => {
|
||||
await populateMoveToFolderDrawer(folderID)
|
||||
if (
|
||||
collectionSlug === folderCollectionSlug &&
|
||||
((doc?.folder && fromFolderID === extractID(doc?.folder)) ||
|
||||
(!fromFolderID && !doc?.folder))
|
||||
) {
|
||||
// if the folder we created is in the same folder as the one we are moving from
|
||||
// set variable so we can clear the route cache when we close the drawer
|
||||
setFolderAddedToUnderlyingFolder(true)
|
||||
}
|
||||
},
|
||||
[addItems, folderFieldName, getEntityConfig],
|
||||
[populateMoveToFolderDrawer, folderID, fromFolderID, folderCollectionSlug],
|
||||
)
|
||||
|
||||
const onConfirmMove = React.useCallback(() => {
|
||||
@@ -212,6 +219,15 @@ function Content({
|
||||
}
|
||||
}, [getSelectedFolder, onConfirm])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isModalOpen(drawerSlug) && folderAddedToUnderlyingFolder) {
|
||||
// if we added a folder to the underlying folder, clear the route cache
|
||||
// so that the folder view will be reloaded with the new folder
|
||||
setFolderAddedToUnderlyingFolder(false)
|
||||
clearRouteCache()
|
||||
}
|
||||
}, [drawerSlug, isModalOpen, clearRouteCache, folderAddedToUnderlyingFolder])
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerActionHeader
|
||||
@@ -230,7 +246,7 @@ function Content({
|
||||
<DrawerHeading
|
||||
action={props.action}
|
||||
count={count}
|
||||
fromFolderName={props.fromFolderID ? fromFolderName : undefined}
|
||||
fromFolderName={fromFolderID ? fromFolderName : undefined}
|
||||
title={props.action === 'moveItemToFolder' ? props.title : undefined}
|
||||
/>
|
||||
}
|
||||
@@ -249,7 +265,7 @@ function Content({
|
||||
),
|
||||
onClick: breadcrumbs.length
|
||||
? () => {
|
||||
void setFolderID({ folderID: null })
|
||||
void populateMoveToFolderDrawer(null)
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
@@ -259,7 +275,7 @@ function Content({
|
||||
onClick:
|
||||
index !== breadcrumbs.length - 1
|
||||
? () => {
|
||||
void setFolderID({ folderID: crumb.id })
|
||||
void populateMoveToFolderDrawer(crumb.id)
|
||||
}
|
||||
: undefined,
|
||||
})),
|
||||
@@ -284,12 +300,10 @@ function Content({
|
||||
[folderFieldName]: folderID,
|
||||
}}
|
||||
onSave={(result) => {
|
||||
if (typeof onCreateSuccess === 'function') {
|
||||
void onCreateSuccess({
|
||||
collectionSlug: folderCollectionConfig.slug,
|
||||
doc: result.doc,
|
||||
})
|
||||
}
|
||||
void onCreateSuccess({
|
||||
collectionSlug: folderCollectionConfig.slug,
|
||||
doc: result.doc,
|
||||
})
|
||||
closeFolderDrawer()
|
||||
}}
|
||||
redirectAfterCreate={false}
|
||||
@@ -300,14 +314,7 @@ function Content({
|
||||
|
||||
<DrawerContentContainer className={`${baseClass}__body-section`}>
|
||||
{subfolders.length > 0 ? (
|
||||
<ItemCardGrid
|
||||
disabledItemKeys={new Set(itemsToMove.map(({ itemKey }) => itemKey))}
|
||||
items={subfolders}
|
||||
selectedItemKeys={
|
||||
new Set<FolderDocumentItemKey>([`${folderCollectionSlug}-${getSelectedFolder().id}`])
|
||||
}
|
||||
type="folder"
|
||||
/>
|
||||
FolderResultsComponent
|
||||
) : (
|
||||
<NoListResults
|
||||
Actions={[
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import type { FolderOrDocument } from 'payload/shared'
|
||||
|
||||
import { useDroppable } from '@dnd-kit/core'
|
||||
import React from 'react'
|
||||
|
||||
import { DocumentIcon } from '../../../icons/Document/index.js'
|
||||
import { ThreeDotsIcon } from '../../../icons/ThreeDots/index.js'
|
||||
import { useFolder } from '../../../providers/Folders/index.js'
|
||||
import { Popup } from '../../Popup/index.js'
|
||||
import { Thumbnail } from '../../Thumbnail/index.js'
|
||||
import { ColoredFolderIcon } from '../ColoredFolderIcon/index.js'
|
||||
@@ -127,3 +130,41 @@ export function FolderFileCard({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type ContextCardProps = {
|
||||
readonly className?: string
|
||||
readonly index: number // todo: possibly remove
|
||||
readonly item: FolderOrDocument
|
||||
readonly type: 'file' | 'folder'
|
||||
}
|
||||
export function ContextFolderFileCard({ type, className, index, item }: ContextCardProps) {
|
||||
const {
|
||||
focusedRowIndex,
|
||||
isDragging,
|
||||
itemKeysToMove,
|
||||
onItemClick,
|
||||
onItemKeyPress,
|
||||
selectedItemKeys,
|
||||
} = useFolder()
|
||||
const isSelected = selectedItemKeys.has(item.itemKey)
|
||||
|
||||
return (
|
||||
<FolderFileCard
|
||||
className={className}
|
||||
disabled={(isDragging && isSelected) || itemKeysToMove.has(item.itemKey)}
|
||||
id={item.value.id}
|
||||
isFocused={focusedRowIndex === index}
|
||||
isSelected={isSelected}
|
||||
itemKey={item.itemKey}
|
||||
onClick={(event) => {
|
||||
void onItemClick({ event, index, item })
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
void onItemKeyPress({ event, index, item })
|
||||
}}
|
||||
previewUrl={item.value.url}
|
||||
title={item.value._folderOrDocumentTitle}
|
||||
type={type}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { FolderDocumentItemKey, FolderOrDocument } from 'payload/shared'
|
||||
'use client'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { extractID } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
import type { FormatDateArgs } from '../../../utilities/formatDocTitle/formatDateTitle.js'
|
||||
|
||||
import { DocumentIcon } from '../../../icons/Document/index.js'
|
||||
import { useConfig } from '../../../providers/Config/index.js'
|
||||
import { useFolder } from '../../../providers/Folders/index.js'
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
import { formatDate } from '../../../utilities/formatDocTitle/formatDateTitle.js'
|
||||
import { ColoredFolderIcon } from '../ColoredFolderIcon/index.js'
|
||||
@@ -19,42 +17,21 @@ import './index.scss'
|
||||
const baseClass = 'folder-file-table'
|
||||
|
||||
type Props = {
|
||||
dateFormat: FormatDateArgs['pattern']
|
||||
disabledItems?: Set<FolderDocumentItemKey>
|
||||
documents: FolderOrDocument[]
|
||||
focusedRowIndex: number
|
||||
i18n: I18nClient
|
||||
isMovingItems: boolean
|
||||
onRowClick: (args: {
|
||||
event: React.MouseEvent<Element>
|
||||
index: number
|
||||
item: FolderOrDocument
|
||||
}) => void
|
||||
onRowPress: (args: {
|
||||
event: React.KeyboardEvent<Element>
|
||||
index: number
|
||||
item: FolderOrDocument
|
||||
}) => void
|
||||
selectedItems: Set<FolderDocumentItemKey>
|
||||
showRelationCell?: boolean
|
||||
subfolders: FolderOrDocument[]
|
||||
}
|
||||
|
||||
export function FolderFileTable({
|
||||
dateFormat,
|
||||
disabledItems = new Set(),
|
||||
documents,
|
||||
focusedRowIndex,
|
||||
i18n,
|
||||
isMovingItems,
|
||||
onRowClick,
|
||||
onRowPress,
|
||||
selectedItems,
|
||||
showRelationCell = true,
|
||||
subfolders,
|
||||
}: Props) {
|
||||
export function FolderFileTable({ showRelationCell = true }: Props) {
|
||||
const {
|
||||
documents,
|
||||
focusedRowIndex,
|
||||
isDragging,
|
||||
onItemClick,
|
||||
onItemKeyPress,
|
||||
selectedItemKeys,
|
||||
subfolders,
|
||||
} = useFolder()
|
||||
const { config } = useConfig()
|
||||
const { t } = useTranslation()
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
const [relationToMap] = React.useState(() => {
|
||||
const map: Record<string, string> = {}
|
||||
@@ -109,7 +86,11 @@ export function FolderFileTable({
|
||||
}
|
||||
|
||||
if ((name === 'createdAt' || name === 'updatedAt') && value[name]) {
|
||||
cellValue = formatDate({ date: value[name], i18n, pattern: dateFormat })
|
||||
cellValue = formatDate({
|
||||
date: value[name],
|
||||
i18n,
|
||||
pattern: config.admin.dateFormat,
|
||||
})
|
||||
}
|
||||
|
||||
if (name === 'type') {
|
||||
@@ -127,9 +108,7 @@ export function FolderFileTable({
|
||||
return cellValue
|
||||
}
|
||||
})}
|
||||
disabled={
|
||||
(isMovingItems && selectedItems?.has(itemKey)) || disabledItems?.has(itemKey)
|
||||
}
|
||||
disabled={isDragging && selectedItemKeys?.has(itemKey)}
|
||||
dragData={{
|
||||
id: subfolderID,
|
||||
type: 'folder',
|
||||
@@ -137,19 +116,19 @@ export function FolderFileTable({
|
||||
id={subfolderID}
|
||||
isDroppable
|
||||
isFocused={focusedRowIndex === rowIndex}
|
||||
isSelected={selectedItems.has(itemKey)}
|
||||
isSelecting={selectedItems.size > 0}
|
||||
isSelected={selectedItemKeys.has(itemKey)}
|
||||
isSelecting={selectedItemKeys.size > 0}
|
||||
itemKey={itemKey}
|
||||
key={`${rowIndex}-${itemKey}`}
|
||||
onClick={(event) => {
|
||||
void onRowClick({
|
||||
void onItemClick({
|
||||
event,
|
||||
index: rowIndex,
|
||||
item: subfolder,
|
||||
})
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
void onRowPress({
|
||||
void onItemKeyPress({
|
||||
event,
|
||||
index: rowIndex,
|
||||
item: subfolder,
|
||||
@@ -173,7 +152,11 @@ export function FolderFileTable({
|
||||
}
|
||||
|
||||
if ((name === 'createdAt' || name === 'updatedAt') && value[name]) {
|
||||
cellValue = formatDate({ date: value[name], i18n, pattern: dateFormat })
|
||||
cellValue = formatDate({
|
||||
date: value[name],
|
||||
i18n,
|
||||
pattern: config.admin.dateFormat,
|
||||
})
|
||||
}
|
||||
|
||||
if (name === 'type') {
|
||||
@@ -191,26 +174,26 @@ export function FolderFileTable({
|
||||
return cellValue
|
||||
}
|
||||
})}
|
||||
disabled={isMovingItems || disabledItems?.has(itemKey)}
|
||||
disabled={isDragging || selectedItemKeys?.has(itemKey)}
|
||||
dragData={{
|
||||
id: documentID,
|
||||
type: 'document',
|
||||
}}
|
||||
id={documentID}
|
||||
isFocused={focusedRowIndex === rowIndex}
|
||||
isSelected={selectedItems.has(itemKey)}
|
||||
isSelecting={selectedItems.size > 0}
|
||||
isSelected={selectedItemKeys.has(itemKey)}
|
||||
isSelecting={selectedItemKeys.size > 0}
|
||||
itemKey={itemKey}
|
||||
key={`${rowIndex}-${itemKey}`}
|
||||
onClick={(event) => {
|
||||
void onRowClick({
|
||||
void onItemClick({
|
||||
event,
|
||||
index: rowIndex,
|
||||
item: document,
|
||||
})
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
void onRowPress({
|
||||
void onItemKeyPress({
|
||||
event,
|
||||
index: rowIndex,
|
||||
item: document,
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import type { FolderDocumentItemKey, FolderOrDocument } from 'payload/shared'
|
||||
import type { FolderOrDocument } from 'payload/shared'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { useFolder } from '../../../providers/Folders/index.js'
|
||||
import { FolderFileCard } from '../FolderFileCard/index.js'
|
||||
import { ContextFolderFileCard } from '../FolderFileCard/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'item-card-grid'
|
||||
|
||||
type ItemCardGridProps = {
|
||||
disabledItemKeys?: Set<FolderDocumentItemKey>
|
||||
items: FolderOrDocument[]
|
||||
RenderActionGroup?: (args: { index: number; item: FolderOrDocument }) => React.ReactNode
|
||||
selectedItemKeys: Set<FolderDocumentItemKey>
|
||||
title?: string
|
||||
} & (
|
||||
| {
|
||||
@@ -26,17 +22,7 @@ type ItemCardGridProps = {
|
||||
type: 'folder'
|
||||
}
|
||||
)
|
||||
export function ItemCardGrid({
|
||||
type,
|
||||
disabledItemKeys,
|
||||
items,
|
||||
RenderActionGroup,
|
||||
selectedItemKeys,
|
||||
subfolderCount,
|
||||
title,
|
||||
}: ItemCardGridProps) {
|
||||
const { focusedRowIndex, isDragging, onItemClick, onItemKeyPress, selectedIndexes } = useFolder()
|
||||
|
||||
export function ItemCardGrid({ type, items, subfolderCount, title }: ItemCardGridProps) {
|
||||
return (
|
||||
<>
|
||||
{title && <p className={`${baseClass}__title`}>{title}</p>}
|
||||
@@ -45,38 +31,9 @@ export function ItemCardGrid({
|
||||
? null
|
||||
: items.map((item, _index) => {
|
||||
const index = _index + (subfolderCount || 0)
|
||||
const { itemKey, value } = item
|
||||
const { itemKey } = item
|
||||
|
||||
return (
|
||||
<FolderFileCard
|
||||
disabled={
|
||||
(isDragging && selectedItemKeys.has(itemKey)) || disabledItemKeys?.has(itemKey)
|
||||
}
|
||||
id={value.id}
|
||||
isFocused={focusedRowIndex === index}
|
||||
isSelected={selectedItemKeys.has(itemKey)}
|
||||
itemKey={itemKey}
|
||||
key={itemKey}
|
||||
onClick={(event) => {
|
||||
void onItemClick({ event, index, item })
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
void onItemKeyPress({ event, index, item })
|
||||
}}
|
||||
PopupActions={
|
||||
RenderActionGroup
|
||||
? RenderActionGroup({
|
||||
index,
|
||||
item,
|
||||
})
|
||||
: null
|
||||
}
|
||||
previewUrl={value?.url}
|
||||
selectedCount={selectedIndexes.size}
|
||||
title={value._folderOrDocumentTitle}
|
||||
type={type}
|
||||
/>
|
||||
)
|
||||
return <ContextFolderFileCard index={index} item={item} key={itemKey} type={type} />
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -45,9 +45,12 @@ const orderOnOptions: {
|
||||
},
|
||||
]
|
||||
export function SortByPill() {
|
||||
const { documents, sortAndUpdateState, sortDirection, sortOn, subfolders } = useFolder()
|
||||
const { refineFolderData, sort } = useFolder()
|
||||
const { t } = useTranslation()
|
||||
const [selectedSortOption] = sortOnOptions.filter(({ value }) => value === sortOn)
|
||||
const sortDirection = sort.startsWith('-') ? 'desc' : 'asc'
|
||||
const [selectedSortOption] = sortOnOptions.filter(
|
||||
({ value }) => value === (sort.startsWith('-') ? sort.slice(1) : sort),
|
||||
)
|
||||
const [selectedOrderOption] = orderOnOptions.filter(({ value }) => value === sortDirection)
|
||||
|
||||
return (
|
||||
@@ -73,13 +76,11 @@ export function SortByPill() {
|
||||
active={selectedSortOption.value === value}
|
||||
key={value}
|
||||
onClick={() => {
|
||||
sortAndUpdateState({
|
||||
documentsToSort: documents,
|
||||
sortOn: value as keyof Pick<
|
||||
FolderOrDocument['value'],
|
||||
'_folderOrDocumentTitle' | 'createdAt' | 'updatedAt'
|
||||
>,
|
||||
subfoldersToSort: subfolders,
|
||||
refineFolderData({
|
||||
query: {
|
||||
sort: value,
|
||||
},
|
||||
updateURL: true,
|
||||
})
|
||||
close()
|
||||
}}
|
||||
@@ -97,11 +98,14 @@ export function SortByPill() {
|
||||
className={`${baseClass}__order-option`}
|
||||
key={value}
|
||||
onClick={() => {
|
||||
sortAndUpdateState({
|
||||
documentsToSort: documents,
|
||||
sortDirection: value,
|
||||
subfoldersToSort: subfolders,
|
||||
})
|
||||
if (value === 'asc') {
|
||||
refineFolderData({
|
||||
query: {
|
||||
sort: value === 'asc' ? `-${sort}` : sort,
|
||||
},
|
||||
updateURL: true,
|
||||
})
|
||||
}
|
||||
close()
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -118,12 +118,12 @@ export function ListCreateNewDocInFolderButton({
|
||||
initialData={{
|
||||
[folderFieldName]: folderID,
|
||||
}}
|
||||
onSave={({ doc }) => {
|
||||
closeModal(newDocInFolderDrawerSlug)
|
||||
void onCreateSuccess({
|
||||
onSave={async ({ doc }) => {
|
||||
await onCreateSuccess({
|
||||
collectionSlug: createCollectionSlug,
|
||||
doc,
|
||||
})
|
||||
closeModal(newDocInFolderDrawerSlug)
|
||||
}}
|
||||
redirectAfterCreate={false}
|
||||
/>
|
||||
@@ -134,13 +134,11 @@ export function ListCreateNewDocInFolderButton({
|
||||
initialData={{
|
||||
[folderFieldName]: folderID,
|
||||
}}
|
||||
onSave={(result) => {
|
||||
if (typeof onCreateSuccess === 'function') {
|
||||
void onCreateSuccess({
|
||||
collectionSlug: folderCollectionConfig.slug,
|
||||
doc: result.doc,
|
||||
})
|
||||
}
|
||||
onSave={async (result) => {
|
||||
await onCreateSuccess({
|
||||
collectionSlug: folderCollectionConfig.slug,
|
||||
doc: result.doc,
|
||||
})
|
||||
closeFolderDrawer()
|
||||
}}
|
||||
redirectAfterCreate={false}
|
||||
|
||||
@@ -4,6 +4,7 @@ export { FolderEditField } from '../../elements/FolderView/Field/index.server.js
|
||||
export { File } from '../../graphics/File/index.js'
|
||||
export { CheckIcon } from '../../icons/Check/index.js'
|
||||
export { copyDataFromLocaleHandler } from '../../utilities/copyDataFromLocale.js'
|
||||
export { getFolderResultsComponentAndData } from '../../utilities/getFolderResultsComponentAndData.js'
|
||||
export { renderFilters, renderTable } from '../../utilities/renderTable.js'
|
||||
export { resolveFilterOptions } from '../../utilities/resolveFilterOptions.js'
|
||||
export { upsertPreferences } from '../../utilities/upsertPreferences.js'
|
||||
|
||||
@@ -101,7 +101,8 @@ export const RelationshipInput: React.FC<RelationshipInputProps> = (props) => {
|
||||
|
||||
const valueRef = useRef(value)
|
||||
// the line below seems odd
|
||||
|
||||
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
valueRef.current = value
|
||||
|
||||
const [DocumentDrawer, , { isDrawerOpen, openDrawer }] = useDocumentDrawer({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ import type {
|
||||
Data,
|
||||
DocumentSlots,
|
||||
ErrorResult,
|
||||
GetFolderResultsComponentAndDataArgs,
|
||||
Locale,
|
||||
ServerFunctionClient,
|
||||
} from 'payload'
|
||||
@@ -13,6 +14,7 @@ import React, { createContext, useCallback } from 'react'
|
||||
import type { buildFormStateHandler } from '../../utilities/buildFormState.js'
|
||||
import type { buildTableStateHandler } from '../../utilities/buildTableState.js'
|
||||
import type { CopyDataFromLocaleArgs } from '../../utilities/copyDataFromLocale.js'
|
||||
import type { getFolderResultsComponentAndDataHandler } from '../../utilities/getFolderResultsComponentAndData.js'
|
||||
import type {
|
||||
schedulePublishHandler,
|
||||
SchedulePublishHandlerArgs,
|
||||
@@ -63,9 +65,16 @@ type GetDocumentSlots = (args: {
|
||||
signal?: AbortSignal
|
||||
}) => Promise<DocumentSlots>
|
||||
|
||||
type GetFolderResultsComponentAndDataClient = (
|
||||
args: {
|
||||
signal?: AbortSignal
|
||||
} & Omit<GetFolderResultsComponentAndDataArgs, 'req'>,
|
||||
) => ReturnType<typeof getFolderResultsComponentAndDataHandler>
|
||||
|
||||
type ServerFunctionsContextType = {
|
||||
copyDataFromLocale: CopyDataFromLocaleClient
|
||||
getDocumentSlots: GetDocumentSlots
|
||||
getFolderResultsComponentAndData: GetFolderResultsComponentAndDataClient
|
||||
getFormState: GetFormStateClient
|
||||
getTableState: GetTableStateClient
|
||||
renderDocument: RenderDocument
|
||||
@@ -218,11 +227,32 @@ export const ServerFunctionsProvider: React.FC<{
|
||||
[serverFunction],
|
||||
)
|
||||
|
||||
const getFolderResultsComponentAndData = useCallback<GetFolderResultsComponentAndDataClient>(
|
||||
async (args) => {
|
||||
const { signal: remoteSignal, ...rest } = args || {}
|
||||
|
||||
try {
|
||||
const result = (await serverFunction({
|
||||
name: 'get-folder-results-component-and-data',
|
||||
args: rest,
|
||||
})) as Awaited<ReturnType<typeof getFolderResultsComponentAndDataHandler>>
|
||||
|
||||
if (!remoteSignal?.aborted) {
|
||||
return result
|
||||
}
|
||||
} catch (_err) {
|
||||
console.error(_err) // eslint-disable-line no-console
|
||||
}
|
||||
},
|
||||
[serverFunction],
|
||||
)
|
||||
|
||||
return (
|
||||
<ServerFunctionsContext
|
||||
value={{
|
||||
copyDataFromLocale,
|
||||
getDocumentSlots,
|
||||
getFolderResultsComponentAndData,
|
||||
getFormState,
|
||||
getTableState,
|
||||
renderDocument,
|
||||
|
||||
@@ -100,7 +100,7 @@ export const TableColumnsProvider: React.FC<TableColumnsProviderProps> = ({
|
||||
resetColumnsState,
|
||||
setActiveColumns,
|
||||
toggleColumn,
|
||||
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
...contextRef.current,
|
||||
}}
|
||||
>
|
||||
|
||||
170
packages/ui/src/utilities/getFolderResultsComponentAndData.tsx
Normal file
170
packages/ui/src/utilities/getFolderResultsComponentAndData.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import type {
|
||||
CollectionSlug,
|
||||
ErrorResult,
|
||||
GetFolderResultsComponentAndDataArgs,
|
||||
Where,
|
||||
} from 'payload'
|
||||
import type { FolderBreadcrumb, FolderOrDocument } from 'payload/shared'
|
||||
|
||||
import { APIError, formatErrors, getFolderData } from 'payload'
|
||||
import { buildFolderWhereConstraints } from 'payload/shared'
|
||||
|
||||
import { FolderFileTable } from '../elements/FolderView/FolderFileTable/index.js'
|
||||
import { ItemCardGrid } from '../elements/FolderView/ItemCardGrid/index.js'
|
||||
|
||||
type GetFolderResultsComponentAndDataResult = {
|
||||
breadcrumbs?: FolderBreadcrumb[]
|
||||
documents?: FolderOrDocument[]
|
||||
FolderResultsComponent: React.ReactNode
|
||||
subfolders?: FolderOrDocument[]
|
||||
}
|
||||
|
||||
type GetFolderResultsComponentAndDataErrorResult = {
|
||||
breadcrumbs?: never
|
||||
documents?: never
|
||||
FolderResultsComponent?: never
|
||||
subfolders?: never
|
||||
} & (
|
||||
| {
|
||||
message: string
|
||||
}
|
||||
| ErrorResult
|
||||
)
|
||||
|
||||
export const getFolderResultsComponentAndDataHandler = async (
|
||||
args: GetFolderResultsComponentAndDataArgs,
|
||||
): Promise<
|
||||
GetFolderResultsComponentAndDataErrorResult | GetFolderResultsComponentAndDataResult
|
||||
> => {
|
||||
const { req } = args
|
||||
|
||||
try {
|
||||
const res = await getFolderResultsComponentAndData(args)
|
||||
return res
|
||||
} catch (err) {
|
||||
req.payload.logger.error({ err, msg: `There was an error building form state` })
|
||||
|
||||
if (err.message === 'Could not find field schema for given path') {
|
||||
return {
|
||||
message: err.message,
|
||||
}
|
||||
}
|
||||
|
||||
if (err.message === 'Unauthorized') {
|
||||
return null
|
||||
}
|
||||
|
||||
return formatErrors(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is responsible for fetching folder data, building the results component
|
||||
* and returns the data and component together.
|
||||
*
|
||||
*
|
||||
* Open ended questions:
|
||||
* - If we rerender the results section, does the provider update?? I dont think so, if the provider is on the server.
|
||||
* Maybe we should move the provider to the client.
|
||||
*/
|
||||
export const getFolderResultsComponentAndData = async ({
|
||||
activeCollectionSlugs,
|
||||
browseByFolder,
|
||||
displayAs,
|
||||
folderID = undefined,
|
||||
req,
|
||||
sort,
|
||||
}: GetFolderResultsComponentAndDataArgs): Promise<GetFolderResultsComponentAndDataResult> => {
|
||||
const { payload } = req
|
||||
|
||||
if (!payload.config.folders) {
|
||||
throw new APIError('Folders are not enabled in the configuration.')
|
||||
}
|
||||
|
||||
let collectionSlug: CollectionSlug | undefined = undefined
|
||||
let documentWhere: undefined | Where = undefined
|
||||
let folderWhere: undefined | Where = undefined
|
||||
|
||||
// todo(perf): - collect promises and resolve them in parallel
|
||||
for (const activeCollectionSlug of activeCollectionSlugs) {
|
||||
if (activeCollectionSlug === payload.config.folders.slug) {
|
||||
const folderCollectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig: payload.collections[activeCollectionSlug].config,
|
||||
folderID,
|
||||
localeCode: req?.locale,
|
||||
req,
|
||||
search: typeof req?.query?.search === 'string' ? req.query.search : undefined,
|
||||
sort,
|
||||
})
|
||||
|
||||
if (folderCollectionConstraints) {
|
||||
folderWhere = folderCollectionConstraints
|
||||
}
|
||||
} else if ((browseByFolder && folderID) || !browseByFolder) {
|
||||
if (!browseByFolder) {
|
||||
collectionSlug = activeCollectionSlug
|
||||
}
|
||||
|
||||
if (!documentWhere) {
|
||||
documentWhere = {
|
||||
or: [],
|
||||
}
|
||||
}
|
||||
|
||||
const collectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig: payload.collections[activeCollectionSlug].config,
|
||||
folderID,
|
||||
localeCode: req?.locale,
|
||||
req,
|
||||
search: typeof req?.query?.search === 'string' ? req.query.search : undefined,
|
||||
sort,
|
||||
})
|
||||
|
||||
if (collectionConstraints) {
|
||||
documentWhere.or.push(collectionConstraints)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const folderData = await getFolderData({
|
||||
collectionSlug,
|
||||
documentWhere,
|
||||
folderID,
|
||||
folderWhere,
|
||||
req,
|
||||
})
|
||||
|
||||
let FolderResultsComponent = null
|
||||
|
||||
if (displayAs === 'grid') {
|
||||
FolderResultsComponent = (
|
||||
<div>
|
||||
{folderData.subfolders.length ? (
|
||||
<>
|
||||
<ItemCardGrid items={folderData.subfolders} title={'Folders'} type="folder" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{folderData.documents.length ? (
|
||||
<>
|
||||
<ItemCardGrid
|
||||
items={folderData.documents}
|
||||
subfolderCount={folderData.subfolders.length}
|
||||
title={'Documents'}
|
||||
type="file"
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
FolderResultsComponent = <FolderFileTable showRelationCell={browseByFolder} />
|
||||
}
|
||||
|
||||
return {
|
||||
breadcrumbs: folderData.breadcrumbs,
|
||||
documents: folderData.documents,
|
||||
FolderResultsComponent,
|
||||
subfolders: folderData.subfolders,
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
'use client'
|
||||
|
||||
import type { DragEndEvent } from '@dnd-kit/core'
|
||||
import type { CollectionSlug, Document, FolderListViewClientProps } from 'payload'
|
||||
import type { FolderDocumentItemKey } from 'payload/shared'
|
||||
import type { FolderListViewClientProps } from 'payload'
|
||||
|
||||
import { useDndMonitor } from '@dnd-kit/core'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { formatFolderOrDocumentItem } from 'payload/shared'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { DroppableBreadcrumb } from '../../elements/FolderView/Breadcrumbs/index.js'
|
||||
@@ -26,9 +25,10 @@ import { SearchBar } from '../../elements/SearchBar/index.js'
|
||||
import { useStepNav } from '../../elements/StepNav/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useEditDepth } from '../../providers/EditDepth/index.js'
|
||||
import { useFolder } from '../../providers/Folders/index.js'
|
||||
import { FolderProvider, useFolder } from '../../providers/Folders/index.js'
|
||||
import { usePreferences } from '../../providers/Preferences/index.js'
|
||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||
import { useRouteTransition } from '../../providers/RouteTransition/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { useWindowInfo } from '../../providers/WindowInfo/index.js'
|
||||
import { ListSelection } from '../CollectionFolder/ListSelection/index.js'
|
||||
@@ -36,12 +36,54 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'folder-list'
|
||||
|
||||
export function DefaultBrowseByFolderView(
|
||||
props: {
|
||||
hasCreatePermissionCollectionSlugs?: string[]
|
||||
selectedCollectionSlugs?: string[]
|
||||
} & FolderListViewClientProps,
|
||||
) {
|
||||
export function DefaultBrowseByFolderView({
|
||||
activeCollectionFolderSlugs,
|
||||
allCollectionFolderSlugs,
|
||||
allowCreateCollectionSlugs,
|
||||
baseFolderPath,
|
||||
breadcrumbs,
|
||||
documents,
|
||||
folderFieldName,
|
||||
folderID,
|
||||
FolderResultsComponent,
|
||||
search,
|
||||
subfolders,
|
||||
...restOfProps
|
||||
}: FolderListViewClientProps) {
|
||||
return (
|
||||
<FolderProvider
|
||||
activeCollectionFolderSlugs={activeCollectionFolderSlugs}
|
||||
allCollectionFolderSlugs={allCollectionFolderSlugs}
|
||||
allowCreateCollectionSlugs={allowCreateCollectionSlugs}
|
||||
baseFolderPath={baseFolderPath}
|
||||
breadcrumbs={breadcrumbs}
|
||||
documents={documents}
|
||||
folderFieldName={folderFieldName}
|
||||
folderID={folderID}
|
||||
FolderResultsComponent={FolderResultsComponent}
|
||||
search={search}
|
||||
subfolders={subfolders}
|
||||
>
|
||||
<BrowseByFolderViewInContext {...restOfProps} />
|
||||
</FolderProvider>
|
||||
)
|
||||
}
|
||||
|
||||
type BrowseByFolderViewInContextProps = Omit<
|
||||
FolderListViewClientProps,
|
||||
| 'activeCollectionFolderSlugs'
|
||||
| 'allCollectionFolderSlugs'
|
||||
| 'allowCreateCollectionSlugs'
|
||||
| 'baseFolderPath'
|
||||
| 'breadcrumbs'
|
||||
| 'documents'
|
||||
| 'folderFieldName'
|
||||
| 'folderID'
|
||||
| 'FolderResultsComponent'
|
||||
| 'subfolders'
|
||||
>
|
||||
|
||||
function BrowseByFolderViewInContext(props: BrowseByFolderViewInContextProps) {
|
||||
const {
|
||||
AfterFolderList,
|
||||
AfterFolderListTable,
|
||||
@@ -50,40 +92,36 @@ export function DefaultBrowseByFolderView(
|
||||
Description,
|
||||
disableBulkDelete,
|
||||
disableBulkEdit,
|
||||
hasCreatePermissionCollectionSlugs,
|
||||
viewPreference,
|
||||
} = props
|
||||
|
||||
const { config, getEntityConfig } = useConfig()
|
||||
const router = useRouter()
|
||||
const { getEntityConfig } = useConfig()
|
||||
const { i18n, t } = useTranslation()
|
||||
const drawerDepth = useEditDepth()
|
||||
const { setStepNav } = useStepNav()
|
||||
const { startRouteTransition } = useRouteTransition()
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
const {
|
||||
breakpoints: { s: smallBreak },
|
||||
} = useWindowInfo()
|
||||
const { setPreference } = usePreferences()
|
||||
const {
|
||||
addItems,
|
||||
activeCollectionFolderSlugs: visibleCollectionSlugs,
|
||||
allowCreateCollectionSlugs,
|
||||
breadcrumbs,
|
||||
documents,
|
||||
filterItems,
|
||||
focusedRowIndex,
|
||||
folderCollectionConfig,
|
||||
folderFieldName,
|
||||
folderID,
|
||||
getFolderRoute,
|
||||
getSelectedItems,
|
||||
isDragging,
|
||||
lastSelectedIndex,
|
||||
moveToFolder,
|
||||
onItemClick,
|
||||
onItemKeyPress,
|
||||
refineFolderData,
|
||||
search,
|
||||
selectedIndexes,
|
||||
setFolderID,
|
||||
selectedItemKeys,
|
||||
setIsDragging,
|
||||
subfolders,
|
||||
visibleCollectionSlugs,
|
||||
} = useFolder()
|
||||
|
||||
const [activeView, setActiveView] = React.useState<'grid' | 'list'>(viewPreference || 'grid')
|
||||
@@ -129,35 +167,6 @@ export function DefaultBrowseByFolderView(
|
||||
return `${acc}, ${getTranslation(collectionConfig.labels?.plural, i18n)}`
|
||||
}, '')
|
||||
|
||||
const onCreateSuccess = React.useCallback(
|
||||
({ collectionSlug, doc }: { collectionSlug: CollectionSlug; doc: Document }) => {
|
||||
const collectionConfig = getEntityConfig({ collectionSlug })
|
||||
void addItems([
|
||||
formatFolderOrDocumentItem({
|
||||
folderFieldName,
|
||||
isUpload: Boolean(collectionConfig?.upload),
|
||||
relationTo: collectionSlug,
|
||||
useAsTitle: collectionConfig.admin.useAsTitle,
|
||||
value: doc,
|
||||
}),
|
||||
])
|
||||
},
|
||||
[getEntityConfig, addItems, folderFieldName],
|
||||
)
|
||||
|
||||
const selectedItemKeys = React.useMemo(() => {
|
||||
return new Set(
|
||||
getSelectedItems().reduce<FolderDocumentItemKey[]>((acc, item) => {
|
||||
if (item) {
|
||||
if (item.relationTo && item.value) {
|
||||
acc.push(`${item.relationTo}-${item.value.id}`)
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, []),
|
||||
)
|
||||
}, [getSelectedItems])
|
||||
|
||||
const handleSetViewType = React.useCallback(
|
||||
(view: 'grid' | 'list') => {
|
||||
void setPreference('browse-by-folder', {
|
||||
@@ -192,7 +201,9 @@ export function DefaultBrowseByFolderView(
|
||||
id={null}
|
||||
key="root"
|
||||
onClick={() => {
|
||||
void setFolderID({ folderID: null })
|
||||
startRouteTransition(() => {
|
||||
router.push(getFolderRoute(null))
|
||||
})
|
||||
}}
|
||||
>
|
||||
<ColoredFolderIcon />
|
||||
@@ -211,7 +222,9 @@ export function DefaultBrowseByFolderView(
|
||||
id={crumb.id}
|
||||
key={crumb.id}
|
||||
onClick={() => {
|
||||
void setFolderID({ folderID: crumb.id })
|
||||
startRouteTransition(() => {
|
||||
router.push(getFolderRoute(crumb.id))
|
||||
})
|
||||
}}
|
||||
>
|
||||
{crumb.name}
|
||||
@@ -221,7 +234,7 @@ export function DefaultBrowseByFolderView(
|
||||
}),
|
||||
])
|
||||
}
|
||||
}, [setStepNav, drawerDepth, i18n, breadcrumbs, setFolderID, t])
|
||||
}, [breadcrumbs, drawerDepth, getFolderRoute, router, setStepNav, startRouteTransition, t])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -242,13 +255,15 @@ export function DefaultBrowseByFolderView(
|
||||
AfterListHeaderContent={Description}
|
||||
title={listHeaderTitle}
|
||||
TitleActions={[
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={t('general:createNew')}
|
||||
collectionSlugs={hasCreatePermissionCollectionSlugs}
|
||||
key="create-new-button"
|
||||
onCreateSuccess={onCreateSuccess}
|
||||
slugPrefix="create-document--header-pill"
|
||||
/>,
|
||||
allowCreateCollectionSlugs.length && (
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={t('general:createNew')}
|
||||
collectionSlugs={allowCreateCollectionSlugs}
|
||||
key="create-new-button"
|
||||
onCreateSuccess={clearRouteCache}
|
||||
slugPrefix="create-document--header-pill"
|
||||
/>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
/>
|
||||
<SearchBar
|
||||
@@ -263,7 +278,7 @@ export function DefaultBrowseByFolderView(
|
||||
<CurrentFolderActions key="current-folder-actions" />,
|
||||
].filter(Boolean)}
|
||||
label={searchPlaceholder}
|
||||
onSearchChange={(search) => filterItems({ search })}
|
||||
onSearchChange={(search) => refineFolderData({ query: { search }, updateURL: true })}
|
||||
searchQueryParam={search}
|
||||
/>
|
||||
{BeforeFolderListTable}
|
||||
@@ -273,12 +288,7 @@ export function DefaultBrowseByFolderView(
|
||||
<div>
|
||||
{subfolders.length ? (
|
||||
<>
|
||||
<ItemCardGrid
|
||||
items={subfolders}
|
||||
selectedItemKeys={selectedItemKeys}
|
||||
title={'Folders'}
|
||||
type="folder"
|
||||
/>
|
||||
<ItemCardGrid items={subfolders} title={'Folders'} type="folder" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -286,7 +296,6 @@ export function DefaultBrowseByFolderView(
|
||||
<>
|
||||
<ItemCardGrid
|
||||
items={documents}
|
||||
selectedItemKeys={selectedItemKeys}
|
||||
subfolderCount={subfolders.length}
|
||||
title={'Documents'}
|
||||
type="file"
|
||||
@@ -295,41 +304,35 @@ export function DefaultBrowseByFolderView(
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<FolderFileTable
|
||||
dateFormat={config.admin.dateFormat}
|
||||
documents={documents}
|
||||
focusedRowIndex={focusedRowIndex}
|
||||
i18n={i18n}
|
||||
isMovingItems={isDragging}
|
||||
onRowClick={onItemClick}
|
||||
onRowPress={onItemKeyPress}
|
||||
selectedItems={selectedItemKeys}
|
||||
subfolders={subfolders}
|
||||
/>
|
||||
<FolderFileTable />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{totalDocsAndSubfolders === 0 && (
|
||||
<NoListResults
|
||||
Actions={[
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={`${t('general:create')} ${getTranslation(folderCollectionConfig.labels?.singular, i18n).toLowerCase()}`}
|
||||
collectionSlugs={[folderCollectionConfig.slug]}
|
||||
key="create-folder"
|
||||
onCreateSuccess={onCreateSuccess}
|
||||
slugPrefix="create-folder--no-results"
|
||||
/>,
|
||||
folderID && (
|
||||
allowCreateCollectionSlugs.includes(folderCollectionConfig.slug) && (
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={`${t('general:create')} ${t('general:document').toLowerCase()}`}
|
||||
collectionSlugs={hasCreatePermissionCollectionSlugs.filter(
|
||||
(slug) => slug !== folderCollectionConfig.slug,
|
||||
)}
|
||||
key="create-document"
|
||||
onCreateSuccess={onCreateSuccess}
|
||||
slugPrefix="create-document--no-results"
|
||||
buttonLabel={`${t('general:create')} ${getTranslation(folderCollectionConfig.labels?.singular, i18n).toLowerCase()}`}
|
||||
collectionSlugs={[folderCollectionConfig.slug]}
|
||||
key="create-folder"
|
||||
onCreateSuccess={clearRouteCache}
|
||||
slugPrefix="create-folder--no-results"
|
||||
/>
|
||||
),
|
||||
folderID &&
|
||||
allowCreateCollectionSlugs.filter((slug) => slug !== folderCollectionConfig.slug)
|
||||
.length > 0 && (
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={`${t('general:create')} ${t('general:document').toLowerCase()}`}
|
||||
collectionSlugs={allowCreateCollectionSlugs.filter(
|
||||
(slug) => slug !== folderCollectionConfig.slug,
|
||||
)}
|
||||
key="create-document"
|
||||
onCreateSuccess={clearRouteCache}
|
||||
slugPrefix="create-document--no-results"
|
||||
/>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
Message={
|
||||
<p>
|
||||
@@ -347,7 +350,7 @@ export function DefaultBrowseByFolderView(
|
||||
<DragOverlaySelection
|
||||
allItems={[...subfolders, ...documents]}
|
||||
lastSelected={lastSelectedIndex}
|
||||
selectedCount={selectedIndexes.size}
|
||||
selectedCount={selectedItemKeys.size}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import type { FolderOrDocument } from 'payload/shared'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import { extractID } from 'payload/shared'
|
||||
import React, { Fragment } from 'react'
|
||||
@@ -16,6 +14,7 @@ import { PublishMany_v4 } from '../../../elements/PublishMany/index.js'
|
||||
import { UnpublishMany_v4 } from '../../../elements/UnpublishMany/index.js'
|
||||
import { useConfig } from '../../../providers/Config/index.js'
|
||||
import { useFolder } from '../../../providers/Folders/index.js'
|
||||
import { useRouteCache } from '../../../providers/RouteCache/index.js'
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
|
||||
const moveToFolderDrawerSlug = 'move-to-folder--list'
|
||||
@@ -46,9 +45,8 @@ export const ListSelection: React.FC<ListSelectionProps> = ({
|
||||
folderID,
|
||||
getSelectedItems,
|
||||
moveToFolder,
|
||||
removeItems,
|
||||
renameFolder,
|
||||
} = useFolder()
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
const { config } = useConfig()
|
||||
const { t } = useTranslation()
|
||||
const { closeModal, openModal } = useModal()
|
||||
@@ -121,12 +119,6 @@ export const ListSelection: React.FC<ListSelectionProps> = ({
|
||||
folderCollectionSlug={folderCollectionSlug}
|
||||
id={groupedSelections[folderCollectionSlug].ids[0]}
|
||||
key="edit-folder-action"
|
||||
onSave={({ doc }) => {
|
||||
renameFolder({
|
||||
folderID: doc.id,
|
||||
newName: doc[folderCollectionConfig.admin.useAsTitle],
|
||||
})
|
||||
}}
|
||||
/>
|
||||
),
|
||||
count > 0 ? (
|
||||
@@ -178,25 +170,9 @@ export const ListSelection: React.FC<ListSelectionProps> = ({
|
||||
) : null,
|
||||
!disableBulkDelete && (
|
||||
<DeleteMany_v4
|
||||
afterDelete={(groupedCollections) => {
|
||||
const itemsToRemove = Object.entries(groupedCollections).reduce<FolderOrDocument[]>(
|
||||
(acc, [slug, res]) => {
|
||||
if (res.ids.length) {
|
||||
res.ids.forEach((id) => {
|
||||
acc.push({
|
||||
itemKey: `${slug}-${id}`,
|
||||
relationTo: slug,
|
||||
value: {
|
||||
id,
|
||||
} as FolderOrDocument['value'],
|
||||
})
|
||||
})
|
||||
}
|
||||
return acc
|
||||
},
|
||||
[],
|
||||
)
|
||||
void removeItems(itemsToRemove)
|
||||
afterDelete={() => {
|
||||
clearRouteCache()
|
||||
clearSelections()
|
||||
}}
|
||||
key="bulk-delete"
|
||||
selections={groupedSelections}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import type { DragEndEvent } from '@dnd-kit/core'
|
||||
import type { CollectionSlug, Document, FolderListViewClientProps } from 'payload'
|
||||
import type { FolderDocumentItemKey } from 'payload/shared'
|
||||
import type { FolderListViewClientProps } from 'payload'
|
||||
|
||||
import { useDndMonitor } from '@dnd-kit/core'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { formatFolderOrDocumentItem } from 'payload/shared'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import { formatAdminURL } from 'payload/shared'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { DroppableBreadcrumb } from '../../elements/FolderView/Breadcrumbs/index.js'
|
||||
import { ColoredFolderIcon } from '../../elements/FolderView/ColoredFolderIcon/index.js'
|
||||
import { CurrentFolderActions } from '../../elements/FolderView/CurrentFolderActions/index.js'
|
||||
import { DragOverlaySelection } from '../../elements/FolderView/DragOverlaySelection/index.js'
|
||||
import { FolderFileTable } from '../../elements/FolderView/FolderFileTable/index.js'
|
||||
import { ItemCardGrid } from '../../elements/FolderView/ItemCardGrid/index.js'
|
||||
import { SortByPill } from '../../elements/FolderView/SortByPill/index.js'
|
||||
import { ToggleViewButtons } from '../../elements/FolderView/ToggleViewButtons/index.js'
|
||||
import { Gutter } from '../../elements/Gutter/index.js'
|
||||
@@ -29,8 +27,10 @@ import { SearchBar } from '../../elements/SearchBar/index.js'
|
||||
import { useStepNav } from '../../elements/StepNav/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useEditDepth } from '../../providers/EditDepth/index.js'
|
||||
import { useFolder } from '../../providers/Folders/index.js'
|
||||
import { FolderProvider, useFolder } from '../../providers/Folders/index.js'
|
||||
import { usePreferences } from '../../providers/Preferences/index.js'
|
||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||
import { useRouteTransition } from '../../providers/RouteTransition/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { useWindowInfo } from '../../providers/WindowInfo/index.js'
|
||||
import { ListSelection } from './ListSelection/index.js'
|
||||
@@ -38,7 +38,53 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'collection-folder-list'
|
||||
|
||||
export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
export function DefaultCollectionFolderView({
|
||||
allCollectionFolderSlugs: folderCollectionSlugs,
|
||||
allowCreateCollectionSlugs,
|
||||
baseFolderPath,
|
||||
breadcrumbs,
|
||||
documents,
|
||||
folderFieldName,
|
||||
folderID,
|
||||
FolderResultsComponent,
|
||||
search,
|
||||
sort,
|
||||
subfolders,
|
||||
...restOfProps
|
||||
}: FolderListViewClientProps) {
|
||||
return (
|
||||
<FolderProvider
|
||||
allCollectionFolderSlugs={folderCollectionSlugs}
|
||||
allowCreateCollectionSlugs={allowCreateCollectionSlugs}
|
||||
baseFolderPath={baseFolderPath}
|
||||
breadcrumbs={breadcrumbs}
|
||||
documents={documents}
|
||||
folderFieldName={folderFieldName}
|
||||
folderID={folderID}
|
||||
FolderResultsComponent={FolderResultsComponent}
|
||||
search={search}
|
||||
sort={sort}
|
||||
subfolders={subfolders}
|
||||
>
|
||||
<CollectionFolderViewInContext {...restOfProps} />
|
||||
</FolderProvider>
|
||||
)
|
||||
}
|
||||
|
||||
type CollectionFolderViewInContextProps = Omit<
|
||||
FolderListViewClientProps,
|
||||
| 'allCollectionFolderSlugs'
|
||||
| 'allowCreateCollectionSlugs'
|
||||
| 'baseFolderPath'
|
||||
| 'breadcrumbs'
|
||||
| 'documents'
|
||||
| 'folderFieldName'
|
||||
| 'folderID'
|
||||
| 'FolderResultsComponent'
|
||||
| 'subfolders'
|
||||
>
|
||||
|
||||
function CollectionFolderViewInContext(props: CollectionFolderViewInContextProps) {
|
||||
const {
|
||||
AfterFolderList,
|
||||
AfterFolderListTable,
|
||||
@@ -48,7 +94,7 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
Description,
|
||||
disableBulkDelete,
|
||||
disableBulkEdit,
|
||||
hasCreatePermission,
|
||||
search,
|
||||
viewPreference,
|
||||
} = props
|
||||
|
||||
@@ -58,28 +104,24 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
const { setStepNav } = useStepNav()
|
||||
const { setPreference } = usePreferences()
|
||||
const {
|
||||
addItems,
|
||||
allowCreateCollectionSlugs,
|
||||
breadcrumbs,
|
||||
documents,
|
||||
filterItems,
|
||||
focusedRowIndex,
|
||||
folderCollectionConfig,
|
||||
folderCollectionSlug,
|
||||
folderFieldName,
|
||||
FolderResultsComponent,
|
||||
getSelectedItems,
|
||||
isDragging,
|
||||
lastSelectedIndex,
|
||||
moveToFolder,
|
||||
onItemClick,
|
||||
onItemKeyPress,
|
||||
search,
|
||||
selectedIndexes,
|
||||
setFolderID,
|
||||
refineFolderData,
|
||||
selectedItemKeys,
|
||||
setIsDragging,
|
||||
subfolders,
|
||||
} = useFolder()
|
||||
|
||||
const [activeView, setActiveView] = React.useState<'grid' | 'list'>(viewPreference || 'grid')
|
||||
const router = useRouter()
|
||||
const { startRouteTransition } = useRouteTransition()
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
|
||||
const collectionConfig = getEntityConfig({ collectionSlug })
|
||||
|
||||
@@ -98,52 +140,30 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
}
|
||||
|
||||
if (event.over.data.current.type === 'folder' && 'id' in event.over.data.current) {
|
||||
await moveToFolder({
|
||||
itemsToMove: getSelectedItems(),
|
||||
toFolderID: event.over.data.current.id || null,
|
||||
})
|
||||
try {
|
||||
await moveToFolder({
|
||||
itemsToMove: getSelectedItems(),
|
||||
toFolderID: event.over.data.current.id,
|
||||
})
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error moving items:', error)
|
||||
}
|
||||
|
||||
clearRouteCache()
|
||||
}
|
||||
},
|
||||
[moveToFolder, getSelectedItems],
|
||||
[moveToFolder, getSelectedItems, clearRouteCache],
|
||||
)
|
||||
|
||||
const onCreateSuccess = React.useCallback(
|
||||
({ collectionSlug, doc }: { collectionSlug: CollectionSlug; doc: Document }) => {
|
||||
const collectionConfig = getEntityConfig({ collectionSlug })
|
||||
void addItems([
|
||||
formatFolderOrDocumentItem({
|
||||
folderFieldName,
|
||||
isUpload: Boolean(collectionConfig?.upload),
|
||||
relationTo: collectionSlug,
|
||||
useAsTitle: collectionConfig.admin.useAsTitle,
|
||||
value: doc,
|
||||
}),
|
||||
])
|
||||
},
|
||||
[getEntityConfig, addItems, folderFieldName],
|
||||
)
|
||||
|
||||
const selectedItemKeys = React.useMemo(() => {
|
||||
return new Set(
|
||||
getSelectedItems().reduce<FolderDocumentItemKey[]>((acc, item) => {
|
||||
if (item) {
|
||||
if (item.relationTo && item.value) {
|
||||
acc.push(`${item.relationTo}-${item.value.id}`)
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, []),
|
||||
)
|
||||
}, [getSelectedItems])
|
||||
|
||||
const handleSetViewType = React.useCallback(
|
||||
(view: 'grid' | 'list') => {
|
||||
void setPreference(`${collectionSlug}-collection-folder`, {
|
||||
async (view: 'grid' | 'list') => {
|
||||
await setPreference(`${collectionSlug}-collection-folder`, {
|
||||
viewPreference: view,
|
||||
})
|
||||
setActiveView(view)
|
||||
clearRouteCache()
|
||||
},
|
||||
[collectionSlug, setPreference],
|
||||
[collectionSlug, setPreference, clearRouteCache],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -170,7 +190,16 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
id={null}
|
||||
key="root"
|
||||
onClick={() => {
|
||||
void setFolderID({ folderID: null })
|
||||
startRouteTransition(() => {
|
||||
if (config.folders) {
|
||||
router.push(
|
||||
formatAdminURL({
|
||||
adminRoute: config.routes.admin,
|
||||
path: `/collections/${collectionSlug}/${config.folders.slug}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
<ColoredFolderIcon />
|
||||
@@ -189,7 +218,16 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
id={crumb.id}
|
||||
key={crumb.id}
|
||||
onClick={() => {
|
||||
void setFolderID({ folderID: crumb.id })
|
||||
startRouteTransition(() => {
|
||||
if (config.folders) {
|
||||
router.push(
|
||||
formatAdminURL({
|
||||
adminRoute: config.routes.admin,
|
||||
path: `/collections/${collectionSlug}/${config.folders.slug}/${crumb.id}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
{crumb.name}
|
||||
@@ -199,13 +237,25 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
}),
|
||||
])
|
||||
}
|
||||
}, [setStepNav, labels, drawerDepth, i18n, breadcrumbs, setFolderID])
|
||||
}, [
|
||||
breadcrumbs,
|
||||
collectionSlug,
|
||||
config.folders,
|
||||
config.routes.admin,
|
||||
drawerDepth,
|
||||
i18n,
|
||||
labels?.plural,
|
||||
router,
|
||||
setStepNav,
|
||||
startRouteTransition,
|
||||
])
|
||||
|
||||
const totalDocsAndSubfolders = documents.length + subfolders.length
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<DndEventListener onDragEnd={onDragEnd} setIsDragging={setIsDragging} />
|
||||
|
||||
<div className={`${baseClass} ${baseClass}--${collectionSlug}`}>
|
||||
{BeforeFolderList}
|
||||
<Gutter className={`${baseClass}__wrap`}>
|
||||
@@ -230,18 +280,18 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
AfterListHeaderContent={Description}
|
||||
title={getTranslation(labels?.plural, i18n)}
|
||||
TitleActions={[
|
||||
hasCreatePermission && (
|
||||
allowCreateCollectionSlugs.length && (
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={t('general:createNew')}
|
||||
collectionSlugs={[folderCollectionConfig.slug, collectionSlug]}
|
||||
collectionSlugs={allowCreateCollectionSlugs}
|
||||
key="create-new-button"
|
||||
onCreateSuccess={onCreateSuccess}
|
||||
onCreateSuccess={clearRouteCache}
|
||||
slugPrefix="create-document--header-pill"
|
||||
/>
|
||||
),
|
||||
<ListBulkUploadButton
|
||||
collectionSlug={collectionSlug}
|
||||
hasCreatePermission={hasCreatePermission}
|
||||
hasCreatePermission={allowCreateCollectionSlugs.includes(collectionSlug)}
|
||||
isBulkUploadEnabled={isBulkUploadEnabled}
|
||||
key="bulk-upload-button"
|
||||
/>,
|
||||
@@ -251,7 +301,7 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
Actions={[
|
||||
<SortByPill key="sort-by-pill" />,
|
||||
<ToggleViewButtons
|
||||
activeView={activeView}
|
||||
activeView={viewPreference}
|
||||
key="toggle-view-buttons"
|
||||
setActiveView={handleSetViewType}
|
||||
/>,
|
||||
@@ -260,70 +310,32 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
label={t('general:searchBy', {
|
||||
label: t('general:name'),
|
||||
})}
|
||||
onSearchChange={(search) => filterItems({ search })}
|
||||
onSearchChange={(search) => refineFolderData({ query: { search }, updateURL: true })}
|
||||
searchQueryParam={search}
|
||||
/>
|
||||
{BeforeFolderListTable}
|
||||
{totalDocsAndSubfolders > 0 && (
|
||||
<>
|
||||
{activeView === 'grid' ? (
|
||||
<div>
|
||||
{subfolders.length ? (
|
||||
<>
|
||||
<ItemCardGrid
|
||||
items={subfolders}
|
||||
selectedItemKeys={selectedItemKeys}
|
||||
title={'Folders'}
|
||||
type="folder"
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{documents.length ? (
|
||||
<>
|
||||
<ItemCardGrid
|
||||
items={documents}
|
||||
selectedItemKeys={selectedItemKeys}
|
||||
subfolderCount={subfolders.length}
|
||||
title={'Documents'}
|
||||
type="file"
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<FolderFileTable
|
||||
dateFormat={config.admin.dateFormat}
|
||||
documents={documents}
|
||||
focusedRowIndex={focusedRowIndex}
|
||||
i18n={i18n}
|
||||
isMovingItems={isDragging}
|
||||
onRowClick={onItemClick}
|
||||
onRowPress={onItemKeyPress}
|
||||
selectedItems={selectedItemKeys}
|
||||
showRelationCell={false}
|
||||
subfolders={subfolders}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{totalDocsAndSubfolders > 0 && FolderResultsComponent}
|
||||
{totalDocsAndSubfolders === 0 && (
|
||||
<NoListResults
|
||||
Actions={[
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={`${t('general:create')} ${getTranslation(folderCollectionConfig.labels?.singular, i18n).toLowerCase()}`}
|
||||
collectionSlugs={[folderCollectionConfig.slug]}
|
||||
key="create-folder"
|
||||
onCreateSuccess={onCreateSuccess}
|
||||
slugPrefix="create-folder--no-results"
|
||||
/>,
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={`${t('general:create')} ${t('general:document').toLowerCase()}`}
|
||||
collectionSlugs={[collectionSlug]}
|
||||
key="create-document"
|
||||
onCreateSuccess={onCreateSuccess}
|
||||
slugPrefix="create-document--no-results"
|
||||
/>,
|
||||
allowCreateCollectionSlugs.includes(folderCollectionSlug) && (
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={`${t('general:create')} ${getTranslation(folderCollectionConfig.labels?.singular, i18n).toLowerCase()}`}
|
||||
collectionSlugs={[folderCollectionConfig.slug]}
|
||||
key="create-folder"
|
||||
onCreateSuccess={clearRouteCache}
|
||||
slugPrefix="create-folder--no-results"
|
||||
/>
|
||||
),
|
||||
allowCreateCollectionSlugs.includes(collectionSlug) && (
|
||||
<ListCreateNewDocInFolderButton
|
||||
buttonLabel={`${t('general:create')} ${t('general:document').toLowerCase()}`}
|
||||
collectionSlugs={[collectionSlug]}
|
||||
key="create-document"
|
||||
onCreateSuccess={clearRouteCache}
|
||||
slugPrefix="create-document--no-results"
|
||||
/>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
Message={
|
||||
<p>
|
||||
@@ -344,11 +356,12 @@ export function DefaultCollectionFolderView(props: FolderListViewClientProps) {
|
||||
<DragOverlaySelection
|
||||
allItems={[...subfolders, ...documents]}
|
||||
lastSelected={lastSelectedIndex}
|
||||
selectedCount={selectedIndexes.size}
|
||||
selectedCount={selectedItemKeys.size}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
function DndEventListener({ onDragEnd, setIsDragging }) {
|
||||
useDndMonitor({
|
||||
onDragCancel() {
|
||||
|
||||
Reference in New Issue
Block a user