Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: folder enabled collections #10030

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2eef7ee
moves base folder work into beta
JarrodMFlesch Oct 1, 2024
8d9bf83
Merge branch 'main' into feat/beta/folders
JarrodMFlesch Nov 19, 2024
8ec6784
Merge branch 'main' into feat/beta/folders
JarrodMFlesch Dec 13, 2024
9c33a48
Merge branch 'main' into feat/folders
JarrodMFlesch Dec 17, 2024
4706019
feat: brings back subfolder viewing, folder assigning etc
JarrodMFlesch Dec 18, 2024
4c775d1
Merge branch 'main' into feat/folders
JarrodMFlesch Jan 2, 2025
b68af8b
Merge branch 'main' into feat/folders
JarrodMFlesch Jan 14, 2025
2d37ac4
merges main
JarrodMFlesch Jan 14, 2025
31911d8
chore: more merge resolutions
JarrodMFlesch Jan 14, 2025
b3aec9a
chore: updates css imports
JarrodMFlesch Jan 16, 2025
a66f134
feat: adds base folder functionality, list and grid views
JarrodMFlesch Feb 14, 2025
b631eeb
Merge branch 'main' into feat/folders
JarrodMFlesch Feb 14, 2025
25e196b
chore: successful build
JarrodMFlesch Feb 14, 2025
91e867e
chore: regenerates lockfile
JarrodMFlesch Feb 14, 2025
9630de1
feat: droppable crumbs, image cards, reusable item grid/table display
JarrodMFlesch Feb 19, 2025
2694603
feat: wire in pref for view type
JarrodMFlesch Feb 19, 2025
d265292
feat: move docs to folders within edit view
JarrodMFlesch Feb 19, 2025
de4be1e
ui/ux fixes
JarrodMFlesch Feb 20, 2025
3bdb127
start of modal tidy up
JarrodMFlesch Feb 20, 2025
3d9c25e
Merge branch 'main' into feat/folders
JarrodMFlesch Feb 20, 2025
f5e535d
confirm modal for move
JarrodMFlesch Feb 20, 2025
6770e7a
adds translation strings
JarrodMFlesch Feb 20, 2025
a617d31
use translations
JarrodMFlesch Feb 20, 2025
95d4324
remove old files
JarrodMFlesch Feb 20, 2025
72d393d
more translations
JarrodMFlesch Feb 20, 2025
07ff1ee
misc fixes
JarrodMFlesch Feb 20, 2025
91dc989
chore: disabled instead of hide folders inside move drawer
JarrodMFlesch Feb 20, 2025
06ef8da
adds confirm translation for move drawer
JarrodMFlesch Feb 21, 2025
cb8ee7d
fixes for moveTo drawer doc handling
JarrodMFlesch Feb 21, 2025
f4679a4
chore: adds folder delete confirmation translation
JarrodMFlesch Feb 21, 2025
4a9de40
feat: wire up delete interations with confirm modal
JarrodMFlesch Feb 21, 2025
d3986cf
folder popup fix
JarrodMFlesch Feb 21, 2025
91f7deb
try/catch moveTo, fix button hover colors
JarrodMFlesch Feb 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts folder-view",
"cwd": "${workspaceFolder}",
"name": "Run Dev Folder View",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts localization",
"cwd": "${workspaceFolder}",
Expand Down
80 changes: 80 additions & 0 deletions packages/next/src/views/Dashboard/Folders/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@import '../../../scss/styles.scss';

@layer payload-default {
.folder-dashboard {
--gap: var(--base);
--cols: 5;
width: 100%;
display: grid;
height: calc(100% - var(--app-header-height));

&__wrap {
border-radius: var(--style-radius-l);
padding-bottom: var(--spacing-view-bottom);
margin-bottom: 2rem;
margin-inline: var(--gutter-h);
display: flex;
flex-direction: column;
gap: var(--base);
}

&__folders {
gap: var(--gap);
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
}

&__group {
display: flex;
flex-direction: column;
gap: var(--gap);
}

&__label {
margin: 0;
}

&__card-list {
padding: 0;
margin: 0;
list-style: none;
gap: var(--gap);
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);

.card {
height: 100%;
}
}

&__locked.locked {
align-items: unset;
justify-content: unset;
}

@include large-break {
--cols: 4;
}

@include mid-break {
--gap: var(--base);
--cols: 2;
}

@include small-break {
--cols: 2;

&__wrap {
gap: var(--base);
}

&__card-list {
gap: base(0.4);
}
}

@include extra-small-break {
--cols: 1;
}
}
}
137 changes: 137 additions & 0 deletions packages/next/src/views/Dashboard/Folders/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import type {
ClientUser,
PaginatedDocs,
SanitizedPermissions,
ServerProps,
VisibleEntities,
} from 'payload'

import { FolderAndDocuments, FolderProvider } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { type groupNavItems, sanitizeID } from '@payloadcms/ui/shared'
import { redirect } from 'next/navigation.js'
import { getFolderData, type GetFolderDataResult } from 'payload/shared'

import './index.scss'

import React from 'react'

const baseClass = 'folder-dashboard'

export type FolderDashboardProps = {
documents: PaginatedDocs[]
folderID?: number | string
globalData: Array<{
data: { _isLocked: boolean; _lastEditedAt: string; _userEditing: ClientUser | number | string }
lockDuration?: number
slug: string
}>
Link: React.ComponentType<any>
navGroups?: ReturnType<typeof groupNavItems>
permissions: SanitizedPermissions
visibleEntities: VisibleEntities
} & GetFolderDataResult &
ServerProps

export const FolderDashboard: React.FC<FolderDashboardProps> = async (props) => {
const {
i18n,
locale,
params,
payload: {
config: {
admin: {
components: { afterDashboard, beforeDashboard },
},
routes,
},
},
payload,
permissions,
searchParams,
user,
} = props

const searchParamFolderID = searchParams?.folder as string

const { breadcrumbs, items } = await getFolderData({
folderID: searchParamFolderID,
payload,
})

const folderID = breadcrumbs[breadcrumbs.length - 1]?.id

if (folderID && searchParamFolderID && searchParamFolderID !== String(folderID)) {
return redirect(`${routes.admin}`)
}

const displayTypePref = (await payload.find({
collection: 'payload-preferences',
depth: 0,
limit: 1,
where: {
and: [
{
key: {
equals: 'folder-view-display',
},
},
{
'user.relationTo': {
equals: user.collection,
},
},
{
'user.value': {
equals: sanitizeID(user.id),
},
},
],
},
})) as unknown as { docs: { value: 'grid' | 'list' }[] }

return (
<FolderProvider
initialData={{
breadcrumbs,
folderID,
items,
}}
>
<div className={baseClass}>
<div className={`${baseClass}__wrap`}>
{beforeDashboard &&
RenderServerComponent({
Component: beforeDashboard,
importMap: payload.importMap,
serverProps: {
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
},
})}

<FolderAndDocuments initialDisplayType={displayTypePref?.docs[0]?.value} />
{afterDashboard &&
RenderServerComponent({
Component: afterDashboard,
importMap: payload.importMap,
serverProps: {
i18n,
locale,
params,
payload,
permissions,
searchParams,
user,
},
})}
</div>
</div>
</FolderProvider>
)
}
5 changes: 4 additions & 1 deletion packages/next/src/views/Dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import type { AdminViewServerProps } from 'payload'
import { HydrateAuthProvider, SetStepNav } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { EntityType, groupNavItems } from '@payloadcms/ui/shared'
import { getFolderData } from 'payload/shared'
import React, { Fragment } from 'react'

import type { DashboardViewClientProps, DashboardViewServerPropsOnly } from './Default/index.js'

import { DefaultDashboard } from './Default/index.js'
import { FolderDashboard } from './Folders/index.js'

export { generateDashboardMetadata } from './meta.js'

Expand Down Expand Up @@ -108,7 +110,8 @@ export async function Dashboard({ initPageResult, params, searchParams }: AdminV
locale,
} satisfies DashboardViewClientProps,
Component: config.admin?.components?.views?.dashboard?.Component,
Fallback: DefaultDashboard,
Fallback: FolderDashboard,
// Fallback: DefaultDashboard,
importMap: payload.importMap,
serverProps: {
globalData,
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/views/Document/handleServerFunction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const renderDocumentHandler = async (args: {
initialState?: FormState
locale?: Locale
overrideEntityVisibility?: boolean
redirectAfterCreate: boolean
redirectAfterDelete: boolean
redirectAfterDuplicate: boolean
req: PayloadRequest
Expand All @@ -40,6 +41,7 @@ export const renderDocumentHandler = async (args: {
initialData,
locale,
overrideEntityVisibility,
redirectAfterCreate,
redirectAfterDelete,
redirectAfterDuplicate,
req,
Expand Down Expand Up @@ -165,6 +167,7 @@ export const renderDocumentHandler = async (args: {
segments: ['collections', collectionSlug, docID],
},
payload,
redirectAfterCreate,
redirectAfterDelete,
redirectAfterDuplicate,
searchParams: {},
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/views/Document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const renderDocument = async ({
initPageResult,
overrideEntityVisibility,
params,
redirectAfterCreate,
redirectAfterDelete,
redirectAfterDuplicate,
searchParams,
Expand Down Expand Up @@ -357,6 +358,7 @@ export const renderDocument = async ({
key={locale?.code}
lastUpdateTime={lastUpdateTime}
mostRecentVersionIsAutosaved={mostRecentVersionIsAutosaved}
redirectAfterCreate={redirectAfterCreate}
redirectAfterDelete={redirectAfterDelete}
redirectAfterDuplicate={redirectAfterDuplicate}
unpublishedVersionCount={unpublishedVersionCount}
Expand Down
9 changes: 8 additions & 1 deletion packages/next/src/views/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerCompo
import { renderFilters, renderTable, upsertPreferences } from '@payloadcms/ui/rsc'
import { formatAdminURL, mergeListSearchAndWhere } from '@payloadcms/ui/shared'
import { notFound } from 'next/navigation.js'
import { isNumber } from 'payload/shared'
import { formatFilesize, isNumber } from 'payload/shared'
import React, { Fragment } from 'react'

import { renderListViewSlots } from './renderListViewSlots.js'
Expand Down Expand Up @@ -137,6 +137,13 @@ export const renderListView = async (

const clientCollectionConfig = clientConfig.collections.find((c) => c.slug === collectionSlug)

if (clientCollectionConfig.upload) {
data.docs = data.docs.map((doc) => ({
...doc,
filesize: formatFilesize(doc.filesize),
}))
}

const { columnState, Table } = renderTable({
clientCollectionConfig,
collectionConfig,
Expand Down
2 changes: 1 addition & 1 deletion packages/payload/src/admin/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type ListQuery = {
search?: string
sort?: Sort
where?: Where
}
} & Record<string, unknown>

export type BuildTableStateArgs = {
collectionSlug: string | string[]
Expand Down
2 changes: 2 additions & 0 deletions packages/payload/src/admin/views/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export type AdminViewServerPropsOnly = {
readonly importMap: ImportMap
readonly initialData?: Data
readonly initPageResult: InitPageResult
readonly params?: { [key: string]: string | string[] | undefined }
readonly redirectAfterCreate?: boolean
readonly redirectAfterDelete?: boolean
readonly redirectAfterDuplicate?: boolean
} & ServerProps
Expand Down
2 changes: 1 addition & 1 deletion packages/payload/src/bin/generateImportMap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function addPayloadComponentToImportMap({
importMap[componentPath + '#' + exportName] = importIdentifier
}

export type AddToImportMap = (payloadComponent: PayloadComponent | PayloadComponent[]) => void
export type AddToImportMap = (payloadComponent?: PayloadComponent | PayloadComponent[]) => void

export async function generateImportMap(
config: SanitizedConfig,
Expand Down
12 changes: 11 additions & 1 deletion packages/payload/src/collections/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ export type AfterForgotPasswordHook = (args: {
context: RequestContext
}) => any

export type EnableFoldersOptions = {
// Displays the folder collection and parentFolder field in the document view
debug?: boolean
}

export type BaseListFilter = (args: {
limit: number
locale?: TypedLocale
Expand Down Expand Up @@ -327,6 +332,10 @@ export type CollectionAdminOptions = {
* Custom description for collection. This will also be used as JSDoc for the generated types
*/
description?: EntityDescription
/**
* Changes the list view and adds parentFolder field to collection
*/
enableFolders?: boolean | EnableFoldersOptions
enableRichTextLink?: boolean
enableRichTextRelationship?: boolean
/**
Expand Down Expand Up @@ -532,8 +541,9 @@ export type SanitizedJoins = {
export interface SanitizedCollectionConfig
extends Omit<
DeepRequired<CollectionConfig>,
'auth' | 'endpoints' | 'fields' | 'slug' | 'upload' | 'versions'
'admin' | 'auth' | 'endpoints' | 'fields' | 'slug' | 'upload' | 'versions'
> {
admin: CollectionAdminOptions
auth: Auth
endpoints: Endpoint[] | false
fields: Field[]
Expand Down
Loading
Loading