feat: ui overhaul (#676)
This commit is contained in:
committed by
GitHub
parent
5c86462859
commit
66b81e9228
@ -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: {} }),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
6
packages/core/src/reducers/selectors/auth.ts
Normal file
6
packages/core/src/reducers/selectors/auth.ts
Normal 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;
|
||||
};
|
@ -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);
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
9
packages/core/src/reducers/selectors/globalUI.ts
Normal file
9
packages/core/src/reducers/selectors/globalUI.ts
Normal 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;
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
Reference in New Issue
Block a user