Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Commit

Permalink
fix: reapply defaults on discard (#864)
Browse files Browse the repository at this point in the history
  • Loading branch information
KaneFreeman authored Sep 6, 2023
1 parent 5602812 commit 6bcf451
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 75 deletions.
76 changes: 5 additions & 71 deletions packages/core/src/actions/entries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import isEqual from 'lodash/isEqual';

import { currentBackend } from '../backend';
import {
ADD_DRAFT_ENTRY_MEDIA_FILE,
Expand Down Expand Up @@ -40,16 +38,11 @@ import {
SORT_ENTRIES_SUCCESS,
} from '../constants';
import ValidationErrorTypes from '../constants/validationErrorTypes';
import {
I18N_FIELD_DUPLICATE,
I18N_FIELD_TRANSLATE,
duplicateDefaultI18nFields,
hasI18n,
serializeI18n,
} from '../lib/i18n';
import { hasI18n, serializeI18n } from '../lib/i18n';
import { serializeValues } from '../lib/serializeEntryValues';
import { Cursor } from '../lib/util';
import { selectFields, updateFieldByKey } from '../lib/util/collection.util';
import { createEmptyDraftData, createEmptyDraftI18nData } from '../lib/util/entry.util';
import { selectCollectionEntriesCursor } from '../reducers/selectors/cursors';
import {
selectEntriesSortField,
Expand Down Expand Up @@ -77,7 +70,6 @@ import type {
FieldError,
I18nSettings,
ImplementationMediaFile,
ObjectValue,
SortDirection,
ValueOrNestedValue,
ViewFilter,
Expand Down Expand Up @@ -439,10 +431,10 @@ export function emptyDraftCreated(entry: Entry) {
/*
* Exported simple Action Creators
*/
export function createDraftFromEntry(entry: Entry) {
export function createDraftFromEntry(collection: Collection, entry: Entry) {
return {
type: DRAFT_CREATE_FROM_ENTRY,
payload: { entry },
payload: { collection, entry },
} as const;
}

Expand Down Expand Up @@ -625,7 +617,7 @@ export function loadEntry(collection: Collection, slug: string, silent = false)
await dispatch(loadMedia());
const loadedEntry = await tryLoadEntry(getState(), collection, slug);
dispatch(entryLoaded(collection, loadedEntry));
dispatch(createDraftFromEntry(loadedEntry));
dispatch(createDraftFromEntry(collection, loadedEntry));
} catch (error: unknown) {
console.error(error);
if (error instanceof Error) {
Expand Down Expand Up @@ -878,64 +870,6 @@ export function createEmptyDraft(collection: Collection, search: string) {
};
}

export function createEmptyDraftData(
fields: Field[],
skipField: (field: Field) => boolean = () => false,
) {
const ddd = fields.reduce((acc, item) => {
if (skipField(item)) {
return acc;
}

const subfields = 'fields' in item && item.fields;
const list = item.widget === 'list';
const name = item.name;
const defaultValue = (('default' in item ? item.default : null) ?? null) as EntryData;

function isEmptyDefaultValue(val: EntryData | EntryData[]) {
return [[{}], {}].some(e => isEqual(val, e));
}

if (subfields) {
if (list && Array.isArray(defaultValue)) {
acc[name] = defaultValue;
} else {
const asList = Array.isArray(subfields) ? subfields : [subfields];

const subDefaultValue = list
? [createEmptyDraftData(asList, skipField)]
: createEmptyDraftData(asList, skipField);

if (!isEmptyDefaultValue(subDefaultValue)) {
acc[name] = subDefaultValue;
}
}
return acc;
}

if (defaultValue !== null) {
acc[name] = defaultValue;
}

return acc;
}, {} as ObjectValue);

return ddd;
}

function createEmptyDraftI18nData(collection: Collection, dataFields: Field[]) {
if (!hasI18n(collection)) {
return {};
}

function skipField(field: Field) {
return field.i18n !== I18N_FIELD_DUPLICATE && field.i18n !== I18N_FIELD_TRANSLATE;
}

const i18nData = createEmptyDraftData(dataFields, skipField);
return duplicateDefaultI18nFields(collection, i18nData);
}

export function getMediaAssets({ entry }: { entry: Entry }) {
const filesArray = entry.mediaFiles;
const assets = filesArray
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/components/entry-editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
await dispatch(loadScroll());
}, [dispatch]);

const handleDiscardDraft = useCallback(() => {
setVersion(version => version + 1);
}, []);

if (entry && entry.error) {
return (
<div>
Expand Down Expand Up @@ -356,6 +360,7 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
toggleScroll={handleToggleScroll}
scrollSyncActive={scrollSyncActive}
loadScroll={handleLoadScroll}
onDiscardDraft={handleDiscardDraft}
submitted={submitted}
slug={slug}
t={t}
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/components/entry-editor/EditorInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ interface EditorInterfaceProps {
loadScroll: () => void;
submitted: boolean;
slug: string | undefined;
onDiscardDraft: () => void;
}

const EditorInterface = ({
Expand All @@ -97,6 +98,7 @@ const EditorInterface = ({
toggleScroll,
submitted,
slug,
onDiscardDraft,
}: TranslatedProps<EditorInterfaceProps>) => {
const config = useAppSelector(selectConfig);

Expand Down Expand Up @@ -413,6 +415,7 @@ const EditorInterface = ({
showMobilePreview={showMobilePreview}
onMobilePreviewToggle={toggleMobilePreview}
className="flex"
onDiscardDraft={onDiscardDraft}
/>
}
>
Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/components/entry-editor/EditorToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface EditorToolbarProps {
className?: string;
showMobilePreview: boolean;
onMobilePreviewToggle: () => void;
onDiscardDraft: () => void;
}

const EditorToolbar = ({
Expand All @@ -75,6 +76,7 @@ const EditorToolbar = ({
className,
showMobilePreview,
onMobilePreviewToggle,
onDiscardDraft,
}: TranslatedProps<EditorToolbarProps>) => {
const canCreate = useMemo(
() => ('folder' in collection && collection.create) ?? false,
Expand All @@ -100,10 +102,11 @@ const EditorToolbar = ({
color: 'warning',
})
) {
dispatch(deleteLocalBackup(collection, slug));
dispatch(loadEntry(collection, slug));
await dispatch(deleteLocalBackup(collection, slug));
await dispatch(loadEntry(collection, slug));
onDiscardDraft();
}
}, [collection, dispatch, slug]);
}, [collection, dispatch, onDiscardDraft, slug]);

const menuItems: JSX.Element[][] = useMemo(() => {
const items: JSX.Element[] = [];
Expand Down
75 changes: 75 additions & 0 deletions packages/core/src/lib/util/entry.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import isEqual from 'lodash/isEqual';

import { isNotNullish } from './null.util';
import {
I18N_FIELD_DUPLICATE,
I18N_FIELD_TRANSLATE,
duplicateDefaultI18nFields,
hasI18n,
} from '../i18n';

import type { Collection, EntryData, Field, ObjectValue } from '@staticcms/core/interface';

export function applyDefaultsToDraftData(
fields: Field[],
skipField: (field: Field) => boolean = () => false,
initialValue?: ObjectValue | null,
) {
const emptyDraftData = fields.reduce((acc, item) => {
const name = item.name;

if (skipField(item) || isNotNullish(acc[name])) {
return acc;
}

const subfields = 'fields' in item && item.fields;
const list = item.widget === 'list';
const defaultValue = (('default' in item ? item.default : null) ?? null) as EntryData;

function isEmptyDefaultValue(val: EntryData | EntryData[]) {
return [[{}], {}].some(e => isEqual(val, e));
}

if (subfields) {
if (list && Array.isArray(defaultValue)) {
acc[name] = defaultValue;
} else {
const asList = Array.isArray(subfields) ? subfields : [subfields];

const subDefaultValue = list
? [applyDefaultsToDraftData(asList, skipField)]
: applyDefaultsToDraftData(asList, skipField);

if (!isEmptyDefaultValue(subDefaultValue)) {
acc[name] = subDefaultValue;
}
}
return acc;
}

if (defaultValue !== null) {
acc[name] = defaultValue;
}

return acc;
}, (initialValue ?? {}) as ObjectValue);

return emptyDraftData;
}

export function createEmptyDraftData(fields: Field[], skipField?: (field: Field) => boolean) {
return applyDefaultsToDraftData(fields, skipField);
}

export function createEmptyDraftI18nData(collection: Collection, dataFields: Field[]) {
if (!hasI18n(collection)) {
return {};
}

function skipField(field: Field) {
return field.i18n !== I18N_FIELD_DUPLICATE && field.i18n !== I18N_FIELD_TRANSLATE;
}

const i18nData = createEmptyDraftData(dataFields, skipField);
return duplicateDefaultI18nFields(collection, i18nData);
}
12 changes: 11 additions & 1 deletion packages/core/src/reducers/entryDraft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
REMOVE_DRAFT_ENTRY_MEDIA_FILE,
} from '../constants';
import { duplicateI18nFields, getDataPath } from '../lib/i18n';
import { fileForEntry } from '../lib/util/collection.util';
import { applyDefaultsToDraftData } from '../lib/util/entry.util';
import { set } from '../lib/util/object.util';

import type { EntriesAction } from '../actions/entries';
Expand Down Expand Up @@ -56,10 +58,18 @@ function entryDraftReducer(
newRecord: false,
};

const collection = action.payload.collection;

const file = fileForEntry(collection, entry.slug);
const fields = file ? file.fields : 'fields' in collection ? collection.fields : [];

// Existing Entry
return {
...newState,
entry,
entry: {
...entry,
data: applyDefaultsToDraftData(fields, undefined, entry.data),
},
original: entry,
fieldsErrors: {},
hasChanged: false,
Expand Down

0 comments on commit 6bcf451

Please sign in to comment.