Revert "feat: simplify column prefs (#11390)" (#11427)

This reverts commit 69c0d09 in #11390.

In order to future proof column prefs, it probably is best to continue
to use the current shape. This change was intended to ensure that as
little transformation to URL params was made as possible for #11387, but
we will likely transform them after all.

This will ensure that we can add support for additional properties over
time, as needed. For example, if we hypothetically wanted to add a
custom `label` or similar feature to columns prefs, it would make more
sense to use explicit properties to identity `accessor` and `active`.

For example:

```ts
[
  {
    accessor: "title",
    active: true,
    label: 'Custom Label' // hypothetical
  }
]
```
This commit is contained in:
Jacob Fletcher
2025-02-27 08:39:24 -05:00
committed by GitHub
parent 2a3682ff68
commit 6aa9da73f8
14 changed files with 79 additions and 84 deletions

View File

@@ -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, ColumnPreference } from '../../index.js'
import type { CollectionSlug } 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?: ColumnPreference[]
columns?: { accessor: string; active: boolean }[]
docs?: PaginatedDocs['docs']
enableRowSelections?: boolean
parent?: {

View File

@@ -1374,7 +1374,6 @@ export { restoreVersionOperation as restoreVersionOperationGlobal } from './glob
export { updateOperation as updateOperationGlobal } from './globals/operations/update.js'
export type {
CollapsedPreferences,
ColumnPreference,
DocumentPreferences,
FieldsPreferences,
InsideFieldsPreferences,

View File

@@ -1,19 +0,0 @@
/**
* @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
}

View File

@@ -2,7 +2,6 @@
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'
@@ -77,14 +76,6 @@ 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 {

View File

@@ -28,12 +28,8 @@ export type DocumentPreferences = {
fields: FieldsPreferences
}
export type ColumnPreference = {
[key: string]: boolean
}
export type ListPreferences = {
columns?: ColumnPreference[]
columns?: { accessor: string; active: boolean }[]
limit?: number
sort?: string
}

View File

@@ -2,7 +2,6 @@
import type {
CollectionSlug,
Column,
ColumnPreference,
JoinFieldClient,
ListQuery,
PaginatedDocs,
@@ -26,6 +25,7 @@ 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,7 +33,6 @@ 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'
@@ -124,10 +123,11 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
newQuery.where = hoistQueryParamsToAnd(newQuery.where, filterOptions)
}
// map columns from string[] to ColumnPreference[]
const defaultColumns: ColumnPreference[] = field.admin.defaultColumns
// map columns from string[] to ListPreferences['columns']
const defaultColumns = field.admin.defaultColumns
? field.admin.defaultColumns.map((accessor) => ({
[accessor]: true,
accessor,
active: true,
}))
: undefined

View File

@@ -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: ColumnPreference[]
columns?: ColumnPreference[]
columnPreferences: ListPreferences['columns']
columns?: ListPreferences['columns']
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: ColumnPreference[]) =>
const sortFieldMap = (fieldMap, sortTo) =>
fieldMap?.sort((a, b) => {
const aIndex = sortTo.findIndex((column) => 'name' in a && a.name in column)
const bIndex = sortTo.findIndex((column) => 'name' in b && b.name in column)
const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === a.name)
const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === b.name)
if (aIndex === -1 && bIndex === -1) {
return 0
@@ -136,12 +136,18 @@ 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 (columnPreferences) {
active = 'name' in field && columnPreferences?.some((col) => col?.[field.name])
if (columnPreference) {
active = columnPreference.active
} else if (columns && Array.isArray(columns) && columns.length > 0) {
active = 'name' in field && columns.some((col) => col?.[field.name])
active = columns.find(
(column) => field && 'name' in field && column.accessor === field.name,
)?.active
} else if (activeColumnsIndices.length < 4) {
active = true
}

View File

@@ -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: ColumnPreference[]
columns?: ColumnPreference[]
columnPreferences: ListPreferences['columns']
columns?: ListPreferences['columns']
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 && a.name in column)
const bIndex = sortTo.findIndex((column) => 'name' in b && b.name in column)
const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === a.name)
const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === b.name)
if (aIndex === -1 && bIndex === -1) {
return 0
@@ -127,12 +127,18 @@ 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 (columnPreferences) {
active = 'name' in field && columnPreferences?.some((col) => col?.[field.name])
if (columnPreference) {
active = columnPreference.active
} else if (columns && Array.isArray(columns) && columns.length > 0) {
active = 'name' in field && columns.some((col) => col?.[field.name])
active = columns.find(
(column) => field && 'name' in field && column.accessor === field.name,
)?.active
} else if (activeColumnsIndices.length < 4) {
active = true
}

View File

@@ -1,11 +1,11 @@
import type { ClientField, CollectionConfig, ColumnPreference, Field } from 'payload'
import type { ClientField, CollectionConfig, Field, ListPreferences } from 'payload'
import { fieldAffectsData } from 'payload/shared'
const getRemainingColumns = <T extends ClientField[] | Field[]>(
fields: T,
useAsTitle: string,
): ColumnPreference[] =>
): ListPreferences['columns'] =>
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'],
): ColumnPreference[] => {
): ListPreferences['columns'] => {
let initialColumns = []
if (Array.isArray(defaultColumns) && defaultColumns.length >= 1) {
@@ -57,6 +57,7 @@ export const getInitialColumns = <T extends ClientField[] | Field[]>(
}
return initialColumns.map((column) => ({
[column]: true,
accessor: column,
active: true,
}))
}

View File

@@ -1,5 +1,5 @@
'use client'
import type { Column, ColumnPreference, ListPreferences, SanitizedCollectionConfig } from 'payload'
import type { Column, ListPreferences, SanitizedCollectionConfig } from 'payload'
import React, { createContext, useCallback, useContext, useEffect } from 'react'
@@ -39,10 +39,12 @@ type Props = {
}
// strip out Heading, Label, and renderedCells properties, they cannot be sent to the server
const formatColumnPreferences = (columns: Column[]): ColumnPreference[] =>
columns.map(({ accessor, active }) => ({
[accessor]: active,
const sanitizeColumns = (columns: Column[]) => {
return columns.map(({ accessor, active }) => ({
accessor,
active,
}))
}
export const TableColumnsProvider: React.FC<Props> = ({
children,
@@ -88,7 +90,7 @@ export const TableColumnsProvider: React.FC<Props> = ({
const result = await getTableState({
collectionSlug,
columns: formatColumnPreferences(withMovedColumn),
columns: sanitizeColumns(withMovedColumn),
docs,
enableRowSelections,
renderRowTypes,
@@ -121,7 +123,7 @@ export const TableColumnsProvider: React.FC<Props> = ({
const { newColumnState, toggledColumns } = tableColumns.reduce<{
newColumnState: Column[]
toggledColumns: ColumnPreference[]
toggledColumns: Pick<Column, 'accessor' | 'active'>[]
}>(
(acc, col) => {
if (col.accessor === column) {
@@ -131,12 +133,14 @@ export const TableColumnsProvider: React.FC<Props> = ({
active: !col.active,
})
acc.toggledColumns.push({
[col.accessor]: !col.active,
accessor: col.accessor,
active: !col.active,
})
} else {
acc.newColumnState.push(col)
acc.toggledColumns.push({
[col.accessor]: col.active,
accessor: col.accessor,
active: col.active,
})
}
@@ -178,8 +182,14 @@ export const TableColumnsProvider: React.FC<Props> = ({
const setActiveColumns = React.useCallback(
async (activeColumnAccessors: string[]) => {
const activeColumns: ColumnPreference[] = formatColumnPreferences(
tableColumns.sort((first, second) => {
const activeColumns: Pick<Column, 'accessor' | 'active'>[] = tableColumns
.map((col) => {
return {
accessor: col.accessor,
active: activeColumnAccessors.includes(col.accessor),
}
})
.sort((first, second) => {
const indexOfFirst = activeColumnAccessors.indexOf(first.accessor)
const indexOfSecond = activeColumnAccessors.indexOf(second.accessor)
@@ -188,8 +198,7 @@ export const TableColumnsProvider: React.FC<Props> = ({
}
return indexOfFirst > indexOfSecond ? 1 : -1
}),
)
})
const { state: columnState, Table } = await getTableState({
collectionSlug,
@@ -230,7 +239,7 @@ export const TableColumnsProvider: React.FC<Props> = ({
if (collectionHasChanged || !listPreferences) {
const currentPreferences = await getPreference<{
columns: ColumnPreference[]
columns: ListPreferences['columns']
}>(preferenceKey)
prevCollection.current = defaultCollection

View File

@@ -3,10 +3,9 @@ import type {
ClientConfig,
ClientField,
CollectionConfig,
Column,
ColumnPreference,
Field,
ImportMap,
ListPreferences,
PaginatedDocs,
Payload,
SanitizedCollectionConfig,
@@ -15,6 +14,9 @@ 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'
@@ -69,8 +71,8 @@ export const renderTable = ({
clientConfig?: ClientConfig
collectionConfig?: SanitizedCollectionConfig
collections?: string[]
columnPreferences: ColumnPreference[]
columns?: ColumnPreference[]
columnPreferences: ListPreferences['columns']
columns?: ListPreferences['columns']
customCellProps?: Record<string, any>
docs: PaginatedDocs['docs']
drawerSlug?: string
@@ -107,7 +109,7 @@ export const renderTable = ({
const columns = columnsFromArgs
? columnsFromArgs?.filter((column) =>
flattenTopLevelFields(fields, true)?.some(
(field) => 'name' in field && column[field.name],
(field) => 'name' in field && field.name === column.accessor,
),
)
: getInitialColumns(fields, useAsTitle, [])
@@ -128,7 +130,7 @@ export const renderTable = ({
const columns = columnsFromArgs
? columnsFromArgs?.filter((column) =>
flattenTopLevelFields(clientCollectionConfig.fields, true)?.some(
(field) => 'name' in field && field.name in column,
(field) => 'name' in field && field.name === column.accessor,
),
)
: getInitialColumns(

View File

@@ -151,7 +151,6 @@ export function DefaultListView(props: ListViewClientProps) {
])
}
}, [setStepNav, labels, drawerDepth])
return (
<Fragment>
<TableColumnsProvider

View File

@@ -170,7 +170,12 @@ describe('Text', () => {
user: client.user,
key: 'text-fields-list',
value: {
columns: [{ disableListColumnText: true }],
columns: [
{
accessor: 'disableListColumnText',
active: true,
},
],
},
})

View File

@@ -31,7 +31,7 @@
}
],
"paths": {
"@payload-config": ["./test/_community/config.ts"],
"@payload-config": ["./test/fields-relationship/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"],