From 364612e9aeffd212d7141692784c9e6cdc6068fc Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Fri, 14 Apr 2023 13:52:11 -0400 Subject: [PATCH] fix: various fixes and tweaks (#701) --- packages/core/dev-test/config.yml | 5 ++++ packages/core/package.json | 2 +- packages/core/src/actions/entries.ts | 3 +- packages/core/src/actions/media.ts | 2 -- .../core/src/backends/proxy/implementation.ts | 5 +++- .../common/progress/CircularProgress.tsx | 29 +++++++++++++++++-- .../src/components/common/table/Table.tsx | 2 +- .../editor-control-pane/EditorControl.tsx | 9 ++++-- .../editor-control-pane/EditorControlPane.tsx | 2 +- .../media-library/common/MediaLibraryCard.tsx | 4 ++- .../common/MediaLibraryCardGrid.tsx | 1 + packages/core/src/interface.ts | 4 +-- .../core/src/lib/hooks/useHasChildErrors.ts | 5 ++-- packages/core/src/lib/util/media.util.ts | 8 ++--- packages/core/src/reducers/entryDraft.ts | 6 ++-- .../core/src/reducers/selectors/entryDraft.ts | 11 ++++--- .../core/src/widgets/list/ListControl.tsx | 2 +- .../src/widgets/list/components/ListItem.tsx | 2 +- .../core/src/widgets/object/ObjectControl.tsx | 2 +- .../src/widgets/relation/RelationControl.tsx | 9 ++++-- .../__tests__/RelationControl.spec.ts | 15 ++++++++++ packages/demo/public/config.yml | 5 ++++ 22 files changed, 101 insertions(+), 32 deletions(-) diff --git a/packages/core/dev-test/config.yml b/packages/core/dev-test/config.yml index b7618768..e67eac11 100644 --- a/packages/core/dev-test/config.yml +++ b/packages/core/dev-test/config.yml @@ -515,6 +515,11 @@ collections: widget: markdown pattern: ['# [a-zA-Z0-9]+', 'Must have a header'] required: false + - name: folder_support + label: Folder Support + widget: markdown + media_library: + folder_support: true - name: number label: Number file: _widgets/number.json diff --git a/packages/core/package.json b/packages/core/package.json index f7e1792e..13554a9c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -33,7 +33,7 @@ "type-check": "tsc --watch" }, "main": "dist/static-cms-core.js", - "types": "dist/src/index.d.ts", + "types": "dist/index.d.ts", "files": [ "dist/**/*" ], diff --git a/packages/core/src/actions/entries.ts b/packages/core/src/actions/entries.ts index fc8ea6fc..25ccb7e3 100644 --- a/packages/core/src/actions/entries.ts +++ b/packages/core/src/actions/entries.ts @@ -481,10 +481,11 @@ export function changeDraftFieldValidation( path: string, errors: FieldError[], i18n?: I18nSettings, + isMeta?: boolean, ) { return { type: DRAFT_VALIDATION_ERRORS, - payload: { path, errors, i18n }, + payload: { path, errors, i18n, isMeta }, } as const; } diff --git a/packages/core/src/actions/media.ts b/packages/core/src/actions/media.ts index f19c52e2..0788d2c5 100644 --- a/packages/core/src/actions/media.ts +++ b/packages/core/src/actions/media.ts @@ -54,8 +54,6 @@ async function loadAsset( ): Promise { try { dispatch(loadAssetRequest(resolvedPath)); - // load asset url from backend - // await waitForMediaLibraryToLoad(dispatch, getState()); const { url } = await getMediaFile(getState(), resolvedPath); const asset = createAssetProxy({ path: resolvedPath, url }); dispatch(addAsset(asset)); diff --git a/packages/core/src/backends/proxy/implementation.ts b/packages/core/src/backends/proxy/implementation.ts index 48eca2b6..6d6b7e51 100644 --- a/packages/core/src/backends/proxy/implementation.ts +++ b/packages/core/src/backends/proxy/implementation.ts @@ -169,7 +169,10 @@ export default class ProxyBackend implements BackendClass { async getMediaFile(path: string): Promise { const file = await this.request({ action: 'getMediaFile', - params: { branch: this.branch, path }, + params: { + branch: this.branch, + path, + }, }); return deserializeMediaFile(file); } diff --git a/packages/core/src/components/common/progress/CircularProgress.tsx b/packages/core/src/components/common/progress/CircularProgress.tsx index 10069df7..56524995 100644 --- a/packages/core/src/components/common/progress/CircularProgress.tsx +++ b/packages/core/src/components/common/progress/CircularProgress.tsx @@ -1,18 +1,43 @@ import React from 'react'; +import classNames from '@staticcms/core/lib/util/classNames.util'; + import type { FC } from 'react'; export interface CircularProgressProps { className?: string; 'data-testid'?: string; + size?: 'small' | 'medium'; } -const CircularProgress: FC = ({ className, 'data-testid': dataTestId }) => { +const CircularProgress: FC = ({ + className, + 'data-testid': dataTestId, + size = 'medium', +}) => { return (
- + {columns.map((column, index) => ( {column} diff --git a/packages/core/src/components/entry-editor/editor-control-pane/EditorControl.tsx b/packages/core/src/components/entry-editor/editor-control-pane/EditorControl.tsx index f8508d4a..0797d25a 100644 --- a/packages/core/src/components/entry-editor/editor-control-pane/EditorControl.tsx +++ b/packages/core/src/components/entry-editor/editor-control-pane/EditorControl.tsx @@ -82,7 +82,10 @@ const EditorControl = ({ const [dirty, setDirty] = useState(!isEmpty(value)); - const fieldErrorsSelector = useMemo(() => selectFieldErrors(path, i18n), [i18n, path]); + const fieldErrorsSelector = useMemo( + () => selectFieldErrors(path, i18n, isMeta), + [i18n, isMeta, path], + ); const errors = useAppSelector(fieldErrorsSelector); const hasErrors = (submitted || dirty) && Boolean(errors.length); @@ -103,11 +106,11 @@ const EditorControl = ({ const validateValue = async () => { const errors = await validate(field, value, widget, t); - dispatch(changeDraftFieldValidation(path, errors, i18n)); + dispatch(changeDraftFieldValidation(path, errors, i18n, isMeta)); }; validateValue(); - }, [dirty, dispatch, field, i18n, hidden, path, submitted, t, value, widget, disabled]); + }, [dirty, dispatch, field, i18n, hidden, path, submitted, t, value, widget, disabled, isMeta]); const handleChangeDraftField = useCallback( (value: ValueOrNestedValue) => { diff --git a/packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.tsx b/packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.tsx index 72dd52eb..84c305ef 100644 --- a/packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.tsx +++ b/packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.tsx @@ -117,7 +117,7 @@ const EditorControlPane = ({ { isSelected?: boolean; displayURL: MediaLibraryDisplayURL; + path: string; text: string; draftText: string; type?: string; @@ -44,6 +45,7 @@ interface MediaLibraryCardProps({ isSelected = false, displayURL, + path, text, draftText, type, @@ -60,7 +62,7 @@ const MediaLibraryCard = >) => { const entry = useAppSelector(selectEditingDraft); - const url = useMediaAsset(displayURL.url, collection, field, entry, currentFolder); + const url = useMediaAsset(path, collection, field, entry, currentFolder); const handleDownload = useCallback(() => { const url = displayURL.url; diff --git a/packages/core/src/components/media-library/common/MediaLibraryCardGrid.tsx b/packages/core/src/components/media-library/common/MediaLibraryCardGrid.tsx index 6c005ffd..e90e3a44 100644 --- a/packages/core/src/components/media-library/common/MediaLibraryCardGrid.tsx +++ b/packages/core/src/components/media-library/common/MediaLibraryCardGrid.tsx @@ -126,6 +126,7 @@ const CardWrapper = ({ isDraft={file.draft} draftText={cardDraftText} displayURL={displayURLs[file.id] ?? (file.url ? { url: file.url } : {})} + path={file.path} loadDisplayURL={() => loadDisplayURL(file)} type={file.type} isViewableImage={file.isViewableImage ?? false} diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index 72e85f2c..a5d2f627 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -476,9 +476,9 @@ export abstract class BackendClass { abstract getMediaDisplayURL(displayURL: DisplayURL): Promise; abstract getMedia( - folder?: string, + mediaFolder?: string, folderSupport?: boolean, - mediaPath?: string, + publicFolder?: string, ): Promise; abstract getMediaFile(path: string): Promise; diff --git a/packages/core/src/lib/hooks/useHasChildErrors.ts b/packages/core/src/lib/hooks/useHasChildErrors.ts index b29160ad..8934fd59 100644 --- a/packages/core/src/lib/hooks/useHasChildErrors.ts +++ b/packages/core/src/lib/hooks/useHasChildErrors.ts @@ -8,11 +8,12 @@ export default function useHasChildErrors( path: string, fieldsErrors: FieldsErrors, i18n: I18nSettings | undefined, + isMeta: boolean | undefined, ) { return useMemo(() => { - const dataPath = getEntryDataPath(i18n); + const dataPath = getEntryDataPath(i18n, isMeta); const fullPath = `${dataPath}.${path}`; return Boolean(Object.keys(fieldsErrors).find(key => key.startsWith(fullPath))); - }, [fieldsErrors, i18n, path]); + }, [fieldsErrors, i18n, isMeta, path]); } diff --git a/packages/core/src/lib/util/media.util.ts b/packages/core/src/lib/util/media.util.ts index 0a312a52..feab18ba 100644 --- a/packages/core/src/lib/util/media.util.ts +++ b/packages/core/src/lib/util/media.util.ts @@ -232,7 +232,7 @@ function traverseFields( export function selectMediaFolder( config: Config, collection: Collection | undefined | null, - entryMap: Entry | null | undefined, + entryMap: Entry | undefined | null, field: MediaField | undefined, currentFolder?: string, ) { @@ -257,9 +257,9 @@ export function selectMediaFolder( export function selectMediaFilePublicPath( config: Config, - collection: Collection | null, + collection: Collection | undefined | null, mediaPath: string, - entryMap: Entry | undefined, + entryMap: Entry | undefined | null, field: MediaField | undefined, currentFolder?: string, ) { @@ -299,7 +299,7 @@ export function selectMediaFilePath( mediaPath: string, field: MediaField | undefined, currentFolder?: string, -) { +): string { if (isAbsolutePath(mediaPath)) { return mediaPath; } diff --git a/packages/core/src/reducers/entryDraft.ts b/packages/core/src/reducers/entryDraft.ts index bb5008a2..7739c002 100644 --- a/packages/core/src/reducers/entryDraft.ts +++ b/packages/core/src/reducers/entryDraft.ts @@ -185,10 +185,12 @@ function entryDraftReducer( } case DRAFT_VALIDATION_ERRORS: { - const { path, errors, i18n } = action.payload; + const { path, errors, i18n, isMeta } = action.payload; const fieldsErrors = { ...state.fieldsErrors }; - const dataPath = (i18n && getDataPath(i18n.currentLocale, i18n.defaultLocale)) || ['data']; + const dataPath = isMeta + ? ['meta'] + : (i18n && getDataPath(i18n.currentLocale, i18n.defaultLocale)) || ['data']; const fullPath = `${dataPath.join('.')}.${path}`; if (errors.length === 0) { diff --git a/packages/core/src/reducers/selectors/entryDraft.ts b/packages/core/src/reducers/selectors/entryDraft.ts index be37d578..02f798aa 100644 --- a/packages/core/src/reducers/selectors/entryDraft.ts +++ b/packages/core/src/reducers/selectors/entryDraft.ts @@ -3,13 +3,16 @@ import { getDataPath } from '@staticcms/core/lib/i18n'; import type { I18nSettings } from '@staticcms/core/interface'; import type { RootState } from '@staticcms/core/store'; -export const getEntryDataPath = (i18n: I18nSettings | undefined) => { - return (i18n && getDataPath(i18n.currentLocale, i18n.defaultLocale)) || ['data']; +export const getEntryDataPath = (i18n: I18nSettings | undefined, isMeta: boolean | undefined) => { + return isMeta + ? ['meta'] + : (i18n && getDataPath(i18n.currentLocale, i18n.defaultLocale)) || ['data']; }; export const selectFieldErrors = - (path: string, i18n: I18nSettings | undefined) => (state: RootState) => { - const dataPath = getEntryDataPath(i18n); + (path: string, i18n: I18nSettings | undefined, isMeta: boolean | undefined) => + (state: RootState) => { + const dataPath = getEntryDataPath(i18n, isMeta); const fullPath = `${dataPath.join('.')}.${path}`; return state.entryDraft.fieldsErrors[fullPath] ?? []; }; diff --git a/packages/core/src/widgets/list/ListControl.tsx b/packages/core/src/widgets/list/ListControl.tsx index 80ce056f..90cc0fe3 100644 --- a/packages/core/src/widgets/list/ListControl.tsx +++ b/packages/core/src/widgets/list/ListControl.tsx @@ -293,7 +293,7 @@ const ListControl: FC> = ({ [onChange, internalValue, keys], ); - const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n); + const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n, false); if (valueType === null) { return null; diff --git a/packages/core/src/widgets/list/components/ListItem.tsx b/packages/core/src/widgets/list/components/ListItem.tsx index 510fd4c0..0d02c8dc 100644 --- a/packages/core/src/widgets/list/components/ListItem.tsx +++ b/packages/core/src/widgets/list/components/ListItem.tsx @@ -165,7 +165,7 @@ const ListItem: FC = ({ } }, [entry, field, index, value, valueType]); - const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n); + const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n, false); const finalValue = useMemo(() => { if (field.fields && field.fields.length === 1) { diff --git a/packages/core/src/widgets/object/ObjectControl.tsx b/packages/core/src/widgets/object/ObjectControl.tsx index 7202b2e6..c200faf6 100644 --- a/packages/core/src/widgets/object/ObjectControl.tsx +++ b/packages/core/src/widgets/object/ObjectControl.tsx @@ -31,7 +31,7 @@ const ObjectControl: FC> = ({ const fields = useMemo(() => field.fields, [field.fields]); - const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n); + const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n, false); const renderedField = useMemo(() => { return ( diff --git a/packages/core/src/widgets/relation/RelationControl.tsx b/packages/core/src/widgets/relation/RelationControl.tsx index bfa67f55..bff69095 100644 --- a/packages/core/src/widgets/relation/RelationControl.tsx +++ b/packages/core/src/widgets/relation/RelationControl.tsx @@ -179,11 +179,15 @@ const RelationControl: FC> ); const [options, setOptions] = useState([]); - const [entries, setEntries] = useState([]); - const loading = useMemo(() => options.length === 0, [options.length]); + const [entries, setEntries] = useState(null); + const loading = useMemo(() => !entries, [entries]); const filterOptions = useCallback( (inputValue: string) => { + if (!entries) { + return; + } + const searchFields = field.search_fields; const limit = field.options_length || DEFAULT_OPTIONS_LIMIT; const expandedEntries = expandSearchEntries(entries, searchFields); @@ -334,6 +338,7 @@ const RelationControl: FC> key="loading-indicator" className="absolute inset-y-0 right-4 flex items-center pr-2" data-testid="relation-loading-indicator" + size="small" /> ) : null} diff --git a/packages/core/src/widgets/relation/__tests__/RelationControl.spec.ts b/packages/core/src/widgets/relation/__tests__/RelationControl.spec.ts index 21ebc400..41fe12b5 100644 --- a/packages/core/src/widgets/relation/__tests__/RelationControl.spec.ts +++ b/packages/core/src/widgets/relation/__tests__/RelationControl.spec.ts @@ -306,6 +306,21 @@ describe(RelationControl.name, () => { expect(queryByTestId('relation-loading-indicator')).not.toBeInTheDocument(); }); + it('should stop showing loading indicator if no entries found', async () => { + mockListAllEntries.mockReturnValue([]); + const { getByTestId, queryByTestId } = renderControl({ value: 'Post 1' }); + + const input = getByTestId('autocomplete-input'); + + expect(input).toHaveValue(''); + + getByTestId('relation-loading-indicator'); + + await waitFor(() => + expect(queryByTestId('relation-loading-indicator')).not.toBeInTheDocument(), + ); + }); + it('should not try to load entiries if search collection does not exist', () => { const field: RelationField = { label: 'Relation', diff --git a/packages/demo/public/config.yml b/packages/demo/public/config.yml index 8775ab2b..c761930c 100644 --- a/packages/demo/public/config.yml +++ b/packages/demo/public/config.yml @@ -515,6 +515,11 @@ collections: widget: markdown pattern: ['# [a-zA-Z0-9]+', 'Must have a header'] required: false + - name: folder_support + label: Folder Support + widget: markdown + media_library: + folder_support: true - name: number label: Number file: _widgets/number.json