mirror of
https://github.com/strapi/strapi.git
synced 2025-11-16 10:07:55 +00:00
Merge pull request #16387 from strapi/chore/cm-field-sizes
Refactor content manager field sizes management
This commit is contained in:
commit
ecf439ab56
@ -1,12 +1,24 @@
|
|||||||
import { GET_DATA, RESET_PROPS, SET_CONTENT_TYPE_LINKS } from './constants';
|
import { GET_INIT_DATA, RESET_INIT_DATA, SET_INIT_DATA } from './constants';
|
||||||
|
|
||||||
export const getData = () => ({
|
export const getInitData = () => ({
|
||||||
type: GET_DATA,
|
type: GET_INIT_DATA,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const resetProps = () => ({ type: RESET_PROPS });
|
export const resetInitData = () => ({ type: RESET_INIT_DATA });
|
||||||
|
|
||||||
export const setContentTypeLinks = (authorizedCtLinks, authorizedStLinks, models, components) => ({
|
export const setInitData = ({
|
||||||
type: SET_CONTENT_TYPE_LINKS,
|
authorizedCollectionTypeLinks,
|
||||||
data: { authorizedCtLinks, authorizedStLinks, components, contentTypeSchemas: models },
|
authorizedSingleTypeLinks,
|
||||||
|
contentTypeSchemas,
|
||||||
|
components,
|
||||||
|
fieldSizes,
|
||||||
|
}) => ({
|
||||||
|
type: SET_INIT_DATA,
|
||||||
|
data: {
|
||||||
|
authorizedCollectionTypeLinks,
|
||||||
|
authorizedSingleTypeLinks,
|
||||||
|
components,
|
||||||
|
contentTypeSchemas,
|
||||||
|
fieldSizes,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
export const GET_DATA = 'ContentManager/App/GET_DATA';
|
export const GET_INIT_DATA = 'ContentManager/App/GET_INIT_DATA';
|
||||||
export const RESET_PROPS = 'ContentManager/App/RESET_PROPS';
|
export const RESET_INIT_DATA = 'ContentManager/App/RESET_INIT_DATA';
|
||||||
export const SET_CONTENT_TYPE_LINKS = 'ContentManager/App/SET_CONTENT_TYPE_LINKS';
|
export const SET_INIT_DATA = 'ContentManager/App/SET_INIT_DATA';
|
||||||
|
|||||||
@ -20,13 +20,14 @@ import NoContentType from '../NoContentType';
|
|||||||
import NoPermissions from '../NoPermissions';
|
import NoPermissions from '../NoPermissions';
|
||||||
import SingleTypeRecursivePath from '../SingleTypeRecursivePath';
|
import SingleTypeRecursivePath from '../SingleTypeRecursivePath';
|
||||||
import LeftMenu from './LeftMenu';
|
import LeftMenu from './LeftMenu';
|
||||||
import useModels from './useModels';
|
import useContentManagerInitData from './useContentManagerInitData';
|
||||||
|
|
||||||
const cmPermissions = permissions.contentManager;
|
const cmPermissions = permissions.contentManager;
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const contentTypeMatch = useRouteMatch(`/content-manager/:kind/:uid`);
|
const contentTypeMatch = useRouteMatch(`/content-manager/:kind/:uid`);
|
||||||
const { status, collectionTypeLinks, singleTypeLinks, models, refetchData } = useModels();
|
const { status, collectionTypeLinks, singleTypeLinks, models, refetchData } =
|
||||||
|
useContentManagerInitData();
|
||||||
const authorisedModels = sortBy([...collectionTypeLinks, ...singleTypeLinks], (model) =>
|
const authorisedModels = sortBy([...collectionTypeLinks, ...singleTypeLinks], (model) =>
|
||||||
model.title.toLowerCase()
|
model.title.toLowerCase()
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
/* eslint-disable consistent-return */
|
/* eslint-disable consistent-return */
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
import { GET_DATA, RESET_PROPS, SET_CONTENT_TYPE_LINKS } from './constants';
|
import { GET_INIT_DATA, RESET_INIT_DATA, SET_INIT_DATA } from './constants';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
components: [],
|
components: [],
|
||||||
@ -20,22 +20,23 @@ const initialState = {
|
|||||||
const mainReducer = (state = initialState, action) =>
|
const mainReducer = (state = initialState, action) =>
|
||||||
produce(state, (draftState) => {
|
produce(state, (draftState) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case GET_DATA: {
|
case GET_INIT_DATA: {
|
||||||
draftState.status = 'loading';
|
draftState.status = 'loading';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RESET_PROPS: {
|
case RESET_INIT_DATA: {
|
||||||
return initialState;
|
return initialState;
|
||||||
}
|
}
|
||||||
case SET_CONTENT_TYPE_LINKS: {
|
case SET_INIT_DATA: {
|
||||||
draftState.collectionTypeLinks = action.data.authorizedCtLinks.filter(
|
draftState.collectionTypeLinks = action.data.authorizedCollectionTypeLinks.filter(
|
||||||
({ isDisplayed }) => isDisplayed
|
({ isDisplayed }) => isDisplayed
|
||||||
);
|
);
|
||||||
draftState.singleTypeLinks = action.data.authorizedStLinks.filter(
|
draftState.singleTypeLinks = action.data.authorizedSingleTypeLinks.filter(
|
||||||
({ isDisplayed }) => isDisplayed
|
({ isDisplayed }) => isDisplayed
|
||||||
);
|
);
|
||||||
draftState.components = action.data.components;
|
draftState.components = action.data.components;
|
||||||
draftState.models = action.data.contentTypeSchemas;
|
draftState.models = action.data.contentTypeSchemas;
|
||||||
|
draftState.fieldSizes = action.data.fieldSizes;
|
||||||
draftState.status = 'resolved';
|
draftState.status = 'resolved';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,10 +23,13 @@ const makeSelectModelAndComponentSchemas = () =>
|
|||||||
schemas: [...components, ...models],
|
schemas: [...components, ...models],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const selectFieldSizes = createSelector(selectAppDomain(), (state) => state.fieldSizes);
|
||||||
|
|
||||||
export default makeSelectApp;
|
export default makeSelectApp;
|
||||||
export {
|
export {
|
||||||
makeSelectModelAndComponentSchemas,
|
makeSelectModelAndComponentSchemas,
|
||||||
makeSelectModelLinks,
|
makeSelectModelLinks,
|
||||||
makeSelectModels,
|
makeSelectModels,
|
||||||
|
selectFieldSizes,
|
||||||
selectAppDomain,
|
selectAppDomain,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,27 +1,32 @@
|
|||||||
import { setContentTypeLinks } from '../actions';
|
import { setInitData } from '../actions';
|
||||||
|
|
||||||
describe('Content Manager | App | actions', () => {
|
describe('Content Manager | App | actions', () => {
|
||||||
it('should format the setContentTypeLinks action', () => {
|
it('should format the setInitData action', () => {
|
||||||
const authorizedCtLinks = [{ title: 'addresses', uid: 'address' }];
|
const authorizedCollectionTypeLinks = [{ title: 'addresses', uid: 'address' }];
|
||||||
const authorizedStLinks = [{ title: 'Home page', uid: 'homepage' }];
|
const authorizedSingleTypeLinks = [{ title: 'Home page', uid: 'homepage' }];
|
||||||
const models = [
|
const contentTypeSchemas = [
|
||||||
{ kind: 'singleType', uid: 'homepage' },
|
{ kind: 'singleType', uid: 'homepage' },
|
||||||
{ kind: 'collectionType', uid: 'address' },
|
{ kind: 'collectionType', uid: 'address' },
|
||||||
];
|
];
|
||||||
const components = [];
|
const components = [];
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
type: 'ContentManager/App/SET_CONTENT_TYPE_LINKS',
|
type: 'ContentManager/App/SET_INIT_DATA',
|
||||||
data: {
|
data: {
|
||||||
authorizedCtLinks,
|
authorizedCollectionTypeLinks,
|
||||||
authorizedStLinks,
|
authorizedSingleTypeLinks,
|
||||||
contentTypeSchemas: models,
|
contentTypeSchemas,
|
||||||
components,
|
components,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(setContentTypeLinks(authorizedCtLinks, authorizedStLinks, models, components)).toEqual(
|
expect(
|
||||||
expected
|
setInitData({
|
||||||
);
|
authorizedCollectionTypeLinks,
|
||||||
|
authorizedSingleTypeLinks,
|
||||||
|
contentTypeSchemas,
|
||||||
|
components,
|
||||||
|
})
|
||||||
|
).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -13,9 +13,9 @@ import Theme from '../../../../components/Theme';
|
|||||||
import ThemeToggleProvider from '../../../../components/ThemeToggleProvider';
|
import ThemeToggleProvider from '../../../../components/ThemeToggleProvider';
|
||||||
import { App as ContentManagerApp } from '..';
|
import { App as ContentManagerApp } from '..';
|
||||||
import cmReducers from '../../../../reducers';
|
import cmReducers from '../../../../reducers';
|
||||||
import useModels from '../useModels';
|
import useContentManagerInitData from '../useContentManagerInitData';
|
||||||
|
|
||||||
jest.mock('../useModels', () =>
|
jest.mock('../useContentManagerInitData', () =>
|
||||||
jest.fn(() => {
|
jest.fn(() => {
|
||||||
return {};
|
return {};
|
||||||
})
|
})
|
||||||
@ -88,7 +88,7 @@ describe('Content manager | App | main', () => {
|
|||||||
components: [],
|
components: [],
|
||||||
status: 'resolved',
|
status: 'resolved',
|
||||||
};
|
};
|
||||||
useModels.mockImplementation(() => contentManagerState);
|
useContentManagerInitData.mockImplementation(() => contentManagerState);
|
||||||
const rootReducer = combineReducers(cmReducers);
|
const rootReducer = combineReducers(cmReducers);
|
||||||
const store = createStore(rootReducer, { 'content-manager_app': contentManagerState });
|
const store = createStore(rootReducer, { 'content-manager_app': contentManagerState });
|
||||||
const history = createMemoryHistory();
|
const history = createMemoryHistory();
|
||||||
@ -815,7 +815,7 @@ describe('Content manager | App | main', () => {
|
|||||||
components: [],
|
components: [],
|
||||||
status: 'resolved',
|
status: 'resolved',
|
||||||
};
|
};
|
||||||
useModels.mockImplementation(() => contentManagerState);
|
useContentManagerInitData.mockImplementation(() => contentManagerState);
|
||||||
const rootReducer = combineReducers(cmReducers);
|
const rootReducer = combineReducers(cmReducers);
|
||||||
const store = createStore(rootReducer, { 'content-manager_app': contentManagerState });
|
const store = createStore(rootReducer, { 'content-manager_app': contentManagerState });
|
||||||
const history = createMemoryHistory();
|
const history = createMemoryHistory();
|
||||||
@ -857,8 +857,8 @@ describe('Content manager | App | main', () => {
|
|||||||
components: [],
|
components: [],
|
||||||
status: 'resolved',
|
status: 'resolved',
|
||||||
};
|
};
|
||||||
useModels.mockImplementation(() => contentManagerState);
|
useContentManagerInitData.mockImplementation(() => contentManagerState);
|
||||||
jest.mock('../useModels', () =>
|
jest.mock('../useContentManagerInitData', () =>
|
||||||
jest.fn(() => {
|
jest.fn(() => {
|
||||||
return contentManagerState;
|
return contentManagerState;
|
||||||
})
|
})
|
||||||
@ -902,8 +902,8 @@ describe('Content manager | App | main', () => {
|
|||||||
components: [],
|
components: [],
|
||||||
status: 'resolved',
|
status: 'resolved',
|
||||||
};
|
};
|
||||||
useModels.mockImplementation(() => contentManagerState);
|
useContentManagerInitData.mockImplementation(() => contentManagerState);
|
||||||
jest.mock('../useModels', () =>
|
jest.mock('../useContentManagerInitData', () =>
|
||||||
jest.fn(() => {
|
jest.fn(() => {
|
||||||
return contentManagerState;
|
return contentManagerState;
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
import { getData, setContentTypeLinks, resetProps } from '../actions';
|
import { getInitData, setInitData, resetInitData } from '../actions';
|
||||||
import mainReducer from '../reducer';
|
import mainReducer from '../reducer';
|
||||||
|
|
||||||
describe('Content Manager | App | reducer', () => {
|
describe('Content Manager | App | reducer', () => {
|
||||||
@ -12,6 +12,7 @@ describe('Content Manager | App | reducer', () => {
|
|||||||
models: [],
|
models: [],
|
||||||
collectionTypeLinks: [],
|
collectionTypeLinks: [],
|
||||||
singleTypeLinks: [],
|
singleTypeLinks: [],
|
||||||
|
fieldSizes: {},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -19,18 +20,18 @@ describe('Content Manager | App | reducer', () => {
|
|||||||
expect(mainReducer(state, {})).toEqual(state);
|
expect(mainReducer(state, {})).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the getData action correctly', () => {
|
it('should handle the getInitData action correctly', () => {
|
||||||
state.status = 'resolved';
|
state.status = 'resolved';
|
||||||
|
|
||||||
const expected = produce(state, (draft) => {
|
const expected = produce(state, (draft) => {
|
||||||
draft.status = 'loading';
|
draft.status = 'loading';
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mainReducer(state, getData())).toEqual(expected);
|
expect(mainReducer(state, getInitData())).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the getData action correctly', () => {
|
it('should handle the setInitData action correctly', () => {
|
||||||
const collectionTypeLinks = [
|
const authorizedCollectionTypeLinks = [
|
||||||
{
|
{
|
||||||
name: 'authorizedCt',
|
name: 'authorizedCt',
|
||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
@ -40,7 +41,7 @@ describe('Content Manager | App | reducer', () => {
|
|||||||
isDisplayed: false,
|
isDisplayed: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const singleTypeLinks = [
|
const authorizedSingleTypeLinks = [
|
||||||
{
|
{
|
||||||
name: 'authorizedSt',
|
name: 'authorizedSt',
|
||||||
isDisplayed: false,
|
isDisplayed: false,
|
||||||
@ -71,15 +72,21 @@ describe('Content Manager | App | reducer', () => {
|
|||||||
expect(
|
expect(
|
||||||
mainReducer(
|
mainReducer(
|
||||||
state,
|
state,
|
||||||
setContentTypeLinks(collectionTypeLinks, singleTypeLinks, ['test'], ['test'])
|
setInitData({
|
||||||
|
authorizedCollectionTypeLinks,
|
||||||
|
authorizedSingleTypeLinks,
|
||||||
|
contentTypeSchemas: ['test'],
|
||||||
|
components: ['test'],
|
||||||
|
fieldSizes: {},
|
||||||
|
})
|
||||||
)
|
)
|
||||||
).toEqual(expected);
|
).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the resetProps action correctly', () => {
|
it('should handle the resetInitData action correctly', () => {
|
||||||
state = 'test';
|
state = 'test';
|
||||||
|
|
||||||
expect(mainReducer(state, resetProps())).toEqual({
|
expect(mainReducer(state, resetInitData())).toEqual({
|
||||||
components: [],
|
components: [],
|
||||||
models: [],
|
models: [],
|
||||||
collectionTypeLinks: [],
|
collectionTypeLinks: [],
|
||||||
|
|||||||
@ -11,11 +11,11 @@ import axios from 'axios';
|
|||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { MUTATE_COLLECTION_TYPES_LINKS, MUTATE_SINGLE_TYPES_LINKS } from '../../../exposedHooks';
|
import { MUTATE_COLLECTION_TYPES_LINKS, MUTATE_SINGLE_TYPES_LINKS } from '../../../exposedHooks';
|
||||||
import { getRequestUrl, getTrad } from '../../utils';
|
import { getRequestUrl, getTrad } from '../../utils';
|
||||||
import { getData, resetProps, setContentTypeLinks } from './actions';
|
import { getInitData, resetInitData, setInitData } from './actions';
|
||||||
import { selectAppDomain } from './selectors';
|
import { selectAppDomain } from './selectors';
|
||||||
import getContentTypeLinks from './utils/getContentTypeLinks';
|
import getContentTypeLinks from './utils/getContentTypeLinks';
|
||||||
|
|
||||||
const useModels = () => {
|
const useContentManagerInitData = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
const state = useSelector(selectAppDomain());
|
const state = useSelector(selectAppDomain());
|
||||||
@ -29,22 +29,14 @@ const useModels = () => {
|
|||||||
const { get } = useFetchClient();
|
const { get } = useFetchClient();
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
dispatch(getData());
|
dispatch(getInitData());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [
|
const {
|
||||||
{
|
data: {
|
||||||
data: { data: components },
|
data: { components, contentTypes: models, fieldSizes },
|
||||||
},
|
},
|
||||||
{
|
} = await get(getRequestUrl('init'), { cancelToken: source.token });
|
||||||
data: { data: models },
|
|
||||||
},
|
|
||||||
] = await Promise.all(
|
|
||||||
['components', 'content-types'].map((endPoint) =>
|
|
||||||
get(getRequestUrl(endPoint), { cancelToken: source.token })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
notifyStatus(
|
notifyStatus(
|
||||||
formatMessage({
|
formatMessage({
|
||||||
id: getTrad('App.schemas.data-loaded'),
|
id: getTrad('App.schemas.data-loaded'),
|
||||||
@ -52,22 +44,31 @@ const useModels = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const { authorizedCtLinks, authorizedStLinks } = await getContentTypeLinks(
|
const unmutatedContentTypeLinks = await getContentTypeLinks({
|
||||||
models,
|
models,
|
||||||
allPermissions,
|
userPermissions: allPermissions,
|
||||||
toggleNotification
|
toggleNotification,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ctLinks: authorizedCollectionTypeLinks } = runHookWaterfall(
|
||||||
|
MUTATE_COLLECTION_TYPES_LINKS,
|
||||||
|
{
|
||||||
|
ctLinks: unmutatedContentTypeLinks.authorizedCollectionTypeLinks,
|
||||||
|
models,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
const { stLinks: authorizedSingleTypeLinks } = runHookWaterfall(MUTATE_SINGLE_TYPES_LINKS, {
|
||||||
const { ctLinks } = runHookWaterfall(MUTATE_COLLECTION_TYPES_LINKS, {
|
stLinks: unmutatedContentTypeLinks.authorizedSingleTypeLinks,
|
||||||
ctLinks: authorizedCtLinks,
|
|
||||||
models,
|
|
||||||
});
|
|
||||||
const { stLinks } = runHookWaterfall(MUTATE_SINGLE_TYPES_LINKS, {
|
|
||||||
stLinks: authorizedStLinks,
|
|
||||||
models,
|
models,
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionToDispatch = setContentTypeLinks(ctLinks, stLinks, models, components);
|
const actionToDispatch = setInitData({
|
||||||
|
authorizedCollectionTypeLinks,
|
||||||
|
authorizedSingleTypeLinks,
|
||||||
|
contentTypeSchemas: models,
|
||||||
|
components,
|
||||||
|
fieldSizes,
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(actionToDispatch);
|
dispatch(actionToDispatch);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -88,7 +89,7 @@ const useModels = () => {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
source.cancel('Operation canceled by the user.');
|
source.cancel('Operation canceled by the user.');
|
||||||
dispatch(resetProps());
|
dispatch(resetInitData());
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [dispatch, toggleNotification]);
|
}, [dispatch, toggleNotification]);
|
||||||
@ -96,4 +97,4 @@ const useModels = () => {
|
|||||||
return { ...state, refetchData: fetchDataRef.current };
|
return { ...state, refetchData: fetchDataRef.current };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useModels;
|
export default useContentManagerInitData;
|
||||||
@ -52,12 +52,12 @@ const generateModelsLinks = (models, modelsConfigurations) => {
|
|||||||
const [collectionTypes, singleTypes] = sortBy(groupedModels, 'name');
|
const [collectionTypes, singleTypes] = sortBy(groupedModels, 'name');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
collectionTypesSectionLinks: generateLinks(
|
collectionTypeSectionLinks: generateLinks(
|
||||||
collectionTypes?.links || [],
|
collectionTypes?.links || [],
|
||||||
'collectionTypes',
|
'collectionTypes',
|
||||||
modelsConfigurations
|
modelsConfigurations
|
||||||
),
|
),
|
||||||
singleTypesSectionLinks: generateLinks(singleTypes?.links ?? [], 'singleTypes'),
|
singleTypeSectionLinks: generateLinks(singleTypes?.links ?? [], 'singleTypes'),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,36 +3,38 @@ import generateModelsLinks from './generateModelsLinks';
|
|||||||
import checkPermissions from './checkPermissions';
|
import checkPermissions from './checkPermissions';
|
||||||
import { getRequestUrl } from '../../../utils';
|
import { getRequestUrl } from '../../../utils';
|
||||||
|
|
||||||
const getContentTypeLinks = async (models, userPermissions, toggleNotification) => {
|
const getContentTypeLinks = async ({ models, userPermissions, toggleNotification }) => {
|
||||||
const { get } = getFetchClient();
|
const { get } = getFetchClient();
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: { data: contentTypeConfigurations },
|
data: { data: contentTypeConfigurations },
|
||||||
} = await get(getRequestUrl('content-types-settings'));
|
} = await get(getRequestUrl('content-types-settings'));
|
||||||
|
|
||||||
const { collectionTypesSectionLinks, singleTypesSectionLinks } = generateModelsLinks(
|
const { collectionTypeSectionLinks, singleTypeSectionLinks } = generateModelsLinks(
|
||||||
models,
|
models,
|
||||||
contentTypeConfigurations
|
contentTypeConfigurations
|
||||||
);
|
);
|
||||||
|
|
||||||
// Content Types verifications
|
// Collection Types verifications
|
||||||
const ctLinksPermissionsPromises = checkPermissions(
|
const collectionTypeLinksPermissions = await Promise.all(
|
||||||
userPermissions,
|
checkPermissions(userPermissions, collectionTypeSectionLinks)
|
||||||
collectionTypesSectionLinks
|
|
||||||
);
|
);
|
||||||
const ctLinksPermissions = await Promise.all(ctLinksPermissionsPromises);
|
const authorizedCollectionTypeLinks = collectionTypeSectionLinks.filter(
|
||||||
const authorizedCtLinks = collectionTypesSectionLinks.filter(
|
(_, index) => collectionTypeLinksPermissions[index]
|
||||||
(_, index) => ctLinksPermissions[index]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Single Types verifications
|
// Single Types verifications
|
||||||
const stLinksPermissionsPromises = checkPermissions(userPermissions, singleTypesSectionLinks);
|
const singleTypeLinksPermissions = await Promise.all(
|
||||||
const stLinksPermissions = await Promise.all(stLinksPermissionsPromises);
|
checkPermissions(userPermissions, singleTypeSectionLinks)
|
||||||
const authorizedStLinks = singleTypesSectionLinks.filter(
|
);
|
||||||
(_, index) => stLinksPermissions[index]
|
const authorizedSingleTypeLinks = singleTypeSectionLinks.filter(
|
||||||
|
(_, index) => singleTypeLinksPermissions[index]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { authorizedCtLinks, authorizedStLinks };
|
return {
|
||||||
|
authorizedCollectionTypeLinks,
|
||||||
|
authorizedSingleTypeLinks,
|
||||||
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ const getContentTypeLinks = async (models, userPermissions, toggleNotification)
|
|||||||
message: { id: 'notification.error' },
|
message: { id: 'notification.error' },
|
||||||
});
|
});
|
||||||
|
|
||||||
return { authorizedCtLinks: [], authorizedStLinks: [], contentTypes: [] };
|
return { authorizedCollectionTypeLinks: [], authorizedSingleTypeLinks: [] };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -119,7 +119,7 @@ describe('ADMIN | LeftMenu | utils', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
collectionTypesSectionLinks: [
|
collectionTypeSectionLinks: [
|
||||||
{
|
{
|
||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
search: null,
|
search: null,
|
||||||
@ -140,7 +140,7 @@ describe('ADMIN | LeftMenu | utils', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
singleTypesSectionLinks: [
|
singleTypeSectionLinks: [
|
||||||
{
|
{
|
||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
kind: 'singleType',
|
kind: 'singleType',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { request, hasPermissions } from '@strapi/helper-plugin';
|
import { getFetchClient, hasPermissions } from '@strapi/helper-plugin';
|
||||||
import getContentTypeLinks from '../getContentTypeLinks';
|
import getContentTypeLinks from '../getContentTypeLinks';
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
@ -44,24 +44,24 @@ describe('checkPermissions', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const data = [
|
const contentTypes = [
|
||||||
{
|
{
|
||||||
uid: 'api::address.address',
|
|
||||||
isDisplayed: true,
|
|
||||||
apiID: 'address',
|
apiID: 'address',
|
||||||
kind: 'collectionType',
|
|
||||||
info: {
|
info: {
|
||||||
label: 'address',
|
displayName: 'Address',
|
||||||
},
|
},
|
||||||
|
isDisplayed: true,
|
||||||
|
kind: 'collectionType',
|
||||||
|
uid: 'api::address.address',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: 'api::article.article',
|
|
||||||
isDisplayed: true,
|
|
||||||
apiID: 'article',
|
apiID: 'article',
|
||||||
kind: 'collectionType',
|
|
||||||
info: {
|
info: {
|
||||||
label: 'article',
|
displayName: 'Article',
|
||||||
},
|
},
|
||||||
|
isDisplayed: true,
|
||||||
|
kind: 'collectionType',
|
||||||
|
uid: 'api::article.article',
|
||||||
pluginOptions: {
|
pluginOptions: {
|
||||||
i18n: {
|
i18n: {
|
||||||
localized: true,
|
localized: true,
|
||||||
@ -70,32 +70,36 @@ describe('checkPermissions', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
request.mockImplementation((url) => {
|
getFetchClient.mockImplementation(() => ({
|
||||||
if (url === '/content-manager/content-types') {
|
get(url) {
|
||||||
return Promise.resolve({ data });
|
if (url === '/content-manager/content-types-settings') {
|
||||||
}
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
return Promise.resolve({
|
data: [
|
||||||
data: [
|
{
|
||||||
{
|
uid: 'api::address.address',
|
||||||
uid: 'api::address.address',
|
settings: {
|
||||||
settings: {
|
pageSize: 10,
|
||||||
pageSize: 10,
|
defaultSortBy: 'name',
|
||||||
defaultSortBy: 'name',
|
defaultSortOrder: 'ASC',
|
||||||
defaultSortOrder: 'ASC',
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
],
|
}
|
||||||
});
|
|
||||||
});
|
// To please the linter
|
||||||
|
return Promise.resolve(null);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
authorizedCtLinks: [
|
authorizedCollectionTypeLinks: [
|
||||||
{
|
{
|
||||||
destination: '/content-manager/collectionType/api::address.address',
|
|
||||||
icon: 'circle',
|
|
||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
label: 'address',
|
kind: 'collectionType',
|
||||||
|
name: 'api::address.address',
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
action: 'plugin::content-manager.explorer.create',
|
action: 'plugin::content-manager.explorer.create',
|
||||||
@ -107,13 +111,14 @@ describe('checkPermissions', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
search: 'page=1&pageSize=10&sort=name:ASC',
|
search: 'page=1&pageSize=10&sort=name:ASC',
|
||||||
|
title: 'Address',
|
||||||
|
to: '/content-manager/collectionType/api::address.address',
|
||||||
|
uid: 'api::address.address',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
destination: '/content-manager/collectionType/api::article.article',
|
|
||||||
icon: 'circle',
|
|
||||||
isDisplayed: true,
|
isDisplayed: true,
|
||||||
label: 'article',
|
kind: 'collectionType',
|
||||||
search: null,
|
name: 'api::article.article',
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
action: 'plugin::content-manager.explorer.create',
|
action: 'plugin::content-manager.explorer.create',
|
||||||
@ -124,12 +129,15 @@ describe('checkPermissions', () => {
|
|||||||
subject: 'api::article.article',
|
subject: 'api::article.article',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
search: null,
|
||||||
|
title: 'Article',
|
||||||
|
to: '/content-manager/collectionType/api::article.article',
|
||||||
|
uid: 'api::article.article',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
authorizedStLinks: [],
|
authorizedSingleTypeLinks: [],
|
||||||
contentTypes: data,
|
|
||||||
};
|
};
|
||||||
const actual = await getContentTypeLinks(userPermissions);
|
const actual = await getContentTypeLinks({ userPermissions, models: contentTypes });
|
||||||
|
|
||||||
expect(actual).toEqual(expected);
|
expect(actual).toEqual(expected);
|
||||||
});
|
});
|
||||||
@ -139,11 +147,13 @@ describe('checkPermissions', () => {
|
|||||||
const toggleNotification = jest.fn();
|
const toggleNotification = jest.fn();
|
||||||
const userPermissions = [];
|
const userPermissions = [];
|
||||||
|
|
||||||
request.mockImplementation(() => {
|
getFetchClient.mockImplementation(() => ({
|
||||||
throw new Error('Something went wrong');
|
get() {
|
||||||
});
|
throw new Error('Something went wrong');
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
await getContentTypeLinks(userPermissions, toggleNotification);
|
await getContentTypeLinks({ userPermissions, toggleNotification });
|
||||||
expect(toggleNotification).toBeCalled();
|
expect(toggleNotification).toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -6,7 +6,7 @@ import { useSelector, shallowEqual } from 'react-redux';
|
|||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useLayoutDnd } from '../../../hooks';
|
import { useLayoutDnd } from '../../../hooks';
|
||||||
import { createPossibleMainFieldsForModelsAndComponents, getInputProps } from '../utils';
|
import { createPossibleMainFieldsForModelsAndComponents, getInputProps } from '../utils';
|
||||||
import { makeSelectModelAndComponentSchemas } from '../../App/selectors';
|
import { makeSelectModelAndComponentSchemas, selectFieldSizes } from '../../App/selectors';
|
||||||
import getTrad from '../../../utils/getTrad';
|
import getTrad from '../../../utils/getTrad';
|
||||||
import GenericInput from './GenericInput';
|
import GenericInput from './GenericInput';
|
||||||
|
|
||||||
@ -17,8 +17,6 @@ const FIELD_SIZES = [
|
|||||||
[12, '100%'],
|
[12, '100%'],
|
||||||
];
|
];
|
||||||
|
|
||||||
const NON_RESIZABLE_FIELD_TYPES = ['dynamiczone', 'component', 'json', 'richtext'];
|
|
||||||
|
|
||||||
const TIME_FIELD_OPTIONS = [1, 5, 10, 15, 30, 60];
|
const TIME_FIELD_OPTIONS = [1, 5, 10, 15, 30, 60];
|
||||||
|
|
||||||
const TIME_FIELD_TYPES = ['datetime', 'time'];
|
const TIME_FIELD_TYPES = ['datetime', 'time'];
|
||||||
@ -28,6 +26,7 @@ const ModalForm = ({ onMetaChange, onSizeChange }) => {
|
|||||||
const { modifiedData, selectedField, attributes, fieldForm } = useLayoutDnd();
|
const { modifiedData, selectedField, attributes, fieldForm } = useLayoutDnd();
|
||||||
const schemasSelector = useMemo(makeSelectModelAndComponentSchemas, []);
|
const schemasSelector = useMemo(makeSelectModelAndComponentSchemas, []);
|
||||||
const { schemas } = useSelector((state) => schemasSelector(state), shallowEqual);
|
const { schemas } = useSelector((state) => schemasSelector(state), shallowEqual);
|
||||||
|
const fieldSizes = useSelector(selectFieldSizes);
|
||||||
|
|
||||||
const formToDisplay = useMemo(() => {
|
const formToDisplay = useMemo(() => {
|
||||||
if (!selectedField) {
|
if (!selectedField) {
|
||||||
@ -103,7 +102,7 @@ const ModalForm = ({ onMetaChange, onSizeChange }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const canResize = !NON_RESIZABLE_FIELD_TYPES.includes(attributes[selectedField].type);
|
const { isResizable } = fieldSizes[attributes[selectedField].type];
|
||||||
|
|
||||||
const sizeField = (
|
const sizeField = (
|
||||||
<GridItem col={6} key="size">
|
<GridItem col={6} key="size">
|
||||||
@ -152,7 +151,7 @@ const ModalForm = ({ onMetaChange, onSizeChange }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{metaFields}
|
{metaFields}
|
||||||
{canResize && sizeField}
|
{isResizable && sizeField}
|
||||||
{hasTimePicker && timeStepField}
|
{hasTimePicker && timeStepField}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
import { ArrowLeft, Check } from '@strapi/icons';
|
import { ArrowLeft, Check } from '@strapi/icons';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { getTrad } from '../../utils';
|
import { getTrad } from '../../utils';
|
||||||
import reducer, { initialState } from './reducer';
|
import reducer, { initialState } from './reducer';
|
||||||
import init from './init';
|
import init from './init';
|
||||||
@ -34,6 +35,7 @@ import ModalForm from './components/FormModal';
|
|||||||
import LayoutDndProvider from '../../components/LayoutDndProvider';
|
import LayoutDndProvider from '../../components/LayoutDndProvider';
|
||||||
import { unformatLayout } from './utils/layout';
|
import { unformatLayout } from './utils/layout';
|
||||||
import putCMSettingsEV from './utils/api';
|
import putCMSettingsEV from './utils/api';
|
||||||
|
import { selectFieldSizes } from '../App/selectors';
|
||||||
|
|
||||||
const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, updateLayout }) => {
|
const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, updateLayout }) => {
|
||||||
const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
|
const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
|
||||||
@ -49,6 +51,7 @@ const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, upd
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const modelName = get(mainLayout, ['info', 'displayName'], '');
|
const modelName = get(mainLayout, ['info', 'displayName'], '');
|
||||||
const attributes = get(modifiedData, ['attributes'], {});
|
const attributes = get(modifiedData, ['attributes'], {});
|
||||||
|
const fieldSizes = useSelector(selectFieldSizes);
|
||||||
|
|
||||||
const entryTitleOptions = Object.keys(attributes).filter((attr) => {
|
const entryTitleOptions = Object.keys(attributes).filter((attr) => {
|
||||||
const type = get(attributes, [attr, 'type'], '');
|
const type = get(attributes, [attr, 'type'], '');
|
||||||
@ -318,6 +321,7 @@ const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, upd
|
|||||||
dispatch({
|
dispatch({
|
||||||
type: 'ON_ADD_FIELD',
|
type: 'ON_ADD_FIELD',
|
||||||
name: field,
|
name: field,
|
||||||
|
fieldSizes,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onRemoveField={(rowId, index) => {
|
onRemoveField={(rowId, index) => {
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import produce from 'immer';
|
|||||||
import set from 'lodash/set';
|
import set from 'lodash/set';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
|
||||||
import { arrayMoveItem } from '../../utils';
|
import { arrayMoveItem } from '../../utils';
|
||||||
import { formatLayout, getDefaultInputSize, getFieldSize, setFieldSize } from './utils/layout';
|
import { formatLayout, getFieldSize, setFieldSize } from './utils/layout';
|
||||||
|
|
||||||
|
const DEFAULT_FIELD_SIZE = 6;
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
fieldForm: {},
|
fieldForm: {},
|
||||||
@ -29,9 +30,8 @@ const reducer = (state = initialState, action) =>
|
|||||||
}
|
}
|
||||||
case 'ON_ADD_FIELD': {
|
case 'ON_ADD_FIELD': {
|
||||||
const newState = cloneDeep(state);
|
const newState = cloneDeep(state);
|
||||||
const size = getDefaultInputSize(
|
const type = get(newState, ['modifiedData', 'attributes', action.name, 'type'], '');
|
||||||
get(newState, ['modifiedData', 'attributes', action.name, 'type'], '')
|
const size = action.fieldSizes[type]?.default ?? DEFAULT_FIELD_SIZE;
|
||||||
);
|
|
||||||
const listSize = get(newState, layoutPathEdit, []).length;
|
const listSize = get(newState, layoutPathEdit, []).length;
|
||||||
const actualRowContentPath = [...layoutPathEdit, listSize - 1, 'rowContent'];
|
const actualRowContentPath = [...layoutPathEdit, listSize - 1, 'rowContent'];
|
||||||
const rowContentToSet = get(newState, actualRowContentPath, []);
|
const rowContentToSet = get(newState, actualRowContentPath, []);
|
||||||
@ -149,8 +149,7 @@ const reducer = (state = initialState, action) =>
|
|||||||
draftState.metaToEdit = action.name;
|
draftState.metaToEdit = action.name;
|
||||||
draftState.metaForm = {
|
draftState.metaForm = {
|
||||||
metadata: get(state, ['modifiedData', 'metadatas', action.name, 'edit'], {}),
|
metadata: get(state, ['modifiedData', 'metadatas', action.name, 'edit'], {}),
|
||||||
size:
|
size: getFieldSize(action.name, state.modifiedData?.layouts?.edit) ?? DEFAULT_FIELD_SIZE,
|
||||||
getFieldSize(action.name, state.modifiedData?.layouts?.edit) ?? getDefaultInputSize(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -4,10 +4,13 @@ import { Router } from 'react-router-dom';
|
|||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
|
import { combineReducers, createStore } from 'redux';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||||
import EditSettingsView from '../index';
|
import EditSettingsView from '../index';
|
||||||
|
import cmReducers from '../../../../reducers';
|
||||||
|
|
||||||
jest.mock('@strapi/helper-plugin', () => ({
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
...jest.requireActual('@strapi/helper-plugin'),
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
@ -57,22 +60,31 @@ const makeApp = (history, layout) => {
|
|||||||
compo1: { uid: 'compo1' },
|
compo1: { uid: 'compo1' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rootReducer = combineReducers(cmReducers);
|
||||||
|
const store = createStore(rootReducer, {
|
||||||
|
'content-manager_app': {
|
||||||
|
fieldSizes: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<QueryClientProvider client={client}>
|
<Provider store={store}>
|
||||||
<IntlProvider messages={{ en: {} }} textComponent="span" locale="en">
|
<QueryClientProvider client={client}>
|
||||||
<ThemeProvider theme={lightTheme}>
|
<IntlProvider messages={{ en: {} }} textComponent="span" locale="en">
|
||||||
<DndProvider backend={HTML5Backend}>
|
<ThemeProvider theme={lightTheme}>
|
||||||
<EditSettingsView
|
<DndProvider backend={HTML5Backend}>
|
||||||
mainLayout={layout || mainLayout}
|
<EditSettingsView
|
||||||
components={components}
|
mainLayout={layout || mainLayout}
|
||||||
isContentTypeView
|
components={components}
|
||||||
slug="api::address.address"
|
isContentTypeView
|
||||||
/>
|
slug="api::address.address"
|
||||||
</DndProvider>
|
/>
|
||||||
</ThemeProvider>
|
</DndProvider>
|
||||||
</IntlProvider>
|
</ThemeProvider>
|
||||||
</QueryClientProvider>
|
</IntlProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</Provider>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
import reducer from '../reducer';
|
import reducer from '../reducer';
|
||||||
|
|
||||||
|
const fieldSizes = {
|
||||||
|
richtext: { default: 12, isResizable: false },
|
||||||
|
string: { default: 6, isResizable: true },
|
||||||
|
boolean: { default: 4, isResizable: true },
|
||||||
|
};
|
||||||
|
|
||||||
describe('CONTENT MANAGER | CONTAINERS | EditSettingsView | reducer', () => {
|
describe('CONTENT MANAGER | CONTAINERS | EditSettingsView | reducer', () => {
|
||||||
let state;
|
let state;
|
||||||
|
|
||||||
@ -82,7 +88,11 @@ describe('CONTENT MANAGER | CONTAINERS | EditSettingsView | reducer', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const action = { type: 'ON_ADD_FIELD', name: 'description' };
|
const action = {
|
||||||
|
type: 'ON_ADD_FIELD',
|
||||||
|
name: 'description',
|
||||||
|
fieldSizes,
|
||||||
|
};
|
||||||
expect(reducer(state, action)).toEqual(expected);
|
expect(reducer(state, action)).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -121,7 +131,7 @@ describe('CONTENT MANAGER | CONTAINERS | EditSettingsView | reducer', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const action = { type: 'ON_ADD_FIELD', name: 'title' };
|
const action = { type: 'ON_ADD_FIELD', name: 'title', fieldSizes };
|
||||||
expect(reducer(state, action)).toEqual(expected);
|
expect(reducer(state, action)).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -163,7 +173,7 @@ describe('CONTENT MANAGER | CONTAINERS | EditSettingsView | reducer', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const action = { type: 'ON_ADD_FIELD', name: 'isActive' };
|
const action = { type: 'ON_ADD_FIELD', name: 'isActive', fieldSizes };
|
||||||
expect(reducer(state, action)).toEqual(expected);
|
expect(reducer(state, action)).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -214,7 +224,7 @@ describe('CONTENT MANAGER | CONTAINERS | EditSettingsView | reducer', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const action = { type: 'ON_ADD_FIELD', name: 'title' };
|
const action = { type: 'ON_ADD_FIELD', name: 'title', fieldSizes };
|
||||||
expect(reducer(state, action)).toEqual(expected);
|
expect(reducer(state, action)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable indent */
|
|
||||||
const getRowSize = (arr) => arr.reduce((sum, value) => sum + value.size, 0);
|
const getRowSize = (arr) => arr.reduce((sum, value) => sum + value.size, 0);
|
||||||
|
|
||||||
const createLayout = (arr) => {
|
const createLayout = (arr) => {
|
||||||
@ -75,26 +74,6 @@ const unformatLayout = (arr) => {
|
|||||||
}, []);
|
}, []);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDefaultInputSize = (type) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'boolean':
|
|
||||||
case 'date':
|
|
||||||
case 'integer':
|
|
||||||
case 'float':
|
|
||||||
case 'biginteger':
|
|
||||||
case 'decimal':
|
|
||||||
case 'time':
|
|
||||||
return 4;
|
|
||||||
case 'json':
|
|
||||||
case 'component':
|
|
||||||
case 'richtext':
|
|
||||||
case 'dynamiczone':
|
|
||||||
return 12;
|
|
||||||
default:
|
|
||||||
return 6;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFieldSize = (name, layouts = []) => {
|
const getFieldSize = (name, layouts = []) => {
|
||||||
return layouts.reduce((acc, { rowContent }) => {
|
return layouts.reduce((acc, { rowContent }) => {
|
||||||
const size = rowContent.find((row) => row.name === name)?.size ?? null;
|
const size = rowContent.find((row) => row.name === name)?.size ?? null;
|
||||||
@ -124,12 +103,4 @@ const setFieldSize = (name, size, layouts = []) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export { createLayout, formatLayout, getFieldSize, setFieldSize, getRowSize, unformatLayout };
|
||||||
createLayout,
|
|
||||||
formatLayout,
|
|
||||||
getDefaultInputSize,
|
|
||||||
getFieldSize,
|
|
||||||
setFieldSize,
|
|
||||||
getRowSize,
|
|
||||||
unformatLayout,
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createLayout,
|
createLayout,
|
||||||
formatLayout,
|
formatLayout,
|
||||||
getDefaultInputSize,
|
|
||||||
getFieldSize,
|
getFieldSize,
|
||||||
setFieldSize,
|
setFieldSize,
|
||||||
getRowSize,
|
getRowSize,
|
||||||
@ -118,24 +117,6 @@ describe('Content Manager | containers | EditSettingsView | utils | layout', ()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getDefaultInputSize', () => {
|
|
||||||
it('Should return 6 if the type is unknown, undefined or text', () => {
|
|
||||||
expect(getDefaultInputSize(undefined)).toBe(6);
|
|
||||||
expect(getDefaultInputSize('unkown')).toBe(6);
|
|
||||||
expect(getDefaultInputSize('text')).toBe(6);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should return 12 if the type is either json, component or richtext', () => {
|
|
||||||
expect(getDefaultInputSize('json')).toBe(12);
|
|
||||||
expect(getDefaultInputSize('richtext')).toBe(12);
|
|
||||||
expect(getDefaultInputSize('component')).toBe(12);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should return 4 if the type is boolean', () => {
|
|
||||||
expect(getDefaultInputSize('boolean')).toBe(4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getFieldSize', () => {
|
describe('getFieldSize', () => {
|
||||||
const fixture = [
|
const fixture = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
const collectionTypes = require('./collection-types');
|
const collectionTypes = require('./collection-types');
|
||||||
const components = require('./components');
|
const components = require('./components');
|
||||||
const contentTypes = require('./content-types');
|
const contentTypes = require('./content-types');
|
||||||
|
const init = require('./init');
|
||||||
const relations = require('./relations');
|
const relations = require('./relations');
|
||||||
const singleTypes = require('./single-types');
|
const singleTypes = require('./single-types');
|
||||||
const uid = require('./uid');
|
const uid = require('./uid');
|
||||||
@ -11,6 +12,7 @@ module.exports = {
|
|||||||
'collection-types': collectionTypes,
|
'collection-types': collectionTypes,
|
||||||
components,
|
components,
|
||||||
'content-types': contentTypes,
|
'content-types': contentTypes,
|
||||||
|
init,
|
||||||
relations,
|
relations,
|
||||||
'single-types': singleTypes,
|
'single-types': singleTypes,
|
||||||
uid,
|
uid,
|
||||||
|
|||||||
20
packages/core/content-manager/server/controllers/init.js
Normal file
20
packages/core/content-manager/server/controllers/init.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { getService } = require('../utils');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getInitData(ctx) {
|
||||||
|
const { toDto } = getService('data-mapper');
|
||||||
|
const { findAllComponents } = getService('components');
|
||||||
|
const { getAllFieldSizes } = getService('field-sizes');
|
||||||
|
const { findAllContentTypes } = getService('content-types');
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
data: {
|
||||||
|
fieldSizes: getAllFieldSizes(),
|
||||||
|
components: findAllComponents().map(toDto),
|
||||||
|
contentTypes: findAllContentTypes().map(toDto),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -5,6 +5,14 @@ const { routing } = require('../middlewares');
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
type: 'admin',
|
type: 'admin',
|
||||||
routes: [
|
routes: [
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
path: '/init',
|
||||||
|
handler: 'init.getInitData',
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/content-types',
|
path: '/content-types',
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fieldSizesService = require('../field-sizes');
|
||||||
|
|
||||||
|
describe('field sizes service', () => {
|
||||||
|
it('should return the correct field sizes', () => {
|
||||||
|
const { getAllFieldSizes } = fieldSizesService();
|
||||||
|
const fieldSizes = getAllFieldSizes();
|
||||||
|
Object.values(fieldSizes).forEach((fieldSize) => {
|
||||||
|
expect(typeof fieldSize.isResizable).toBe('boolean');
|
||||||
|
expect([4, 6, 8, 12]).toContain(fieldSize.default);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct field size for a given type', () => {
|
||||||
|
const { getFieldSize } = fieldSizesService();
|
||||||
|
const fieldSize = getFieldSize('string');
|
||||||
|
expect(fieldSize.isResizable).toBe(true);
|
||||||
|
expect(fieldSize.default).toBe(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if the type is not found', () => {
|
||||||
|
const { getFieldSize } = fieldSizesService();
|
||||||
|
expect(() => getFieldSize('not-found')).toThrowError(
|
||||||
|
'Could not find field size for type not-found'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if the type is not provided', () => {
|
||||||
|
const { getFieldSize } = fieldSizesService();
|
||||||
|
expect(() => getFieldSize()).toThrowError('The type is required');
|
||||||
|
});
|
||||||
|
});
|
||||||
63
packages/core/content-manager/server/services/field-sizes.js
Normal file
63
packages/core/content-manager/server/services/field-sizes.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const needsFullSize = {
|
||||||
|
default: 12,
|
||||||
|
isResizable: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const smallSize = {
|
||||||
|
default: 4,
|
||||||
|
isResizable: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultSize = {
|
||||||
|
default: 6,
|
||||||
|
isResizable: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldSizes = {
|
||||||
|
// Full row and not resizable
|
||||||
|
dynamiczone: needsFullSize,
|
||||||
|
component: needsFullSize,
|
||||||
|
json: needsFullSize,
|
||||||
|
richtext: needsFullSize,
|
||||||
|
// Small and resizable
|
||||||
|
checkbox: smallSize,
|
||||||
|
boolean: smallSize,
|
||||||
|
date: smallSize,
|
||||||
|
time: smallSize,
|
||||||
|
biginteger: smallSize,
|
||||||
|
decimal: smallSize,
|
||||||
|
float: smallSize,
|
||||||
|
integer: smallSize,
|
||||||
|
number: smallSize,
|
||||||
|
// Medium and resizable
|
||||||
|
datetime: defaultSize,
|
||||||
|
email: defaultSize,
|
||||||
|
enumeration: defaultSize,
|
||||||
|
media: defaultSize,
|
||||||
|
password: defaultSize,
|
||||||
|
relation: defaultSize,
|
||||||
|
string: defaultSize,
|
||||||
|
text: defaultSize,
|
||||||
|
timestamp: defaultSize,
|
||||||
|
uid: defaultSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = () => ({
|
||||||
|
getAllFieldSizes() {
|
||||||
|
return fieldSizes;
|
||||||
|
},
|
||||||
|
getFieldSize(type) {
|
||||||
|
if (!type) {
|
||||||
|
throw new Error('The type is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldSize = fieldSizes[type];
|
||||||
|
if (!fieldSize) {
|
||||||
|
throw new Error(`Could not find field size for type ${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldSize;
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -3,19 +3,21 @@
|
|||||||
const components = require('./components');
|
const components = require('./components');
|
||||||
const contentTypes = require('./content-types');
|
const contentTypes = require('./content-types');
|
||||||
const dataMapper = require('./data-mapper');
|
const dataMapper = require('./data-mapper');
|
||||||
|
const entityManager = require('./entity-manager');
|
||||||
|
const fieldSizes = require('./field-sizes');
|
||||||
const metrics = require('./metrics');
|
const metrics = require('./metrics');
|
||||||
const permissionChecker = require('./permission-checker');
|
const permissionChecker = require('./permission-checker');
|
||||||
const permission = require('./permission');
|
const permission = require('./permission');
|
||||||
const uid = require('./uid');
|
const uid = require('./uid');
|
||||||
const entityManager = require('./entity-manager');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
components,
|
components,
|
||||||
'content-types': contentTypes,
|
'content-types': contentTypes,
|
||||||
'data-mapper': dataMapper,
|
'data-mapper': dataMapper,
|
||||||
|
'entity-manager': entityManager,
|
||||||
|
'field-sizes': fieldSizes,
|
||||||
metrics,
|
metrics,
|
||||||
'permission-checker': permissionChecker,
|
'permission-checker': permissionChecker,
|
||||||
permission,
|
permission,
|
||||||
uid,
|
uid,
|
||||||
'entity-manager': entityManager,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,42 +1,28 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const { getService } = require('../../../utils');
|
||||||
const { isListable, hasEditableAttribute, hasRelationAttribute } = require('./attributes');
|
const { isListable, hasEditableAttribute, hasRelationAttribute } = require('./attributes');
|
||||||
|
|
||||||
const DEFAULT_LIST_LENGTH = 4;
|
const DEFAULT_LIST_LENGTH = 4;
|
||||||
const MAX_ROW_SIZE = 12;
|
const MAX_ROW_SIZE = 12;
|
||||||
const FIELD_TYPES_FULL_SIZE = ['dynamiczone', 'component', 'json', 'richtext'];
|
|
||||||
const FIELD_TYPES_SMALL = [
|
|
||||||
'checkbox',
|
|
||||||
'boolean',
|
|
||||||
'date',
|
|
||||||
'time',
|
|
||||||
'biginteger',
|
|
||||||
'decimal',
|
|
||||||
'float',
|
|
||||||
'integer',
|
|
||||||
'number',
|
|
||||||
];
|
|
||||||
|
|
||||||
const isAllowedFieldSize = (type, size) => {
|
const isAllowedFieldSize = (type, size) => {
|
||||||
if (FIELD_TYPES_FULL_SIZE.includes(type)) {
|
const { getFieldSize } = getService('field-sizes');
|
||||||
return size === MAX_ROW_SIZE;
|
const fieldSize = getFieldSize(type);
|
||||||
|
|
||||||
|
// Check if field was locked to another size
|
||||||
|
if (!fieldSize.isResizable && size !== fieldSize.default) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate, whether the field has 4, 6, 8 or 12 columns?
|
// Otherwise allow unless it's bigger than a row
|
||||||
return size <= MAX_ROW_SIZE;
|
return size <= MAX_ROW_SIZE;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDefaultFieldSize = (type) => {
|
const getDefaultFieldSize = (type) => {
|
||||||
if (FIELD_TYPES_FULL_SIZE.includes(type)) {
|
const { getFieldSize } = getService('field-sizes');
|
||||||
return MAX_ROW_SIZE;
|
return getFieldSize(type).default;
|
||||||
}
|
|
||||||
|
|
||||||
if (FIELD_TYPES_SMALL.includes(type)) {
|
|
||||||
return MAX_ROW_SIZE / 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MAX_ROW_SIZE / 2;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function createDefaultLayouts(schema) {
|
async function createDefaultLayouts(schema) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import * as configuration from '../services/configuration';
|
|||||||
import * as contentTypes from '../services/content-types';
|
import * as contentTypes from '../services/content-types';
|
||||||
import * as dataMapper from '../services/data-mapper';
|
import * as dataMapper from '../services/data-mapper';
|
||||||
import * as entityManager from '../services/entity-manager';
|
import * as entityManager from '../services/entity-manager';
|
||||||
|
import * as fieldSizes from '../services/field-sizes';
|
||||||
import * as metris from '../services/metris';
|
import * as metris from '../services/metris';
|
||||||
import * as permissionChecker from '../services/permission-checker';
|
import * as permissionChecker from '../services/permission-checker';
|
||||||
import * as permission from '../services/permission';
|
import * as permission from '../services/permission';
|
||||||
@ -15,6 +16,7 @@ type S = {
|
|||||||
['permission-checker']: typeof permissionChecker;
|
['permission-checker']: typeof permissionChecker;
|
||||||
components: typeof components;
|
components: typeof components;
|
||||||
configuration: typeof configuration;
|
configuration: typeof configuration;
|
||||||
|
['field-sizes']: typeof fieldSizes;
|
||||||
metris: typeof metris;
|
metris: typeof metris;
|
||||||
permission: typeof permission;
|
permission: typeof permission;
|
||||||
uid: typeof uid;
|
uid: typeof uid;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user