fix: various fixes and tweaks (#701)

This commit is contained in:
Daniel Lautzenheiser 2023-04-14 13:52:11 -04:00 committed by GitHub
parent 422b7798da
commit 364612e9ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 101 additions and 32 deletions

View File

@ -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

View File

@ -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/**/*"
],

View File

@ -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;
}

View File

@ -54,8 +54,6 @@ async function loadAsset(
): Promise<AssetProxy> {
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));

View File

@ -169,7 +169,10 @@ export default class ProxyBackend implements BackendClass {
async getMediaFile(path: string): Promise<ImplementationMediaFile> {
const file = await this.request<MediaFile>({
action: 'getMediaFile',
params: { branch: this.branch, path },
params: {
branch: this.branch,
path,
},
});
return deserializeMediaFile(file);
}

View File

@ -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<CircularProgressProps> = ({ className, 'data-testid': dataTestId }) => {
const CircularProgress: FC<CircularProgressProps> = ({
className,
'data-testid': dataTestId,
size = 'medium',
}) => {
return (
<div role="status" className={className} data-testid={dataTestId}>
<svg
aria-hidden="true"
className="w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600"
className={classNames(
`
mr-2
text-gray-200
animate-spin
dark:text-gray-600
fill-blue-600
`,
size === 'medium' &&
`
w-8
h-8
`,
size === 'small' &&
`
w-5
h-5
`,
)}
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@ -13,7 +13,7 @@ const TableCell = ({ columns, children }: TableCellProps) => {
return (
<div className="relative overflow-x-auto shadow-md sm:rounded-lg border border-slate-200 dark:border-gray-700">
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-300 ">
<thead className="text-xs text-gray-700 bg-slate-50 dark:bg-slate-700 dark:text-gray-300">
<thead className="text-xs text-gray-700 bg-gray-100 dark:bg-slate-700 dark:text-gray-300">
<tr>
{columns.map((column, index) => (
<TableHeaderCell key={index}>{column}</TableHeaderCell>

View File

@ -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) => {

View File

@ -117,7 +117,7 @@ const EditorControlPane = ({
<EditorControl
key="entry-path"
field={pathField}
value={nestedFieldPath}
value={entry.meta?.path ?? nestedFieldPath}
fieldsErrors={fieldsErrors}
submitted={submitted}
locale={locale}

View File

@ -26,6 +26,7 @@ import type { FC, KeyboardEvent } from 'react';
interface MediaLibraryCardProps<T extends MediaField, EF extends BaseField = UnknownField> {
isSelected?: boolean;
displayURL: MediaLibraryDisplayURL;
path: string;
text: string;
draftText: string;
type?: string;
@ -44,6 +45,7 @@ interface MediaLibraryCardProps<T extends MediaField, EF extends BaseField = Unk
const MediaLibraryCard = <T extends MediaField, EF extends BaseField = UnknownField>({
isSelected = false,
displayURL,
path,
text,
draftText,
type,
@ -60,7 +62,7 @@ const MediaLibraryCard = <T extends MediaField, EF extends BaseField = UnknownFi
t,
}: TranslatedProps<MediaLibraryCardProps<T, EF>>) => {
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;

View File

@ -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}

View File

@ -476,9 +476,9 @@ export abstract class BackendClass {
abstract getMediaDisplayURL(displayURL: DisplayURL): Promise<string>;
abstract getMedia(
folder?: string,
mediaFolder?: string,
folderSupport?: boolean,
mediaPath?: string,
publicFolder?: string,
): Promise<ImplementationMediaFile[]>;
abstract getMediaFile(path: string): Promise<ImplementationMediaFile>;

View File

@ -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]);
}

View File

@ -232,7 +232,7 @@ function traverseFields<EF extends BaseField>(
export function selectMediaFolder<EF extends BaseField>(
config: Config<EF>,
collection: Collection<EF> | undefined | null,
entryMap: Entry | null | undefined,
entryMap: Entry | undefined | null,
field: MediaField | undefined,
currentFolder?: string,
) {
@ -257,9 +257,9 @@ export function selectMediaFolder<EF extends BaseField>(
export function selectMediaFilePublicPath<EF extends BaseField>(
config: Config<EF>,
collection: Collection<EF> | null,
collection: Collection<EF> | 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;
}

View File

@ -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) {

View File

@ -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] ?? [];
};

View File

@ -293,7 +293,7 @@ const ListControl: FC<WidgetControlProps<ValueOrNestedValue[], ListField>> = ({
[onChange, internalValue, keys],
);
const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n);
const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n, false);
if (valueType === null) {
return null;

View File

@ -165,7 +165,7 @@ const ListItem: FC<ListItemProps> = ({
}
}, [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) {

View File

@ -31,7 +31,7 @@ const ObjectControl: FC<WidgetControlProps<ObjectValue, ObjectField>> = ({
const fields = useMemo(() => field.fields, [field.fields]);
const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n);
const hasChildErrors = useHasChildErrors(path, fieldsErrors, i18n, false);
const renderedField = useMemo(() => {
return (

View File

@ -179,11 +179,15 @@ const RelationControl: FC<WidgetControlProps<string | string[], RelationField>>
);
const [options, setOptions] = useState<HitOption[]>([]);
const [entries, setEntries] = useState<Entry[]>([]);
const loading = useMemo(() => options.length === 0, [options.length]);
const [entries, setEntries] = useState<Entry[] | null>(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<WidgetControlProps<string | string[], RelationField>>
key="loading-indicator"
className="absolute inset-y-0 right-4 flex items-center pr-2"
data-testid="relation-loading-indicator"
size="small"
/>
) : null}
</>

View File

@ -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',

View File

@ -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