chore: builds rows on init, refactors preferences for documents

This commit is contained in:
Jarrod Flesch
2023-06-07 22:21:08 -04:00
parent 551f1bd09f
commit 87554e9b16
22 changed files with 221 additions and 220 deletions

View File

@@ -36,7 +36,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
const hasInitializedState = useRef(false);
const [isOpen, setIsOpen] = useState(false);
const [collectionConfig] = useRelatedCollections(collectionSlug);
const { docPermissions, id } = useDocumentInfo();
const { docPermissions, id, getDocPreferences } = useDocumentInfo();
const [fields, setFields] = useState(() => formatFields(collectionConfig, true));
@@ -57,8 +57,10 @@ const Content: React.FC<DocumentDrawerProps> = ({
}
const awaitInitialState = async () => {
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({
fieldSchema: fields,
preferences,
data,
user,
operation: id ? 'update' : 'create',
@@ -71,7 +73,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
awaitInitialState();
hasInitializedState.current = true;
}, [data, fields, id, user, locale, isLoadingDocument, t]);
}, [data, fields, id, user, locale, isLoadingDocument, t, getDocPreferences]);
useEffect(() => {
setIsOpen(Boolean(modalState[drawerSlug]?.isOpen));

View File

@@ -1,5 +1,4 @@
import React, { createContext, useCallback, useContext, useEffect, useReducer, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
import { usePreferences } from '../../utilities/Preferences';
import { ListPreferences } from '../../views/collections/List/types';
@@ -45,7 +44,6 @@ export const TableColumnsProvider: React.FC<{
const prevCollection = useRef<SanitizedCollectionConfig['slug']>();
const hasInitialized = useRef(false);
const { getPreference, setPreference } = usePreferences();
const { t } = useTranslation();
const [formattedFields] = useState<Field[]>(() => formatFields(collection));
const [tableColumns, dispatchTableColumns] = useReducer(columnReducer, {}, () => {

View File

@@ -24,6 +24,9 @@ type Args = {
data: Data
fullData: Data
t: TFunction
preferences: {
[key: string]: unknown
}
}
export const addFieldStatePromise = async ({
@@ -38,6 +41,7 @@ export const addFieldStatePromise = async ({
id,
operation,
t,
preferences,
}: Args): Promise<void> => {
if (fieldAffectsData(field)) {
const fieldState: Field = {
@@ -78,8 +82,7 @@ export const addFieldStatePromise = async ({
switch (field.type) {
case 'array': {
const arrayValue = Array.isArray(valueWithDefault) ? valueWithDefault : [];
const promises = arrayValue.map((row, i) => {
const { promises, rowMetadata } = arrayValue.reduce((acc, row, i) => {
const rowPath = `${path}${field.name}.${i}.`;
state[`${rowPath}id`] = {
value: row.id,
@@ -87,7 +90,7 @@ export const addFieldStatePromise = async ({
valid: true,
};
return iterateFields({
acc.promises.push(iterateFields({
state,
fields: field.fields,
data: row,
@@ -99,9 +102,23 @@ export const addFieldStatePromise = async ({
locale,
operation,
t,
preferences,
}));
console.log(`${path}${field.name}`, { preferences });
acc.rowMetadata.push({
id: row.id,
collapsed: (preferences?.fields?.[`${path}${field.name}`]?.collapsed || []).includes(row.id),
});
return acc;
}, {
promises: [],
rowMetadata: [],
});
console.log(rowMetadata);
await Promise.all(promises);
// Add values to field state
@@ -117,6 +134,8 @@ export const addFieldStatePromise = async ({
}
}
fieldState.rows = rowMetadata;
// Add field to state
state[`${path}${field.name}`] = fieldState;
@@ -126,8 +145,7 @@ export const addFieldStatePromise = async ({
case 'blocks': {
const blocksValue = Array.isArray(valueWithDefault) ? valueWithDefault : [];
const promises = [];
blocksValue.forEach((row, i) => {
const { promises, rowMetadata } = blocksValue.reduce((acc, row, i) => {
const block = field.blocks.find((blockType) => blockType.slug === row.blockType);
const rowPath = `${path}${field.name}.${i}.`;
@@ -150,7 +168,7 @@ export const addFieldStatePromise = async ({
valid: true,
};
promises.push(iterateFields({
acc.promises.push(iterateFields({
state,
fields: block.fields,
data: row,
@@ -162,10 +180,24 @@ export const addFieldStatePromise = async ({
operation,
id,
t,
preferences,
}));
}
acc.rowMetadata.push({
id: row.id,
collapsed: (preferences?.fields?.[`${path}${field.name}`]?.collapsed || []).includes(row.id),
blockType: row.blockType,
});
}
return acc;
}, {
promises: [],
rowMetadata: [],
});
await Promise.all(promises);
// Add values to field state
if (valueWithDefault === null) {
fieldState.value = null;
@@ -179,6 +211,8 @@ export const addFieldStatePromise = async ({
}
}
fieldState.rows = rowMetadata;
// Add field to state
state[`${path}${field.name}`] = fieldState;
@@ -198,6 +232,7 @@ export const addFieldStatePromise = async ({
locale,
user,
t,
preferences,
});
break;
@@ -227,6 +262,7 @@ export const addFieldStatePromise = async ({
locale,
operation,
t,
preferences,
});
} else if (field.type === 'tabs') {
const promises = field.tabs.map((tab) => iterateFields({
@@ -241,6 +277,7 @@ export const addFieldStatePromise = async ({
locale,
operation,
t,
preferences,
}));
await Promise.all(promises);

View File

@@ -13,6 +13,9 @@ type Args = {
operation?: 'create' | 'update'
locale: string
t: TFunction
preferences: {
[key: string]: unknown
}
}
const buildStateFromSchema = async (args: Args): Promise<Fields> => {
@@ -24,6 +27,7 @@ const buildStateFromSchema = async (args: Args): Promise<Fields> => {
operation,
locale,
t,
preferences,
} = args;
if (fieldSchema) {
@@ -41,6 +45,7 @@ const buildStateFromSchema = async (args: Args): Promise<Fields> => {
fullData,
parentPassesCondition: true,
t,
preferences,
});
return state;

View File

@@ -16,6 +16,9 @@ type Args = {
id: string | number
operation: 'create' | 'update'
t: TFunction
preferences: {
[key: string]: unknown
}
}
export const iterateFields = async ({
@@ -30,6 +33,7 @@ export const iterateFields = async ({
id,
state,
t,
preferences,
}: Args): Promise<void> => {
const promises = [];
fields.forEach((field) => {
@@ -49,6 +53,7 @@ export const iterateFields = async ({
passesCondition,
data,
t,
preferences,
}));
}
});

View File

@@ -207,15 +207,28 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
}
case 'SET_ROW_COLLAPSED': {
const { rowID, path, collapsed } = action;
const { rowID, path, collapsed, setDocFieldPreferences } = action;
const arrayState = state[path];
const matchedRowIndex = arrayState.rows.findIndex(({ id }) => rowID === id);
if (matchedRowIndex > -1) {
arrayState.rows[matchedRowIndex].collapsed = collapsed;
const { matchedIndex, collapsedRowIDs } = state[path].rows.reduce((acc, row, index) => {
const isMatchingRow = row.id === rowID;
if (isMatchingRow) acc.matchedIndex = index;
if (!isMatchingRow && row.collapsed) acc.collapsedRowIDs.push(row.id);
else if (isMatchingRow && collapsed) acc.collapsedRowIDs.push(row.id);
return acc;
}, {
matchedIndex: undefined,
collapsedRowIDs: [],
});
if (matchedIndex > -1) {
arrayState.rows[matchedIndex].collapsed = collapsed;
setDocFieldPreferences(path, { collapsed: collapsedRowIDs });
}
console.log({ collapsedRowIDs });
const newState = {
...state,
[path]: {
@@ -227,20 +240,29 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
}
case 'SET_ALL_ROWS_COLLAPSED': {
const { collapsed, path } = action;
const { collapsed, path, setDocFieldPreferences } = action;
const newRowsMetadata = state[path].rows.map((rowMetadata) => {
return {
...rowMetadata,
const { rows, collapsedRowIDs } = state[path].rows.reduce((acc, row) => {
if (collapsed) acc.collapsedRowIDs.push(row.id);
acc.rows.push({
...row,
collapsed,
};
}, []);
});
return acc;
}, {
rows: [],
collapsedRowIDs: [],
});
setDocFieldPreferences(path, { collapsed: collapsedRowIDs });
return {
...state,
[path]: {
...state[path],
rows: newRowsMetadata,
rows,
},
};
}

View File

@@ -49,7 +49,7 @@ const Form: React.FC<Props> = (props) => {
const locale = useLocale();
const { t, i18n } = useTranslation('general');
const { refreshCookie, user } = useAuth();
const { id } = useDocumentInfo();
const { id, getDocPreferences } = useDocumentInfo();
const operation = useOperation();
const [modified, setModified] = useState(false);
@@ -332,11 +332,12 @@ const Form: React.FC<Props> = (props) => {
}, [contextRef]);
const reset = useCallback(async (fieldSchema: Field[], data: unknown) => {
const state = await buildStateFromSchema({ fieldSchema, data, user, id, operation, locale, t });
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({ fieldSchema, preferences, data, user, id, operation, locale, t });
contextRef.current = { ...initContextState } as FormContextType;
setModified(false);
dispatchFields({ type: 'REPLACE_STATE', state });
}, [id, user, operation, locale, t, dispatchFields]);
}, [id, user, operation, locale, t, dispatchFields, getDocPreferences]);
const replaceState = useCallback((state: Fields) => {
contextRef.current = { ...initContextState } as FormContextType;

View File

@@ -1,7 +1,12 @@
import React, { Dispatch } from 'react';
import { Condition, Field as FieldConfig, Validate } from '../../../../fields/config/types';
import { User } from '../../../../auth/types';
import { Row } from '../field-types/rowReducer';
export type Row = {
id: string
collapsed?: boolean
blockType?: string
}
export type Field = {
value: unknown
@@ -121,12 +126,14 @@ export type SET_ROW_COLLAPSED = {
path: string
rowID: string
collapsed: boolean
setDocFieldPreferences: (field: string, fieldPreferences: { [key: string]: unknown }) => void
}
export type SET_ALL_ROWS_COLLAPSED = {
type: 'SET_ALL_ROWS_COLLAPSED'
path: string
collapsed: boolean
setDocFieldPreferences: (field: string, fieldPreferences: { [key: string]: unknown }) => void
}
export type FieldAction =

View File

@@ -16,7 +16,6 @@ import { useOperation } from '../../../utilities/OperationProvider';
import { Collapsible } from '../../../elements/Collapsible';
import RenderFields from '../../RenderFields';
import { Props } from './types';
import { usePreferences } from '../../../utilities/Preferences';
import { ArrayAction } from '../../../elements/ArrayAction';
import { scrollToID } from '../../../../utilities/scrollToID';
import HiddenInput from '../HiddenInput';
@@ -49,7 +48,6 @@ const ArrayFieldType: React.FC<Props> = (props) => {
readOnly,
description,
condition,
initCollapsed,
className,
components,
},
@@ -62,8 +60,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
const CustomRowLabel = components?.RowLabel || undefined;
const { preferencesKey, id } = useDocumentInfo();
const { getPreference, setPreference } = usePreferences();
const { setDocFieldPreferences, id, getDocPreferences } = useDocumentInfo();
const formContext = useForm();
const { user } = useAuth();
const locale = useLocale();
@@ -100,24 +97,24 @@ const ArrayFieldType: React.FC<Props> = (props) => {
showError,
errorMessage,
value,
rows = [],
rows,
} = useField<number>({
path,
validate: memoizedValidate,
condition,
// TODO: what does this do? How can we implement with new approach?
// disableFormData: rowsOLD?.length > 0,
hasRows: true,
});
const addRow = useCallback(async (rowIndex: number) => {
const subFieldState = await buildStateFromSchema({ fieldSchema: fields, operation, id, user, locale, t });
const preferences = await getDocPreferences();
const subFieldState = await buildStateFromSchema({ fieldSchema: fields, preferences, operation, id, user, locale, t });
dispatchFields({ type: 'ADD_ROW', rowIndex, subFieldState, path });
setModified(true);
setTimeout(() => {
scrollToID(`${path}-row-${rowIndex + 1}`);
}, 0);
}, [dispatchFields, fields, id, locale, operation, path, setModified, t, user]);
}, [dispatchFields, fields, id, locale, operation, path, setModified, t, user, getDocPreferences]);
const duplicateRow = useCallback(async (rowIndex: number) => {
dispatchFields({ type: 'DUPLICATE_ROW', rowIndex, path });
@@ -138,70 +135,13 @@ const ArrayFieldType: React.FC<Props> = (props) => {
setModified(true);
}, [dispatchFields, path, setModified]);
const setCollapse = useCallback(
async (rowID: string, collapsed: boolean) => {
dispatchFields({ type: 'SET_ROW_COLLAPSED', path, collapsed, rowID });
if (preferencesKey) {
const preferencesToSet = (await getPreference(preferencesKey)) || { fields: {} };
let newCollapsedState: string[] = preferencesToSet?.fields?.[path]?.collapsed;
if (initCollapsed && typeof newCollapsedState === 'undefined') {
newCollapsedState = rows.map((row) => row.id);
} else if (typeof newCollapsedState === 'undefined') {
newCollapsedState = [];
}
if (!collapsed) {
newCollapsedState = newCollapsedState.filter((existingID) => existingID !== rowID);
} else {
newCollapsedState.push(rowID);
}
setPreference(preferencesKey, {
...preferencesToSet,
fields: {
...(preferencesToSet?.fields || {}),
[path]: {
...preferencesToSet?.fields?.[path],
collapsed: newCollapsedState,
},
},
});
}
},
[getPreference, initCollapsed, path, preferencesKey, rows, setPreference, dispatchFields],
);
const setCollapse = useCallback(async (rowID: string, collapsed: boolean) => {
dispatchFields({ type: 'SET_ROW_COLLAPSED', path, collapsed, rowID, setDocFieldPreferences });
}, [dispatchFields, path, setDocFieldPreferences]);
const toggleCollapseAll = useCallback(async (collapsed: boolean) => {
dispatchFields({ type: 'SET_ALL_ROWS_COLLAPSED', path, collapsed });
if (preferencesKey) {
const preferencesToSet = await getPreference(preferencesKey) || { fields: {} };
setPreference(preferencesKey, {
...preferencesToSet,
fields: {
...preferencesToSet?.fields || {},
[path]: {
...preferencesToSet?.fields?.[path],
collapsed: collapsed ? rows.map(({ id: rowID }) => rowID) : [],
},
},
});
}
}, [dispatchFields, getPreference, path, preferencesKey, rows, setPreference]);
// TODO: move this to the useField/useForm hook
// useEffect(() => {
// const initializeRowState = async () => {
// const data = formContext.getDataByPath<Row[]>(path);
// const preferences = (await getPreference(preferencesKey)) || { fields: {} };
// dispatchRows({ type: 'SET_ALL', data: data || [], collapsedState: preferences?.fields?.[path]?.collapsed, initCollapsed });
// };
// initializeRowState();
// }, [formContext, path, getPreference, preferencesKey, initCollapsed]);
dispatchFields({ type: 'SET_ALL_ROWS_COLLAPSED', path, collapsed, setDocFieldPreferences });
}, [dispatchFields, path, setDocFieldPreferences]);
const hasMaxRows = maxRows && rows?.length >= maxRows;

View File

@@ -1,7 +1,6 @@
import React, { Fragment, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useAuth } from '../../../utilities/Auth';
import { usePreferences } from '../../../utilities/Preferences';
import { useLocale } from '../../../utilities/Locale';
import withCondition from '../../withCondition';
import { useDocumentInfo } from '../../../utilities/DocumentInfo';
@@ -57,16 +56,13 @@ const BlocksField: React.FC<Props> = (props) => {
readOnly,
description,
condition,
initCollapsed,
className,
},
} = props;
const path = pathFromProps || name;
const { preferencesKey, id } = useDocumentInfo();
const { getPreference } = usePreferences();
const { setPreference } = usePreferences();
const { id, setDocFieldPreferences, getDocPreferences } = useDocumentInfo();
const formContext = useForm();
const { user } = useAuth();
const locale = useLocale();
@@ -104,20 +100,19 @@ const BlocksField: React.FC<Props> = (props) => {
path,
validate: memoizedValidate,
condition,
// TODO: what does this do? How can we implement with new approach?
// disableFormData: rowsOLD?.length > 0,
});
const addRow = useCallback(async (rowIndex: number, blockType: string) => {
const block = blocks.find((potentialBlock) => potentialBlock.slug === blockType);
const subFieldState = await buildStateFromSchema({ fieldSchema: block.fields, operation, id, user, locale, t });
const preferences = await getDocPreferences();
const subFieldState = await buildStateFromSchema({ fieldSchema: block.fields, preferences, operation, id, user, locale, t });
dispatchFields({ type: 'ADD_ROW', rowIndex, subFieldState, path });
setModified(true);
setTimeout(() => {
scrollToID(`${path}-row-${rowIndex + 1}`);
}, 0);
}, [blocks, dispatchFields, id, locale, operation, path, setModified, t, user]);
}, [blocks, dispatchFields, id, locale, operation, path, getDocPreferences, setModified, t, user]);
const duplicateRow = useCallback(async (rowIndex: number) => {
dispatchFields({ type: 'DUPLICATE_ROW', rowIndex, path });
@@ -139,68 +134,12 @@ const BlocksField: React.FC<Props> = (props) => {
}, [dispatchFields, path, setModified]);
const setCollapse = useCallback(async (rowID: string, collapsed: boolean) => {
dispatchFields({ type: 'SET_ROW_COLLAPSED', path, collapsed, rowID });
if (preferencesKey) {
const preferencesToSet = await getPreference(preferencesKey) || { fields: {} };
let newCollapsedState: string[] = preferencesToSet?.fields?.[path]?.collapsed;
if (initCollapsed && typeof newCollapsedState === 'undefined') {
newCollapsedState = rows.map((row) => row.id);
} else if (typeof newCollapsedState === 'undefined') {
newCollapsedState = [];
}
if (!collapsed) {
newCollapsedState = newCollapsedState.filter((existingID) => existingID !== rowID);
} else {
newCollapsedState.push(rowID);
}
setPreference(preferencesKey, {
...preferencesToSet,
fields: {
...preferencesToSet?.fields || {},
[path]: {
...preferencesToSet?.fields?.[path],
collapsed: newCollapsedState,
},
},
});
}
}, [getPreference, initCollapsed, path, preferencesKey, rows, setPreference, dispatchFields]);
dispatchFields({ type: 'SET_ROW_COLLAPSED', path, collapsed, rowID, setDocFieldPreferences });
}, [dispatchFields, path, setDocFieldPreferences]);
const toggleCollapseAll = useCallback(async (collapsed: boolean) => {
dispatchFields({ type: 'SET_ALL_ROWS_COLLAPSED', path, collapsed });
if (preferencesKey) {
const preferencesToSet = await getPreference(preferencesKey) || { fields: {} };
setPreference(preferencesKey, {
...preferencesToSet,
fields: {
...preferencesToSet?.fields || {},
[path]: {
...preferencesToSet?.fields?.[path],
collapsed: collapsed ? rows.map(({ id: rowID }) => rowID) : [],
},
},
});
}
}, [dispatchFields, getPreference, path, preferencesKey, rows, setPreference]);
// TODO: move this to the useField/useForm hook
// Set row count on mount and when form context is reset
// useEffect(() => {
// const initializeRowState = async () => {
// const data = formContext.getDataByPath<Row[]>(path);
// const preferences = (await getPreference(preferencesKey)) || { fields: {} };
// dispatchRows({ type: 'SET_ALL', data: data || [], collapsedState: preferences?.fields?.[path]?.collapsed, initCollapsed });
// };
// initializeRowState();
// }, [formContext, path, getPreference, preferencesKey, initCollapsed]);
dispatchFields({ type: 'SET_ALL_ROWS_COLLAPSED', path, collapsed, setDocFieldPreferences });
}, [dispatchFields, path, setDocFieldPreferences]);
const hasMaxRows = maxRows && rows?.length >= maxRows;

View File

@@ -16,6 +16,7 @@ import { useAuth } from '../../../../../../utilities/Auth';
import { Fields } from '../../../../../Form/types';
import { useLocale } from '../../../../../../utilities/Locale';
import { useDrawerSlug } from '../../../../../../elements/Drawer/useDrawerSlug';
import { useDocumentInfo } from '../../../../../../utilities/DocumentInfo';
/**
* This function is called when an new link is created - not when an existing link is edited.
@@ -76,6 +77,7 @@ export const LinkButton: React.FC<{
const { openModal, closeModal } = useModal();
const drawerSlug = useDrawerSlug('rich-text-link');
const { getDocPreferences } = useDocumentInfo();
return (
<Fragment>
@@ -96,7 +98,8 @@ export const LinkButton: React.FC<{
text: editor.selection ? Editor.string(editor, editor.selection) : '',
};
const state = await buildStateFromSchema({ fieldSchema, data, user, operation: 'create', locale, t });
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({ fieldSchema, preferences, data, user, operation: 'create', locale, t });
setInitialState(state);
}
}

View File

@@ -16,8 +16,10 @@ import deepCopyObject from '../../../../../../../../utilities/deepCopyObject';
import Button from '../../../../../../elements/Button';
import { getTranslation } from '../../../../../../../../utilities/getTranslation';
import { Props as RichTextFieldProps } from '../../../types';
import './index.scss';
import { useDrawerSlug } from '../../../../../../elements/Drawer/useDrawerSlug';
import { useDocumentInfo } from '../../../../../../utilities/DocumentInfo';
import './index.scss';
const baseClass = 'rich-text-link';
@@ -80,6 +82,7 @@ export const LinkElement: React.FC<{
const [renderModal, setRenderModal] = useState(false);
const [renderPopup, setRenderPopup] = useState(false);
const [initialState, setInitialState] = useState<Fields>({});
const { getDocPreferences } = useDocumentInfo();
const [fieldSchema] = useState(() => {
const fields = transformExtraFields(customFieldSchema, config, i18n);
@@ -106,12 +109,13 @@ export const LinkElement: React.FC<{
fields: deepCopyObject(element.fields),
};
const state = await buildStateFromSchema({ fieldSchema, data, user, operation: 'update', locale, t });
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({ fieldSchema, preferences, data, user, operation: 'update', locale, t });
setInitialState(state);
};
awaitInitialState();
}, [renderModal, element, fieldSchema, user, locale, t]);
}, [renderModal, element, fieldSchema, user, locale, t, getDocPreferences]);
return (
<span

View File

@@ -15,6 +15,7 @@ import FormSubmit from '../../../../../../Submit';
import buildStateFromSchema from '../../../../../../Form/buildStateFromSchema';
import { getTranslation } from '../../../../../../../../../utilities/getTranslation';
import deepCopyObject from '../../../../../../../../../utilities/deepCopyObject';
import { useDocumentInfo } from '../../../../../../../utilities/DocumentInfo';
export const UploadDrawer: React.FC<ElementProps & {
drawerSlug: string
@@ -33,6 +34,7 @@ export const UploadDrawer: React.FC<ElementProps & {
const locale = useLocale();
const { user } = useAuth();
const { closeModal } = useModal();
const { getDocPreferences } = useDocumentInfo();
const [initialState, setInitialState] = useState({});
const fieldSchema = fieldProps?.admin?.upload?.collections?.[relatedCollection.slug]?.fields;
@@ -53,12 +55,13 @@ export const UploadDrawer: React.FC<ElementProps & {
useEffect(() => {
const awaitInitialState = async () => {
const state = await buildStateFromSchema({ fieldSchema, data: deepCopyObject(element?.fields || {}), user, operation: 'update', locale, t });
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({ fieldSchema, preferences, data: deepCopyObject(element?.fields || {}), user, operation: 'update', locale, t });
setInitialState(state);
};
awaitInitialState();
}, [fieldSchema, element.fields, user, locale, t]);
}, [fieldSchema, element.fields, user, locale, t, getDocPreferences]);
return (
<Drawer

View File

@@ -19,6 +19,7 @@ const useField = <T, >(options: Options): FieldType<T> => {
validate,
disableFormData = false,
condition,
hasRows,
} = options;
const submitted = useFormSubmitted();
@@ -57,7 +58,7 @@ const useField = <T, >(options: Options): FieldType<T> => {
type: 'UPDATE',
path,
value: val,
disableFormData,
disableFormData: disableFormData || (hasRows && val > 0),
});
}, [
setModified,
@@ -65,6 +66,7 @@ const useField = <T, >(options: Options): FieldType<T> => {
path,
dispatchField,
disableFormData,
hasRows,
]);
// Store result from hook as ref
@@ -86,7 +88,7 @@ const useField = <T, >(options: Options): FieldType<T> => {
const action: UPDATE = {
type: 'UPDATE',
path,
disableFormData,
disableFormData: disableFormData || (hasRows ? typeof value === 'number' && value > 0 : false),
validate,
condition,
value,

View File

@@ -1,11 +1,12 @@
import { Condition, Validate } from '../../../../fields/config/types';
import { Row } from '../field-types/rowReducer';
import { Row } from '../Form/types';
export type Options = {
path: string
validate?: Validate
disableFormData?: boolean
condition?: Condition
hasRows?: boolean
}
export type FieldType<T> = {

View File

@@ -29,7 +29,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
const id = idFromProps || (getIDFromParams ? idFromParams : null);
const { serverURL, routes: { api } } = useConfig();
const { getPreference } = usePreferences();
const { getPreference, setPreference } = usePreferences();
const { i18n } = useTranslation();
const { permissions } = useAuth();
const [publishedDoc, setPublishedDoc] = useState<TypeWithID & TypeWithTimestamps>(null);
@@ -39,20 +39,17 @@ export const DocumentInfoProvider: React.FC<Props> = ({
const baseURL = `${serverURL}${api}`;
let slug: string;
let type: 'global' | 'collection';
let pluralType: 'globals' | 'collections';
let preferencesKey: string;
if (global) {
slug = global.slug;
type = 'global';
pluralType = 'globals';
preferencesKey = `global-${slug}`;
}
if (collection) {
slug = collection.slug;
type = 'collection';
pluralType = 'collections';
if (id) {
@@ -213,27 +210,39 @@ export const DocumentInfoProvider: React.FC<Props> = ({
}
}, [serverURL, api, pluralType, slug, id, permissions, i18n.language]);
const setDocFieldPreferences = useCallback<ContextType['setDocFieldPreferences']>(async (path, fieldPreferences) => {
const allPreferences = await getPreference<DocumentPreferences>(path);
if (preferencesKey) {
setPreference(preferencesKey, {
...allPreferences,
fields: {
...(allPreferences?.fields || {}),
[path]: {
...allPreferences?.fields?.[path],
...fieldPreferences,
},
},
});
}
}, [getPreference, setPreference, preferencesKey]);
const getDocPreferences = useCallback<ContextType['getDocPreferences']>(async () => {
const allPreferences = await getPreference<DocumentPreferences>(preferencesKey);
return allPreferences;
}, [getPreference, preferencesKey]);
useEffect(() => {
getVersions();
}, [getVersions]);
useEffect(() => {
if (preferencesKey) {
const getDocPreferences = async () => {
await getPreference<DocumentPreferences>(preferencesKey);
};
getDocPreferences();
}
}, [getPreference, preferencesKey]);
useEffect(() => {
getDocPermissions();
}, [getDocPermissions]);
const value = {
const value: ContextType = {
slug,
type,
preferencesKey,
global,
collection,
@@ -244,6 +253,8 @@ export const DocumentInfoProvider: React.FC<Props> = ({
id,
getDocPermissions,
docPermissions,
setDocFieldPreferences,
getDocPreferences,
};
return (

View File

@@ -21,6 +21,8 @@ export type ContextType = {
getVersions: () => Promise<void>
docPermissions: DocumentPermissions
getDocPermissions: () => Promise<void>
setDocFieldPreferences: (field: string, fieldPreferences: { [key: string]: unknown }) => void
getDocPreferences: () => Promise<{ [key: string]: unknown }>
}
export type Props = {

View File

@@ -19,7 +19,7 @@ const AccountView: React.FC = () => {
const { setStepNav } = useStepNav();
const { user } = useAuth();
const [initialState, setInitialState] = useState<Fields>();
const { id, preferencesKey, docPermissions, getDocPermissions, slug } = useDocumentInfo();
const { id, preferencesKey, docPermissions, getDocPermissions, slug, getDocPreferences } = useDocumentInfo();
const { getPreference } = usePreferences();
const {
@@ -61,9 +61,10 @@ const AccountView: React.FC = () => {
const onSave = React.useCallback(async (json: any) => {
getDocPermissions();
const state = await buildStateFromSchema({ fieldSchema: collection.fields, data: json.doc, user, id, operation: 'update', locale, t });
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({ fieldSchema: collection.fields, preferences, data: json.doc, user, id, operation: 'update', locale, t });
setInitialState(state);
}, [collection, user, id, t, locale, getDocPermissions]);
}, [collection, user, id, t, locale, getDocPermissions, getDocPreferences]);
useEffect(() => {
const nav = [{
@@ -75,8 +76,10 @@ const AccountView: React.FC = () => {
useEffect(() => {
const awaitInitialState = async () => {
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({
fieldSchema: fields,
preferences,
data: dataToRender,
operation: 'update',
id,
@@ -89,7 +92,7 @@ const AccountView: React.FC = () => {
};
if (dataToRender) awaitInitialState();
}, [dataToRender, fields, id, user, locale, preferencesKey, getPreference, t]);
}, [dataToRender, fields, id, user, locale, preferencesKey, getPreference, t, getDocPreferences]);
const isLoading = !initialState || !docPermissions || isLoadingData;

View File

@@ -21,7 +21,7 @@ const GlobalView: React.FC<IndexProps> = (props) => {
const { user } = useAuth();
const [initialState, setInitialState] = useState<Fields>();
const [updatedAt, setUpdatedAt] = useState<string>();
const { getVersions, preferencesKey, docPermissions, getDocPermissions } = useDocumentInfo();
const { getVersions, preferencesKey, docPermissions, getDocPermissions, getDocPreferences } = useDocumentInfo();
const { getPreference } = usePreferences();
const { t } = useTranslation();
@@ -51,9 +51,10 @@ const GlobalView: React.FC<IndexProps> = (props) => {
getVersions();
getDocPermissions();
setUpdatedAt(json?.result?.updatedAt);
const state = await buildStateFromSchema({ fieldSchema: fields, data: json.result, operation: 'update', user, locale, t });
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({ fieldSchema: fields, preferences, data: json.result, operation: 'update', user, locale, t });
setInitialState(state);
}, [getVersions, fields, user, locale, t, getDocPermissions]);
}, [getVersions, fields, user, locale, t, getDocPermissions, getDocPreferences]);
const [{ data, isLoading: isLoadingData }] = usePayloadAPI(
`${serverURL}${api}/globals/${slug}`,
@@ -72,13 +73,14 @@ const GlobalView: React.FC<IndexProps> = (props) => {
useEffect(() => {
const awaitInitialState = async () => {
const state = await buildStateFromSchema({ fieldSchema: fields, data: dataToRender, user, operation: 'update', locale, t });
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({ fieldSchema: fields, preferences, data: dataToRender, user, operation: 'update', locale, t });
await getPreference(preferencesKey);
setInitialState(state);
};
if (dataToRender) awaitInitialState();
}, [dataToRender, fields, user, locale, getPreference, preferencesKey, t]);
}, [dataToRender, fields, user, locale, getPreference, preferencesKey, t, getDocPreferences]);
const isLoading = !initialState || !docPermissions || isLoadingData;

View File

@@ -13,7 +13,6 @@ import { useLocale } from '../../../utilities/Locale';
import { IndexProps } from './types';
import { useDocumentInfo } from '../../../utilities/DocumentInfo';
import { Fields } from '../../../forms/Form/types';
import { usePreferences } from '../../../utilities/Preferences';
import { EditDepthContext } from '../../../utilities/EditDepth';
import { CollectionPermission } from '../../../../../auth';
@@ -43,8 +42,7 @@ const EditView: React.FC<IndexProps> = (props) => {
const [internalState, setInternalState] = useState<Fields>();
const [updatedAt, setUpdatedAt] = useState<string>();
const { user } = useAuth();
const { getVersions, preferencesKey, getDocPermissions, docPermissions } = useDocumentInfo();
const { getPreference } = usePreferences();
const { getVersions, getDocPermissions, docPermissions, getDocPreferences } = useDocumentInfo();
const { t } = useTranslation('general');
const [{ data, isLoading: isLoadingData, isError }] = usePayloadAPI(
@@ -61,23 +59,24 @@ const EditView: React.FC<IndexProps> = (props) => {
if (!isEditing) {
setRedirect(`${admin}/collections/${collection.slug}/${json?.doc?.id}`);
} else {
const state = await buildStateFromSchema({ fieldSchema: collection.fields, data: json.doc, user, id, operation: 'update', locale, t });
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({ fieldSchema: collection.fields, preferences, data: json.doc, user, id, operation: 'update', locale, t });
setInternalState(state);
}
}, [admin, collection, isEditing, getVersions, user, id, t, locale, getDocPermissions]);
}, [admin, collection.fields, collection.slug, getDocPreferences, getDocPermissions, getVersions, id, isEditing, locale, t, user]);
const dataToRender = (locationState as Record<string, unknown>)?.data || data;
useEffect(() => {
const awaitInternalState = async () => {
setUpdatedAt(dataToRender?.updatedAt);
const state = await buildStateFromSchema({ fieldSchema: fields, data: dataToRender || {}, user, operation: isEditing ? 'update' : 'create', id, locale, t });
await getPreference(preferencesKey);
const preferences = await getDocPreferences();
const state = await buildStateFromSchema({ fieldSchema: fields, preferences, data: dataToRender || {}, user, operation: isEditing ? 'update' : 'create', id, locale, t });
setInternalState(state);
};
if (!isEditing || dataToRender) awaitInternalState();
}, [dataToRender, fields, isEditing, id, user, locale, preferencesKey, getPreference, t]);
}, [dataToRender, fields, isEditing, id, user, locale, t, getDocPreferences]);
useEffect(() => {
if (redirect) {

View File

@@ -6,7 +6,20 @@ export default buildConfig({
slug: 'arrays',
fields: [
{
name: 'array',
name: 'arrayOfFields',
type: 'array',
fields: [
{
type: 'text',
name: 'required',
required: true,
},
{
type: 'text',
name: 'optional',
},
{
name: 'innerArrayOfFields',
type: 'array',
fields: [
{
@@ -23,4 +36,6 @@ export default buildConfig({
],
},
],
},
],
});

View File

@@ -24,7 +24,7 @@ describe('array-update', () => {
const doc = await payload.create({
collection,
data: {
array: [
arrayOfFields: [
{
required: 'a required field here',
optional: originalText,
@@ -37,7 +37,7 @@ describe('array-update', () => {
},
});
const arrayWithExistingValues = [...doc.array];
const arrayWithExistingValues = [...doc.arrayOfFields];
const updatedText = 'this is some new text for the first item in array';
@@ -50,11 +50,11 @@ describe('array-update', () => {
id: doc.id,
collection,
data: {
array: arrayWithExistingValues,
arrayOfFields: arrayWithExistingValues,
},
});
expect(updatedDoc.array?.[0]).toMatchObject({
expect(updatedDoc.arrayOfFields?.[0]).toMatchObject({
required: updatedText,
optional: originalText,
});
@@ -71,7 +71,7 @@ describe('array-update', () => {
const doc = await payload.create({
collection,
data: {
array: [
arrayOfFields: [
{
required: 'a required field here',
optional: 'some optional text',
@@ -85,13 +85,13 @@ describe('array-update', () => {
id: doc.id,
collection,
data: {
array: [
arrayOfFields: [
{
required: updatedText,
},
{
id: doc.array?.[1].id,
required: doc.array?.[1].required as string,
id: doc.arrayOfFields?.[1].id,
required: doc.arrayOfFields?.[1].required as string,
// NOTE - not passing optional field. It should persist
// because we're passing ID
},
@@ -99,9 +99,9 @@ describe('array-update', () => {
},
});
expect(updatedDoc.array?.[0].required).toStrictEqual(updatedText);
expect(updatedDoc.array?.[0].optional).toBeUndefined();
expect(updatedDoc.arrayOfFields?.[0].required).toStrictEqual(updatedText);
expect(updatedDoc.arrayOfFields?.[0].optional).toBeUndefined();
expect(updatedDoc.array?.[1]).toMatchObject(secondArrayItem);
expect(updatedDoc.arrayOfFields?.[1]).toMatchObject(secondArrayItem);
});
});