317 lines
6.7 KiB
TypeScript
317 lines
6.7 KiB
TypeScript
import isEqual from 'lodash/isEqual';
|
|
import { v4 as uuid } from 'uuid';
|
|
|
|
import {
|
|
ADD_DRAFT_ENTRY_MEDIA_FILE,
|
|
DRAFT_CHANGE_FIELD,
|
|
DRAFT_CREATE_DUPLICATE_FROM_ENTRY,
|
|
DRAFT_CREATE_EMPTY,
|
|
DRAFT_CREATE_FROM_ENTRY,
|
|
DRAFT_CREATE_FROM_LOCAL_BACKUP,
|
|
DRAFT_DISCARD,
|
|
DRAFT_LOCAL_BACKUP_DELETE,
|
|
DRAFT_LOCAL_BACKUP_RETRIEVED,
|
|
DRAFT_VALIDATION_ERRORS,
|
|
ENTRY_DELETE_SUCCESS,
|
|
ENTRY_PERSIST_FAILURE,
|
|
ENTRY_PERSIST_REQUEST,
|
|
ENTRY_PERSIST_SUCCESS,
|
|
REMOVE_DRAFT_ENTRY_MEDIA_FILE,
|
|
} from '../constants';
|
|
import { duplicateI18nFields, getDataPath } from '../lib/i18n';
|
|
import { set } from '../lib/util/object.util';
|
|
|
|
import type { EntriesAction } from '../actions/entries';
|
|
import type { Entry, FieldsErrors } from '../interface';
|
|
|
|
export interface EntryDraftState {
|
|
original?: Entry;
|
|
entry?: Entry;
|
|
fieldsErrors: FieldsErrors;
|
|
hasChanged: boolean;
|
|
key: string;
|
|
localBackup?: {
|
|
entry: Entry;
|
|
};
|
|
}
|
|
|
|
const initialState: EntryDraftState = {
|
|
fieldsErrors: {},
|
|
hasChanged: false,
|
|
key: '',
|
|
};
|
|
|
|
function entryDraftReducer(
|
|
state: EntryDraftState = initialState,
|
|
action: EntriesAction,
|
|
): EntryDraftState {
|
|
switch (action.type) {
|
|
case DRAFT_CREATE_FROM_ENTRY: {
|
|
const newState = { ...state };
|
|
|
|
const entry: Entry = {
|
|
...action.payload.entry,
|
|
newRecord: false,
|
|
};
|
|
|
|
// Existing Entry
|
|
return {
|
|
...newState,
|
|
entry,
|
|
original: entry,
|
|
fieldsErrors: {},
|
|
hasChanged: false,
|
|
key: uuid(),
|
|
};
|
|
}
|
|
case DRAFT_CREATE_EMPTY: {
|
|
const newState = { ...state };
|
|
delete newState.localBackup;
|
|
|
|
const entry: Entry = {
|
|
...action.payload,
|
|
newRecord: true,
|
|
};
|
|
|
|
// New Entry
|
|
return {
|
|
...newState,
|
|
entry,
|
|
original: entry,
|
|
fieldsErrors: {},
|
|
hasChanged: false,
|
|
key: uuid(),
|
|
};
|
|
}
|
|
case DRAFT_CREATE_FROM_LOCAL_BACKUP: {
|
|
const backupDraftEntry = state.localBackup;
|
|
if (!backupDraftEntry) {
|
|
return state;
|
|
}
|
|
|
|
const backupEntry = backupDraftEntry?.['entry'];
|
|
|
|
const newState = { ...state };
|
|
delete newState.localBackup;
|
|
|
|
const entry: Entry = {
|
|
...backupEntry,
|
|
newRecord: !backupEntry?.path,
|
|
};
|
|
|
|
// Local Backup
|
|
return {
|
|
...state,
|
|
entry,
|
|
original: entry,
|
|
fieldsErrors: {},
|
|
hasChanged: true,
|
|
key: uuid(),
|
|
};
|
|
}
|
|
|
|
case DRAFT_CREATE_DUPLICATE_FROM_ENTRY: {
|
|
const newState = { ...state };
|
|
delete newState.localBackup;
|
|
|
|
const entry: Entry = {
|
|
...action.payload,
|
|
newRecord: true,
|
|
};
|
|
|
|
// Duplicate Entry
|
|
return {
|
|
...newState,
|
|
entry,
|
|
original: entry,
|
|
fieldsErrors: {},
|
|
hasChanged: true,
|
|
};
|
|
}
|
|
case DRAFT_DISCARD:
|
|
return initialState;
|
|
case DRAFT_LOCAL_BACKUP_RETRIEVED: {
|
|
const { entry } = action.payload;
|
|
const newState = {
|
|
entry,
|
|
};
|
|
return {
|
|
...state,
|
|
localBackup: newState,
|
|
};
|
|
}
|
|
|
|
case DRAFT_LOCAL_BACKUP_DELETE: {
|
|
const newState = { ...state };
|
|
delete newState.localBackup;
|
|
return newState;
|
|
}
|
|
|
|
case DRAFT_CHANGE_FIELD: {
|
|
let newState = { ...state };
|
|
if (!newState.entry) {
|
|
return state;
|
|
}
|
|
|
|
const { path, field, value, i18n, isMeta } = action.payload;
|
|
const dataPath = isMeta
|
|
? ['meta']
|
|
: (i18n && getDataPath(i18n.currentLocale, i18n.defaultLocale)) || ['data'];
|
|
|
|
newState = {
|
|
...newState,
|
|
entry: set(newState.entry, `${dataPath.join('.')}.${path}`, value),
|
|
};
|
|
|
|
if (i18n) {
|
|
newState = duplicateI18nFields(newState, field, i18n.locales, i18n.defaultLocale, path);
|
|
}
|
|
|
|
let hasChanged =
|
|
!isEqual(newState.entry?.meta, newState.original?.meta) ||
|
|
!isEqual(newState.entry?.data, newState.original?.data);
|
|
|
|
const i18nData = newState.entry?.i18n ?? {};
|
|
for (const locale in i18nData) {
|
|
hasChanged =
|
|
hasChanged ||
|
|
!isEqual(newState.entry?.i18n?.[locale]?.data, newState.original?.i18n?.[locale]?.data);
|
|
}
|
|
|
|
return {
|
|
...newState,
|
|
hasChanged: !newState.original || hasChanged,
|
|
};
|
|
}
|
|
|
|
case DRAFT_VALIDATION_ERRORS: {
|
|
const { path, errors, i18n } = action.payload;
|
|
const fieldsErrors = { ...state.fieldsErrors };
|
|
|
|
const dataPath = (i18n && getDataPath(i18n.currentLocale, i18n.defaultLocale)) || ['data'];
|
|
const fullPath = `${dataPath.join('.')}.${path}`;
|
|
|
|
if (errors.length === 0) {
|
|
delete fieldsErrors[fullPath];
|
|
} else {
|
|
fieldsErrors[fullPath] = action.payload.errors;
|
|
}
|
|
return {
|
|
...state,
|
|
fieldsErrors,
|
|
};
|
|
}
|
|
|
|
case ENTRY_PERSIST_REQUEST: {
|
|
if (!state.entry) {
|
|
return state;
|
|
}
|
|
|
|
return {
|
|
...state,
|
|
entry: {
|
|
...state.entry,
|
|
isPersisting: true,
|
|
},
|
|
};
|
|
}
|
|
|
|
case ENTRY_PERSIST_FAILURE: {
|
|
if (!state.entry) {
|
|
return state;
|
|
}
|
|
|
|
return {
|
|
...state,
|
|
entry: {
|
|
...state.entry,
|
|
isPersisting: false,
|
|
},
|
|
};
|
|
}
|
|
|
|
case ENTRY_PERSIST_SUCCESS: {
|
|
if (!state.entry) {
|
|
return state;
|
|
}
|
|
|
|
const newState = { ...state };
|
|
delete newState.localBackup;
|
|
|
|
const entry: Entry = {
|
|
...state.entry,
|
|
slug: action.payload.slug,
|
|
isPersisting: false,
|
|
};
|
|
|
|
return {
|
|
...newState,
|
|
hasChanged: false,
|
|
entry,
|
|
original: entry,
|
|
};
|
|
}
|
|
|
|
case ENTRY_DELETE_SUCCESS: {
|
|
if (!state.entry) {
|
|
return state;
|
|
}
|
|
|
|
const newState = { ...state };
|
|
delete newState.localBackup;
|
|
|
|
const entry: Entry = {
|
|
...state.entry,
|
|
isPersisting: false,
|
|
};
|
|
|
|
return {
|
|
...newState,
|
|
hasChanged: false,
|
|
entry,
|
|
original: entry,
|
|
};
|
|
}
|
|
|
|
case ADD_DRAFT_ENTRY_MEDIA_FILE: {
|
|
if (!state.entry) {
|
|
return state;
|
|
}
|
|
|
|
const mediaFiles = state.entry.mediaFiles.filter(file => file.id !== action.payload.id);
|
|
mediaFiles.unshift(action.payload);
|
|
|
|
return {
|
|
...state,
|
|
hasChanged: true,
|
|
entry: {
|
|
...state.entry,
|
|
mediaFiles,
|
|
},
|
|
};
|
|
}
|
|
|
|
case REMOVE_DRAFT_ENTRY_MEDIA_FILE: {
|
|
if (!state.entry) {
|
|
return state;
|
|
}
|
|
|
|
const mediaFiles = state.entry.mediaFiles.filter(file => file.id !== action.payload.id);
|
|
|
|
return {
|
|
...state,
|
|
hasChanged: true,
|
|
entry: {
|
|
...state.entry,
|
|
mediaFiles,
|
|
},
|
|
};
|
|
}
|
|
|
|
default:
|
|
return state;
|
|
}
|
|
}
|
|
|
|
export default entryDraftReducer;
|