feat: simplify column prefs (#11390)
Transforms how column prefs are stored in the database. This change reduces the complexity of the `columns` property by removing the unnecessary `accessor` and `active` keys. This change is necessary in order to [maintain column state in the URL](https://github.com/payloadcms/payload/pull/11387), where the state itself needs to be as concise as possible. Does so in a non-breaking way, where the old column shape is transformed as needed. Here's an example: Before: ```ts [ { accessor: "title", active: true } ] ``` After: ```ts [ { title: true } ] ```
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { ImportMap } from '../../bin/generateImportMap/index.js'
|
||||
import type { SanitizedConfig } from '../../config/types.js'
|
||||
import type { PaginatedDocs } from '../../database/types.js'
|
||||
import type { CollectionSlug } from '../../index.js'
|
||||
import type { CollectionSlug, ColumnPreference } from '../../index.js'
|
||||
import type { PayloadRequest, Sort, Where } from '../../types/index.js'
|
||||
|
||||
export type DefaultServerFunctionArgs = {
|
||||
@@ -50,7 +50,7 @@ export type ListQuery = {
|
||||
|
||||
export type BuildTableStateArgs = {
|
||||
collectionSlug: string | string[]
|
||||
columns?: { accessor: string; active: boolean }[]
|
||||
columns?: ColumnPreference[]
|
||||
docs?: PaginatedDocs['docs']
|
||||
enableRowSelections?: boolean
|
||||
parent?: {
|
||||
|
||||
@@ -1374,6 +1374,7 @@ export { restoreVersionOperation as restoreVersionOperationGlobal } from './glob
|
||||
export { updateOperation as updateOperationGlobal } from './globals/operations/update.js'
|
||||
export type {
|
||||
CollapsedPreferences,
|
||||
ColumnPreference,
|
||||
DocumentPreferences,
|
||||
FieldsPreferences,
|
||||
InsideFieldsPreferences,
|
||||
|
||||
19
packages/payload/src/preferences/migrateColumns.ts
Normal file
19
packages/payload/src/preferences/migrateColumns.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @todo remove this function and subsequent hooks in v4
|
||||
* They are used to transform the old shape of `columnPreferences` to new shape
|
||||
* i.e. ({ accessor: string, active: boolean })[] to ({ [accessor: string]: boolean })[]
|
||||
* In v4 can we use the new shape directly
|
||||
*/
|
||||
export const migrateColumns = (value: Record<string, any>) => {
|
||||
if (value && typeof value === 'object' && 'columns' in value && Array.isArray(value.columns)) {
|
||||
value.columns = value.columns.map((col) => {
|
||||
if ('accessor' in col) {
|
||||
return { [col.accessor]: col.active }
|
||||
}
|
||||
|
||||
return col
|
||||
})
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { CollectionConfig } from '../collections/config/types.js'
|
||||
import type { Access, Config } from '../config/types.js'
|
||||
|
||||
import { migrateColumns } from './migrateColumns.js'
|
||||
import { deleteHandler } from './requestHandlers/delete.js'
|
||||
import { findByIDHandler } from './requestHandlers/findOne.js'
|
||||
import { updateHandler } from './requestHandlers/update.js'
|
||||
@@ -76,6 +77,14 @@ const getPreferencesCollection = (config: Config): CollectionConfig => ({
|
||||
{
|
||||
name: 'value',
|
||||
type: 'json',
|
||||
/**
|
||||
* @todo remove these hooks in v4
|
||||
* See `migrateColumns` for more information
|
||||
*/
|
||||
hooks: {
|
||||
afterRead: [({ value }) => migrateColumns(value)],
|
||||
beforeValidate: [({ value }) => migrateColumns(value)],
|
||||
},
|
||||
validate: (value) => {
|
||||
if (value) {
|
||||
try {
|
||||
|
||||
@@ -28,8 +28,12 @@ export type DocumentPreferences = {
|
||||
fields: FieldsPreferences
|
||||
}
|
||||
|
||||
export type ColumnPreference = {
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
export type ListPreferences = {
|
||||
columns?: { accessor: string; active: boolean }[]
|
||||
columns?: ColumnPreference[]
|
||||
limit?: number
|
||||
sort?: string
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type {
|
||||
CollectionSlug,
|
||||
Column,
|
||||
ColumnPreference,
|
||||
JoinFieldClient,
|
||||
ListQuery,
|
||||
PaginatedDocs,
|
||||
@@ -25,7 +26,6 @@ import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { hoistQueryParamsToAnd } from '../../utilities/mergeListSearchAndWhere.js'
|
||||
import { AnimateHeight } from '../AnimateHeight/index.js'
|
||||
import './index.scss'
|
||||
import { ColumnSelector } from '../ColumnSelector/index.js'
|
||||
import { useDocumentDrawer } from '../DocumentDrawer/index.js'
|
||||
import { Popup, PopupList } from '../Popup/index.js'
|
||||
@@ -33,6 +33,7 @@ import { RelationshipProvider } from '../Table/RelationshipProvider/index.js'
|
||||
import { TableColumnsProvider } from '../TableColumns/index.js'
|
||||
import { DrawerLink } from './cells/DrawerLink/index.js'
|
||||
import { RelationshipTablePagination } from './Pagination.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'relationship-table'
|
||||
|
||||
@@ -123,11 +124,10 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
|
||||
newQuery.where = hoistQueryParamsToAnd(newQuery.where, filterOptions)
|
||||
}
|
||||
|
||||
// map columns from string[] to ListPreferences['columns']
|
||||
const defaultColumns = field.admin.defaultColumns
|
||||
// map columns from string[] to ColumnPreference[]
|
||||
const defaultColumns: ColumnPreference[] = field.admin.defaultColumns
|
||||
? field.admin.defaultColumns.map((accessor) => ({
|
||||
accessor,
|
||||
active: true,
|
||||
[accessor]: true,
|
||||
}))
|
||||
: undefined
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import type {
|
||||
ClientComponentProps,
|
||||
ClientField,
|
||||
Column,
|
||||
ColumnPreference,
|
||||
DefaultCellComponentProps,
|
||||
DefaultServerCellComponentProps,
|
||||
Field,
|
||||
ListPreferences,
|
||||
PaginatedDocs,
|
||||
Payload,
|
||||
SanitizedCollectionConfig,
|
||||
@@ -39,8 +39,8 @@ type Args = {
|
||||
beforeRows?: Column[]
|
||||
clientCollectionConfig: ClientCollectionConfig
|
||||
collectionConfig: SanitizedCollectionConfig
|
||||
columnPreferences: ListPreferences['columns']
|
||||
columns?: ListPreferences['columns']
|
||||
columnPreferences: ColumnPreference[]
|
||||
columns?: ColumnPreference[]
|
||||
customCellProps: DefaultCellComponentProps['customCellProps']
|
||||
docs: PaginatedDocs['docs']
|
||||
enableRowSelections: boolean
|
||||
@@ -99,10 +99,10 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
|
||||
const sortTo = columnPreferences || columns
|
||||
|
||||
const sortFieldMap = (fieldMap, sortTo) =>
|
||||
const sortFieldMap = (fieldMap, sortTo: ColumnPreference[]) =>
|
||||
fieldMap?.sort((a, b) => {
|
||||
const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === a.name)
|
||||
const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === b.name)
|
||||
const aIndex = sortTo.findIndex((column) => 'name' in a && a.name in column)
|
||||
const bIndex = sortTo.findIndex((column) => 'name' in b && b.name in column)
|
||||
|
||||
if (aIndex === -1 && bIndex === -1) {
|
||||
return 0
|
||||
@@ -136,18 +136,12 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
(f) => 'name' in field && 'name' in f && f.name === field.name,
|
||||
)
|
||||
|
||||
const columnPreference = columnPreferences?.find(
|
||||
(preference) => field && 'name' in field && preference.accessor === field.name,
|
||||
)
|
||||
|
||||
let active = false
|
||||
|
||||
if (columnPreference) {
|
||||
active = columnPreference.active
|
||||
if (columnPreferences) {
|
||||
active = 'name' in field && columnPreferences?.some((col) => col?.[field.name])
|
||||
} else if (columns && Array.isArray(columns) && columns.length > 0) {
|
||||
active = columns.find(
|
||||
(column) => field && 'name' in field && column.accessor === field.name,
|
||||
)?.active
|
||||
active = 'name' in field && columns.some((col) => col?.[field.name])
|
||||
} else if (activeColumnsIndices.length < 4) {
|
||||
active = true
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import type { I18nClient } from '@payloadcms/translations'
|
||||
import type {
|
||||
ClientField,
|
||||
Column,
|
||||
ColumnPreference,
|
||||
DefaultCellComponentProps,
|
||||
DefaultServerCellComponentProps,
|
||||
Field,
|
||||
ListPreferences,
|
||||
PaginatedDocs,
|
||||
Payload,
|
||||
SanitizedCollectionConfig,
|
||||
@@ -36,8 +36,8 @@ import { filterFields } from './filterFields.js'
|
||||
|
||||
type Args = {
|
||||
beforeRows?: Column[]
|
||||
columnPreferences: ListPreferences['columns']
|
||||
columns?: ListPreferences['columns']
|
||||
columnPreferences: ColumnPreference[]
|
||||
columns?: ColumnPreference[]
|
||||
customCellProps: DefaultCellComponentProps['customCellProps']
|
||||
docs: PaginatedDocs['docs']
|
||||
enableRowSelections: boolean
|
||||
@@ -92,8 +92,8 @@ export const buildPolymorphicColumnState = (args: Args): Column[] => {
|
||||
|
||||
const sortFieldMap = (fieldMap, sortTo) =>
|
||||
fieldMap?.sort((a, b) => {
|
||||
const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === a.name)
|
||||
const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === b.name)
|
||||
const aIndex = sortTo.findIndex((column) => 'name' in a && a.name in column)
|
||||
const bIndex = sortTo.findIndex((column) => 'name' in b && b.name in column)
|
||||
|
||||
if (aIndex === -1 && bIndex === -1) {
|
||||
return 0
|
||||
@@ -127,18 +127,12 @@ export const buildPolymorphicColumnState = (args: Args): Column[] => {
|
||||
(f) => 'name' in field && 'name' in f && f.name === field.name,
|
||||
)
|
||||
|
||||
const columnPreference = columnPreferences?.find(
|
||||
(preference) => field && 'name' in field && preference.accessor === field.name,
|
||||
)
|
||||
|
||||
let active = false
|
||||
|
||||
if (columnPreference) {
|
||||
active = columnPreference.active
|
||||
if (columnPreferences) {
|
||||
active = 'name' in field && columnPreferences?.some((col) => col?.[field.name])
|
||||
} else if (columns && Array.isArray(columns) && columns.length > 0) {
|
||||
active = columns.find(
|
||||
(column) => field && 'name' in field && column.accessor === field.name,
|
||||
)?.active
|
||||
active = 'name' in field && columns.some((col) => col?.[field.name])
|
||||
} else if (activeColumnsIndices.length < 4) {
|
||||
active = true
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { ClientField, CollectionConfig, Field, ListPreferences } from 'payload'
|
||||
import type { ClientField, CollectionConfig, ColumnPreference, Field } from 'payload'
|
||||
|
||||
import { fieldAffectsData } from 'payload/shared'
|
||||
|
||||
const getRemainingColumns = <T extends ClientField[] | Field[]>(
|
||||
fields: T,
|
||||
useAsTitle: string,
|
||||
): ListPreferences['columns'] =>
|
||||
): ColumnPreference[] =>
|
||||
fields?.reduce((remaining, field) => {
|
||||
if (fieldAffectsData(field) && field.name === useAsTitle) {
|
||||
return remaining
|
||||
@@ -40,7 +40,7 @@ export const getInitialColumns = <T extends ClientField[] | Field[]>(
|
||||
fields: T,
|
||||
useAsTitle: CollectionConfig['admin']['useAsTitle'],
|
||||
defaultColumns: CollectionConfig['admin']['defaultColumns'],
|
||||
): ListPreferences['columns'] => {
|
||||
): ColumnPreference[] => {
|
||||
let initialColumns = []
|
||||
|
||||
if (Array.isArray(defaultColumns) && defaultColumns.length >= 1) {
|
||||
@@ -57,7 +57,6 @@ export const getInitialColumns = <T extends ClientField[] | Field[]>(
|
||||
}
|
||||
|
||||
return initialColumns.map((column) => ({
|
||||
accessor: column,
|
||||
active: true,
|
||||
[column]: true,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { Column, ListPreferences, SanitizedCollectionConfig } from 'payload'
|
||||
import type { Column, ColumnPreference, ListPreferences, SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
import React, { createContext, useCallback, useContext, useEffect } from 'react'
|
||||
|
||||
@@ -39,12 +39,10 @@ type Props = {
|
||||
}
|
||||
|
||||
// strip out Heading, Label, and renderedCells properties, they cannot be sent to the server
|
||||
const sanitizeColumns = (columns: Column[]) => {
|
||||
return columns.map(({ accessor, active }) => ({
|
||||
accessor,
|
||||
active,
|
||||
const formatColumnPreferences = (columns: Column[]): ColumnPreference[] =>
|
||||
columns.map(({ accessor, active }) => ({
|
||||
[accessor]: active,
|
||||
}))
|
||||
}
|
||||
|
||||
export const TableColumnsProvider: React.FC<Props> = ({
|
||||
children,
|
||||
@@ -90,7 +88,7 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
|
||||
const result = await getTableState({
|
||||
collectionSlug,
|
||||
columns: sanitizeColumns(withMovedColumn),
|
||||
columns: formatColumnPreferences(withMovedColumn),
|
||||
docs,
|
||||
enableRowSelections,
|
||||
renderRowTypes,
|
||||
@@ -123,7 +121,7 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
|
||||
const { newColumnState, toggledColumns } = tableColumns.reduce<{
|
||||
newColumnState: Column[]
|
||||
toggledColumns: Pick<Column, 'accessor' | 'active'>[]
|
||||
toggledColumns: ColumnPreference[]
|
||||
}>(
|
||||
(acc, col) => {
|
||||
if (col.accessor === column) {
|
||||
@@ -133,14 +131,12 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
active: !col.active,
|
||||
})
|
||||
acc.toggledColumns.push({
|
||||
accessor: col.accessor,
|
||||
active: !col.active,
|
||||
[col.accessor]: !col.active,
|
||||
})
|
||||
} else {
|
||||
acc.newColumnState.push(col)
|
||||
acc.toggledColumns.push({
|
||||
accessor: col.accessor,
|
||||
active: col.active,
|
||||
[col.accessor]: col.active,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -182,14 +178,8 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
|
||||
const setActiveColumns = React.useCallback(
|
||||
async (activeColumnAccessors: string[]) => {
|
||||
const activeColumns: Pick<Column, 'accessor' | 'active'>[] = tableColumns
|
||||
.map((col) => {
|
||||
return {
|
||||
accessor: col.accessor,
|
||||
active: activeColumnAccessors.includes(col.accessor),
|
||||
}
|
||||
})
|
||||
.sort((first, second) => {
|
||||
const activeColumns: ColumnPreference[] = formatColumnPreferences(
|
||||
tableColumns.sort((first, second) => {
|
||||
const indexOfFirst = activeColumnAccessors.indexOf(first.accessor)
|
||||
const indexOfSecond = activeColumnAccessors.indexOf(second.accessor)
|
||||
|
||||
@@ -198,7 +188,8 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
}
|
||||
|
||||
return indexOfFirst > indexOfSecond ? 1 : -1
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
const { state: columnState, Table } = await getTableState({
|
||||
collectionSlug,
|
||||
@@ -239,7 +230,7 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
|
||||
if (collectionHasChanged || !listPreferences) {
|
||||
const currentPreferences = await getPreference<{
|
||||
columns: ListPreferences['columns']
|
||||
columns: ColumnPreference[]
|
||||
}>(preferenceKey)
|
||||
|
||||
prevCollection.current = defaultCollection
|
||||
|
||||
@@ -3,9 +3,10 @@ import type {
|
||||
ClientConfig,
|
||||
ClientField,
|
||||
CollectionConfig,
|
||||
Column,
|
||||
ColumnPreference,
|
||||
Field,
|
||||
ImportMap,
|
||||
ListPreferences,
|
||||
PaginatedDocs,
|
||||
Payload,
|
||||
SanitizedCollectionConfig,
|
||||
@@ -14,9 +15,6 @@ import type {
|
||||
import { getTranslation, type I18nClient } from '@payloadcms/translations'
|
||||
import { fieldAffectsData, fieldIsHiddenOrDisabled, flattenTopLevelFields } from 'payload/shared'
|
||||
|
||||
// eslint-disable-next-line payload/no-imports-from-exports-dir
|
||||
import type { Column } from '../exports/client/index.js'
|
||||
|
||||
import { RenderServerComponent } from '../elements/RenderServerComponent/index.js'
|
||||
import { buildColumnState } from '../elements/TableColumns/buildColumnState.js'
|
||||
import { buildPolymorphicColumnState } from '../elements/TableColumns/buildPolymorphicColumnState.js'
|
||||
@@ -71,8 +69,8 @@ export const renderTable = ({
|
||||
clientConfig?: ClientConfig
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
collections?: string[]
|
||||
columnPreferences: ListPreferences['columns']
|
||||
columns?: ListPreferences['columns']
|
||||
columnPreferences: ColumnPreference[]
|
||||
columns?: ColumnPreference[]
|
||||
customCellProps?: Record<string, any>
|
||||
docs: PaginatedDocs['docs']
|
||||
drawerSlug?: string
|
||||
@@ -109,7 +107,7 @@ export const renderTable = ({
|
||||
const columns = columnsFromArgs
|
||||
? columnsFromArgs?.filter((column) =>
|
||||
flattenTopLevelFields(fields, true)?.some(
|
||||
(field) => 'name' in field && field.name === column.accessor,
|
||||
(field) => 'name' in field && column[field.name],
|
||||
),
|
||||
)
|
||||
: getInitialColumns(fields, useAsTitle, [])
|
||||
@@ -130,7 +128,7 @@ export const renderTable = ({
|
||||
const columns = columnsFromArgs
|
||||
? columnsFromArgs?.filter((column) =>
|
||||
flattenTopLevelFields(clientCollectionConfig.fields, true)?.some(
|
||||
(field) => 'name' in field && field.name === column.accessor,
|
||||
(field) => 'name' in field && field.name in column,
|
||||
),
|
||||
)
|
||||
: getInitialColumns(
|
||||
|
||||
@@ -151,6 +151,7 @@ export function DefaultListView(props: ListViewClientProps) {
|
||||
])
|
||||
}
|
||||
}, [setStepNav, labels, drawerDepth])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TableColumnsProvider
|
||||
|
||||
@@ -170,12 +170,7 @@ describe('Text', () => {
|
||||
user: client.user,
|
||||
key: 'text-fields-list',
|
||||
value: {
|
||||
columns: [
|
||||
{
|
||||
accessor: 'disableListColumnText',
|
||||
active: true,
|
||||
},
|
||||
],
|
||||
columns: [{ disableListColumnText: true }],
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": ["./test/fields-relationship/config.ts"],
|
||||
"@payload-config": ["./test/_community/config.ts"],
|
||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||
"@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user