feat: ui overhaul (#676)

This commit is contained in:
Daniel Lautzenheiser
2023-03-30 13:29:09 -04:00
committed by GitHub
parent 5c86462859
commit 66b81e9228
385 changed files with 20607 additions and 16493 deletions

View File

@ -1,6 +1,6 @@
import { DRAFT_CHANGE_FIELD, DRAFT_CREATE_EMPTY } from '@staticcms/core/constants';
import mockEntry from '@staticcms/core/lib/test-utils/mock-data/MockEntry';
import entryDraftReducer from '../entryDraft';
import { createMockEntry } from '@staticcms/test/data/entry.mock';
import type { I18nSettings, StringOrTextField } from '@staticcms/core/interface';
import type { EntryDraftState } from '../entryDraft';
@ -13,7 +13,7 @@ describe('entryDraft', () => {
beforeEach(() => {
startState = entryDraftReducer(undefined, {
type: DRAFT_CREATE_EMPTY,
payload: mockEntry,
payload: createMockEntry({ data: {} }),
});
});

View File

@ -21,12 +21,12 @@ import {
SORT_ENTRIES_REQUEST,
SORT_ENTRIES_SUCCESS,
} from '../constants';
import { VIEW_STYLE_LIST } from '../constants/collectionViews';
import { VIEW_STYLE_LIST } from '../constants/views';
import { set } from '../lib/util/object.util';
import type { EntriesAction } from '../actions/entries';
import type { SearchAction } from '../actions/search';
import type { CollectionViewStyle } from '../constants/collectionViews';
import type { ViewStyle } from '../constants/views';
import type {
Entities,
Entry,
@ -91,7 +91,7 @@ function persistSort(sort: Sort | undefined) {
}
const loadViewStyle = once(() => {
const viewStyle = localStorage.getItem(viewStyleKey) as CollectionViewStyle;
const viewStyle = localStorage.getItem(viewStyleKey) as ViewStyle;
if (viewStyle) {
return viewStyle;
}
@ -118,7 +118,7 @@ export type EntriesState = {
sort: Sort;
filter?: Filter;
group?: Group;
viewStyle: CollectionViewStyle;
viewStyle: ViewStyle;
};
function entries(

View File

@ -1,29 +1,43 @@
import type { AnyAction } from 'redux';
import { THEME_CHANGE } from '../constants';
import type { GlobalUIAction } from '../actions/globalUI';
export type GlobalUIState = {
isFetching: boolean;
theme: 'dark' | 'light';
};
const defaultState: GlobalUIState = {
isFetching: false,
theme: 'light',
};
/**
* Reducer for some global UI state that we want to share between components
*/
const globalUI = (state: GlobalUIState = defaultState, action: AnyAction): GlobalUIState => {
const globalUI = (state: GlobalUIState = defaultState, action: GlobalUIAction): GlobalUIState => {
// Generic, global loading indicator
if (action.type.includes('REQUEST')) {
return {
...state,
isFetching: true,
};
} else if (action.type.includes('SUCCESS') || action.type.includes('FAILURE')) {
return {
...state,
isFetching: false,
};
}
return state;
switch (action.type) {
case THEME_CHANGE:
return {
...state,
theme: action.payload,
};
default:
return state;
}
};
export default globalUI;

View File

@ -21,18 +21,20 @@ import {
} from '../constants';
import type { MediaLibraryAction } from '../actions/mediaLibrary';
import type { Collection, Field, MediaFile, MediaLibraryInstance } from '../interface';
export interface MediaLibraryDisplayURL {
url?: string;
isFetching: boolean;
err?: unknown;
}
import type {
Collection,
Field,
MediaFile,
MediaLibrarInsertOptions,
MediaLibraryDisplayURL,
MediaLibraryInstance,
MediaPath,
} from '../interface';
export type MediaLibraryState = {
isVisible: boolean;
showMediaButton: boolean;
controlMedia: Record<string, string | string[]>;
controlMedia: Record<string, MediaPath>;
displayURLs: Record<string, MediaLibraryDisplayURL>;
externalLibrary?: MediaLibraryInstance;
controlID?: string;
@ -42,8 +44,8 @@ export type MediaLibraryState = {
collection?: Collection;
field?: Field;
value?: string | string[];
alt?: string;
replaceIndex?: number;
canInsert?: boolean;
isLoading?: boolean;
dynamicSearch?: boolean;
dynamicSearchActive?: boolean;
@ -53,6 +55,7 @@ export type MediaLibraryState = {
isDeleting?: boolean;
hasNextPage?: boolean;
isPaginating?: boolean;
insertOptions?: MediaLibrarInsertOptions;
};
const defaultState: MediaLibraryState = {
@ -76,8 +79,17 @@ function mediaLibrary(
};
case MEDIA_LIBRARY_OPEN: {
const { controlID, forImage, config, collection, field, value, replaceIndex } =
action.payload;
const {
controlID,
forImage,
config,
collection,
field,
value,
alt,
replaceIndex,
insertOptions,
} = action.payload;
const libConfig = config || {};
return {
@ -85,12 +97,13 @@ function mediaLibrary(
isVisible: true,
forImage: Boolean(forImage),
controlID,
canInsert: !!controlID,
config: libConfig,
collection,
field,
value,
alt,
replaceIndex,
insertOptions,
};
}
@ -98,10 +111,12 @@ function mediaLibrary(
return {
...state,
isVisible: false,
insertOptions: undefined,
alt: undefined,
};
case MEDIA_INSERT: {
const { mediaPath } = action.payload;
const { mediaPath, alt } = action.payload;
const controlID = state.controlID;
if (!controlID) {
return state;
@ -114,7 +129,10 @@ function mediaLibrary(
...state,
controlMedia: {
...state.controlMedia,
[controlID]: mediaPath,
[controlID]: {
path: mediaPath,
alt,
},
},
};
}
@ -132,7 +150,9 @@ function mediaLibrary(
...state,
controlMedia: {
...state.controlMedia,
[controlID]: valueArray,
[controlID]: {
path: valueArray,
},
},
};
}
@ -140,12 +160,12 @@ function mediaLibrary(
case MEDIA_REMOVE_INSERTED: {
const controlID = action.payload.controlID;
const newControlMedia = { ...state.controlMedia };
delete newControlMedia[controlID];
return {
...state,
controlMedia: {
...state.controlMedia,
[controlID]: '',
},
controlMedia: newControlMedia,
};
}

View File

@ -0,0 +1,6 @@
/* eslint-disable import/prefer-default-export */
import type { RootState } from '@staticcms/core/store';
export const selectUser = (state: RootState) => {
return state.auth.user;
};

View File

@ -1,6 +1,10 @@
/* eslint-disable import/prefer-default-export */
import type { RootState } from '@staticcms/core/store';
export const selectCollection = (collectionName: string) => (state: RootState) => {
export const selectCollections = (state: RootState) => {
return state.collections;
};
export const selectCollection = (collectionName: string | undefined) => (state: RootState) => {
return Object.values(state.collections).find(collection => collection.name === collectionName);
};

View File

@ -10,3 +10,7 @@ export function selectLocale(config?: Config) {
export function selectConfig(state: RootState) {
return state.config.config;
}
export function selectIsSearchEnabled(state: RootState) {
return state.config.config?.search !== false;
}

View File

@ -2,7 +2,7 @@ import get from 'lodash/get';
import { SORT_DIRECTION_NONE } from '@staticcms/core/constants';
import type { CollectionViewStyle } from '@staticcms/core/constants/collectionViews';
import type { ViewStyle } from '@staticcms/core/constants/views';
import type { Entry, Group, GroupMap, Sort } from '@staticcms/core/interface';
import type { RootState } from '@staticcms/core/store';
@ -42,7 +42,7 @@ export const selectEntriesSortField = (collectionName: string) => (entries: Root
return Object.values(sort ?? {}).find(v => v?.direction !== SORT_DIRECTION_NONE);
};
export function selectViewStyle(entries: RootState): CollectionViewStyle {
export function selectViewStyle(entries: RootState): ViewStyle {
return entries.entries.viewStyle;
}

View File

@ -17,3 +17,7 @@ export const selectFieldErrors =
export function selectEditingDraft(state: RootState) {
return state.entryDraft.entry;
}
export function selectDraftMediaFiles(state: RootState) {
return state.entryDraft.entry?.mediaFiles;
}

View File

@ -0,0 +1,9 @@
import type { RootState } from '@staticcms/core/store';
export function selectIsFetching(state: RootState) {
return state.globalUI.isFetching;
}
export function selectTheme(state: RootState) {
return state.globalUI.theme;
}

View File

@ -4,36 +4,40 @@ import { dirname } from 'path';
import { selectMediaFolder } from '@staticcms/core/lib/util/media.util';
import { selectEditingDraft } from './entryDraft';
import type { DisplayURLState, Field, MediaFile } from '@staticcms/core/interface';
import type { DisplayURLState, MediaField, MediaFile } from '@staticcms/core/interface';
import type { RootState } from '@staticcms/core/store';
export function selectMediaFiles(state: RootState, field?: Field): MediaFile[] {
const { mediaLibrary, entryDraft } = state;
const editingDraft = selectEditingDraft(state);
export const selectMediaFiles =
(field?: MediaField) =>
(state: RootState): MediaFile[] => {
const { mediaLibrary, entryDraft } = state;
const editingDraft = selectEditingDraft(state);
let files: MediaFile[] = [];
if (editingDraft) {
const entryFiles = entryDraft?.entry?.mediaFiles ?? [];
const entry = entryDraft['entry'];
const collection = entry?.collection ? state.collections[entry.collection] : null;
if (state.config.config) {
const mediaFolder = selectMediaFolder(state.config.config, collection, entry, field);
files = entryFiles
.filter(f => dirname(f.path) === mediaFolder)
.map(file => ({ key: file.id, ...file }));
let files: MediaFile[] = [];
if (editingDraft) {
const entryFiles = entryDraft?.entry?.mediaFiles ?? [];
const entry = entryDraft['entry'];
const collection = entry?.collection ? state.collections[entry.collection] : null;
if (state.config.config) {
const mediaFolder = selectMediaFolder(state.config.config, collection, entry, field);
files = entryFiles
.filter(f => dirname(f.path) === mediaFolder)
.map(file => ({ key: file.id, ...file }));
}
} else {
files = mediaLibrary.files || [];
}
} else {
files = mediaLibrary.files || [];
}
return files;
}
return files;
};
export function selectMediaFileByPath(state: RootState, path: string) {
const files = selectMediaFiles(state);
const file = files.find(file => file.path === path);
return file;
}
export const selectMediaLibraryState = (state: RootState) => {
return state.mediaLibrary;
};
export const selectMediaLibraryFiles = (state: RootState) => {
return state.mediaLibrary.files;
};
export function selectMediaDisplayURL(state: RootState, id: string) {
return (get(state.mediaLibrary, ['displayURLs', id]) ?? {}) as DisplayURLState;
@ -42,3 +46,15 @@ export function selectMediaDisplayURL(state: RootState, id: string) {
export const selectMediaPath = (controlID: string) => (state: RootState) => {
return state.mediaLibrary.controlMedia[controlID];
};
export const selectPersisting = (state: RootState) => {
return state.mediaLibrary.isPersisting;
};
export const selectDeleting = (state: RootState) => {
return state.mediaLibrary.isDeleting;
};
export const selectVisible = (state: RootState) => {
return state.mediaLibrary.isVisible;
};