Merge branch 'main' into fix/protect-assets-backup

This commit is contained in:
Christian 2023-04-27 22:14:06 +02:00 committed by GitHub
commit a3ab33afac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 433 additions and 278 deletions

View File

@ -4,6 +4,8 @@ updates:
directory: /
schedule:
interval: weekly
day: sunday
time: '22:00'
versioning-strategy: increase
ignore:
# Only allow patch as minor babel versions need to be upgraded all together
@ -24,6 +26,8 @@ updates:
directory: /
schedule:
interval: weekly
day: sunday
time: '22:00'
labels:
- 'source: dependencies'
- 'pr: chore'

View File

@ -0,0 +1,17 @@
---
title: Introduction
slug: /admin/ee
tags:
- enterprise-edition
---
# Admin Enterprise Edition
This section is an overview of all the features related to the Enterprise Edition in Admin:
```mdx-code-block
import DocCardList from '@theme/DocCardList';
import { useCurrentSidebarCategory } from '@docusaurus/theme-common';
<DocCardList items={useCurrentSidebarCategory().items} />
```

View File

@ -0,0 +1,140 @@
---
title: Review Workflows
slug: /admin/ee/review-workflows
description: Review workflow technical design
tags:
- review-workflows
- implementation
- tech design
---
# Review Workflows
## Summary
The review workflow feature is only available in the Enterprise Edition.
That is why, in part, it is completely decoupled from the code of the Community Edition.
The purpose of this feature is to allow users to assign a tag to the various entities of their Strapi project. This tag is called a 'stage' and is available within what we will call a workflow.
## Detailed backend design
The Review Workflow feature have been built with one main consideration, to be decoupled from the Community Edition. As so, the implementation can relate a lot to how a plugin would be built.
All the backend code related to Review Workflow can be found in `packages/core/admin/ee`.
This code is separated into several elements:
- Two content-types
- _strapi_workflows_: `packages/core/admin/ee/server/content-types/workflow/index.js`
- _strapi_workflows_stages_: `packages/core/admin/ee/server/content-types/workflow-stage/index.js`
- Two controllers
- _workflows_: `packages/core/admin/ee/server/controllers/workflows/index.js`
- _stages_: `packages/core/admin/ee/server/controllers/workflows/stages/index.js`
- One middleware
- _contentTypeMiddleware_: `packages/core/admin/ee/server/middlewares/review-workflows.js`
- Routes
- `packages/core/admin/ee/server/routes/index.js`
- Four services
- _review-workflows_: `packages/core/admin/ee/server/services/review-workflows/review-workflows.js`
- _workflows_: `packages/core/admin/ee/server/services/review-workflows/workflows.js`
- _stages_: `packages/core/admin/ee/server/services/review-workflows/stages.js`
- _metrics_: `packages/core/admin/ee/server/services/review-workflows/metrics.js`
- One decorator
- _EntityService_ decorator: `packages/core/admin/ee/server/services/review-workflows/entity-service-decorator.js`
- One utils file
- _Review workflows utils_: `packages/core/admin/ee/server/utils/review-workflows.js`
- A bootstrap and a register part
- `packages/core/admin/ee/server/bootstrap.js`
- `packages/core/admin/ee/server/register.js`
### Content types
#### strapi_workflows
This content type stores the workflow information and is responsible for holding all the information about stages and their order. In MVP, only one workflow is stored inside the Strapi database.
#### strapi_workflows_stages
This content type store the stage information such as its name.
### Controllers
#### workflows
Used to interact with the `strapi_workflows` content-type.
#### stages
Used to interact with the `strapi_workflows_stages` content-type.
### Middlewares
#### contentTypeMiddleware
In order to properly manage the options for content-type in the root level of the object, it is necessary to relocate the `reviewWorkflows` option within the `options` object located inside the content-type data. By doing so, we can ensure that all options are consistently organized and easily accessible within their respective data structures. This will also make it simpler to maintain and update the options as needed, providing a more streamlined and efficient workflow for developers working with the system. Therefore, it is recommended to move the reviewWorkflows option to its appropriate location within the options object inside the content-type data before sending it to the admin API.
### Routes
The Admin API of the Enterprise Edition includes several routes related to the Review Workflow feature. Here is a list of those routes:
#### GET `/review-workflows/workflows`
This route returns a list of all workflows.
#### GET `/review-workflows/workflows/:id`
This route returns the details of a specific workflow identified by the id parameter.
#### GET `/review-workflows/workflows/:workflow_id/stages`
This route returns a list of all stages associated with a specific workflow identified by the workflow_id parameter.
#### GET `/review-workflows/workflows/:workflow_id/stages/:id`
This route returns the details of a specific stage identified by the id parameter and associated with the workflow identified by the workflow_id parameter.
#### PUT `/review-workflows/workflows/:workflow_id/stages`
This route updates the stages associated with a specific workflow identified by the workflow_id parameter. The updated stages are passed in the request body.
#### PUT `/content-manager/(collection|single)-types/:model_uid/:id/stage`
This route updates the stage of a specific entity identified by the id parameter and belonging to a specific collection identified by the model_uid parameter. The new stage value is passed in the request body.
### Services
The Review Workflow feature of the Enterprise Edition includes several services to manipulate workflows and stages. Here is a list of those services:
#### review-workflows
This service is used during the bootstrap and register phases of Strapi. Its primary responsibility is to migrate data on entities as needed and add the stage field to the entity schemas.
#### workflows
This service is used to manipulate the workflows entities. It provides functionalities to create, retrieve, and update workflows.
#### stages
This service is used to manipulate the stages entities and to update stages on other entities. It provides functionalities to create, retrieve, update, and delete stages.
#### metrics
This is the telemetry service used to gather information on the usage of this feature. It provides information on the number of workflows and stages created, as well as the frequency of stage updates on entities.
### Decorators
#### Entity Service
The entity service is decorated so that entities can be linked to a default stage upon creation. This allows the entities to be automatically associated with a specific workflow stage when they are created.
## Alternatives
The Review Workflow feature is currently included as a core feature within the Strapi repository. However, there has been discussion about potentially moving it to a plugin in the future. While no decision has been made on this subject yet, it is possible that it may happen at some point in the future.
## Resources
- https://docs.strapi.io/user-docs/settings/review-workflows
- https://docs.strapi.io/user-docs/content-type-builder/creating-new-content-type#creating-a-new-content-type
- https://docs.strapi.io/user-docs/users-roles-permissions/configuring-administrator-roles#plugins-and-settings
- [Content manager](/content-manager/review-workflows)
- [Content type builder](/content-type-builder/review-workflows)

View File

@ -0,0 +1,17 @@
---
title: Introduction
slug: /admin
tags:
- admin
---
# Admin
This section is an overview of all the features related to admin:
```mdx-code-block
import DocCardList from '@theme/DocCardList';
import { useCurrentSidebarCategory } from '@docusaurus/theme-common';
<DocCardList items={useCurrentSidebarCategory().items} />
```

View File

@ -16,17 +16,6 @@ const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
docs: [
'index',
{
type: 'category',
label: 'Admin',
items: [
{
type: 'doc',
label: 'Link Strapi Design System',
id: 'core/admin/link-strapi-design-system',
},
],
},
{
type: 'category',
label: 'Core',
@ -38,7 +27,26 @@ const sidebars = {
{
type: 'category',
label: 'Admin',
link: {
type: 'doc',
id: 'core/admin/intro',
},
items: [
{
type: 'category',
label: 'Enterprise Edition',
link: {
type: 'doc',
id: 'core/admin/ee/intro',
},
items: [
{
type: 'doc',
label: 'Review Workflows',
id: 'core/admin/ee/review-workflows',
},
],
},
{
type: 'doc',
label: 'Link Strapi Design System',

View File

@ -0,0 +1,53 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useDragLayer } from 'react-dnd';
import { Box } from '@strapi/design-system';
function getStyle(initialOffset, currentOffset, mouseOffset) {
if (!initialOffset || !currentOffset) {
return { display: 'none' };
}
const { x, y } = mouseOffset;
return {
transform: `translate(${x}px, ${y}px)`,
};
}
export function DragLayer({ renderItem }) {
const { itemType, isDragging, item, initialOffset, currentOffset, mouseOffset } = useDragLayer(
(monitor) => ({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialOffset: monitor.getInitialSourceClientOffset(),
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
mouseOffset: monitor.getClientOffset(),
})
);
if (!isDragging) {
return null;
}
return (
<Box
height="100%"
left={0}
position="fixed"
pointerEvents="none"
top={0}
zIndex={100}
width="100%"
>
<Box style={getStyle(initialOffset, currentOffset, mouseOffset)}>
{renderItem({ type: itemType, item })}
</Box>
</Box>
);
}
DragLayer.propTypes = {
renderItem: PropTypes.func.isRequired,
};

View File

@ -0,0 +1 @@
export * from './DragLayer';

View File

@ -1,83 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { pxToRem } from '@strapi/helper-plugin';
import { Box, Flex, Typography, IconButton } from '@strapi/design-system';
import { Trash, Drag, CarretDown } from '@strapi/icons';
const DragPreviewBox = styled(Box)`
border: 1px solid ${({ theme }) => theme.colors.neutral200};
`;
const DropdownIconWrapper = styled(Box)`
height: ${32 / 16}rem;
width: ${32 / 16}rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
svg {
height: ${6 / 16}rem;
width: ${11 / 16}rem;
> path {
fill: ${({ theme }) => theme.colors.neutral600};
}
}
`;
const ToggleButton = styled.button`
border: none;
background: transparent;
display: block;
width: 100%;
text-align: unset;
padding: 0;
`;
const DragPreview = ({ displayedValue }) => {
return (
<DragPreviewBox
paddingLeft={3}
paddingRight={3}
paddingTop={3}
paddingBottom={3}
hasRadius
background="neutral0"
width={pxToRem(300)}
>
<Flex justifyContent="space-between">
<ToggleButton type="button">
<Flex>
<DropdownIconWrapper background="neutral200">
<CarretDown />
</DropdownIconWrapper>
<Flex gap={2} paddingLeft={6} maxWidth={pxToRem(150)}>
<Typography textColor="neutral700" ellipsis>
{displayedValue}
</Typography>
</Flex>
</Flex>
</ToggleButton>
<Box paddingLeft={3}>
<Flex>
<IconButton noBorder>
<Trash />
</IconButton>
<Box paddingLeft={2}>
<IconButton noBorder>
<Drag />
</IconButton>
</Box>
</Flex>
</Box>
</Flex>
</DragPreviewBox>
);
};
DragPreview.propTypes = {
displayedValue: PropTypes.string.isRequired,
};
export default DragPreview;

View File

@ -1,85 +0,0 @@
import React from 'react';
import { useDragLayer } from 'react-dnd';
import LayoutDndProvider from '../LayoutDndProvider';
import ItemTypes from '../../utils/ItemTypes';
import CardPreview from '../../pages/ListSettingsView/components/CardPreview';
import ComponentPreview from './ComponentDragPreview';
import { RelationDragPreview } from './RelationDragPreview';
const layerStyles = {
position: 'fixed',
pointerEvents: 'none',
zIndex: 100,
left: 0,
top: 0,
width: '100%',
height: '100%',
};
function getItemStyles(initialOffset, currentOffset, mouseOffset) {
if (!initialOffset || !currentOffset) {
return { display: 'none' };
}
const { x, y } = mouseOffset;
// TODO adjust
const transform = `translate(${x}px, ${y}px)`;
return {
transform,
WebkitTransform: transform,
};
}
const CustomDragLayer = () => {
const { itemType, isDragging, item, initialOffset, currentOffset, mouseOffset } = useDragLayer(
(monitor) => ({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialOffset: monitor.getInitialSourceClientOffset(),
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
mouseOffset: monitor.getClientOffset(),
})
);
if (!isDragging) {
return null;
}
/**
* Because a user may have multiple relations / dynamic zones / repeable fields in the same content type,
* we append the fieldName for the item type to make them unique, however, we then want to extract that
* first type to apply the correct preview.
*/
const [actualType] = itemType.split('_');
return (
<LayoutDndProvider>
<div style={layerStyles}>
<div style={getItemStyles(initialOffset, currentOffset, mouseOffset)} className="col-md-2">
{[ItemTypes.EDIT_FIELD, ItemTypes.FIELD].includes(itemType) && (
<CardPreview labelField={item.labelField} />
)}
{actualType === ItemTypes.COMPONENT && (
<ComponentPreview displayedValue={item.displayedValue} />
)}
{actualType === ItemTypes.DYNAMIC_ZONE && (
<ComponentPreview displayedValue={item.displayedValue} />
)}
{actualType === ItemTypes.RELATION && (
<RelationDragPreview
displayedValue={item.displayedValue}
status={item.status}
width={item.width}
/>
)}
</div>
</div>
</LayoutDndProvider>
);
};
export default CustomDragLayer;

View File

@ -1,5 +0,0 @@
import { createContext } from 'react';
const LayoutDndContext = createContext();
export default LayoutDndContext;

View File

@ -1,3 +1,2 @@
export { default as ContentTypeLayoutContext } from './ContentTypeLayout';
export { default as LayoutDndContext } from './LayoutDnd';
export { default as WysiwygContext } from './Wysiwyg';

View File

@ -1,7 +1,6 @@
export { default as useContentTypeLayout } from './useContentTypeLayout';
export { default as useFetchContentTypeLayout } from './useFetchContentTypeLayout';
export { default as useFindRedirectionLink } from './useFindRedirectionLink';
export { default as useLayoutDnd } from './useLayoutDnd';
export { default as usePluginsQueryParams } from './usePluginsQueryParams';
export { default as useSyncRbac } from './useSyncRbac';
export { default as useWysiwyg } from './useWysiwyg';

View File

@ -1,6 +0,0 @@
import { useContext } from 'react';
import LayoutDndContext from '../contexts/LayoutDnd';
const useLayoutDnd = () => useContext(LayoutDndContext);
export default useLayoutDnd;

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Flex, Typography } from '@strapi/design-system';
import { Pencil, Cross, Drag } from '@strapi/icons';
import ellipsisCardTitle from '../utils/ellipsisCardTitle';
import { pxToRem } from '@strapi/helper-plugin';
const ActionBox = styled(Flex)`
height: ${({ theme }) => theme.spaces[7]};
@ -14,9 +14,8 @@ const ActionBox = styled(Flex)`
`;
const DragButton = styled(ActionBox)`
padding: 0 ${({ theme }) => theme.spaces[3]};
border-right: 1px solid ${({ theme }) => theme.colors.neutral150};
cursor: all-scroll;
border-right: 1px solid
${({ theme, isSibling }) => (isSibling ? theme.colors.neutral150 : theme.colors.primary200)};
svg {
width: ${12 / 16}rem;
@ -25,11 +24,6 @@ const DragButton = styled(ActionBox)`
`;
const FieldContainer = styled(Flex)`
display: inline-flex;
max-height: ${32 / 16}rem;
opacity: ${({ transparent }) => (transparent ? 0 : 1)};
background-color: ${({ theme, isSibling }) =>
isSibling ? theme.colors.neutral100 : theme.colors.primary100};
border: 1px solid
${({ theme, isSibling }) => (isSibling ? theme.colors.neutral150 : theme.colors.primary200)};
@ -41,54 +35,60 @@ const FieldContainer = styled(Flex)`
fill: ${({ theme, isSibling }) => (isSibling ? undefined : theme.colors.primary600)};
}
}
${Typography} {
color: ${({ theme, isSibling }) => (isSibling ? undefined : theme.colors.primary600)};
}
${DragButton} {
border-right: 1px solid
${({ theme, isSibling }) => (isSibling ? theme.colors.neutral150 : theme.colors.primary200)};
}
`;
const CardPreview = ({ labelField, transparent, isSibling }) => {
const cardEllipsisTitle = ellipsisCardTitle(labelField);
const TypographyMaxWidth = styled(Typography)`
max-width: ${72 / 16}rem;
`;
export function CardDragPreview({ labelField, transparent, isSibling }) {
return (
<FieldContainer
background={isSibling ? 'neutral100' : 'primary100'}
display="inline-flex"
gap={3}
hasRadius
justifyContent="space-between"
transparent={transparent}
isSibling={isSibling}
max-height={pxToRem(32)}
maxWidth="min-content"
opacity={transparent ? 0 : 1}
>
<Flex gap={3}>
<DragButton alignItems="center">
<DragButton alignItems="center" cursor="all-scroll" padding={3}>
<Drag />
</DragButton>
<Typography fontWeight="bold">{cardEllipsisTitle}</Typography>
<TypographyMaxWidth
textColor={isSibling ? undefined : 'primary600'}
fontWeight="bold"
ellipsis
>
{labelField}
</TypographyMaxWidth>
</Flex>
<Flex paddingLeft={3}>
<Flex>
<ActionBox alignItems="center">
<Pencil />
</ActionBox>
<ActionBox alignItems="center">
<Cross />
</ActionBox>
</Flex>
</FieldContainer>
);
};
}
CardPreview.defaultProps = {
CardDragPreview.defaultProps = {
isSibling: false,
transparent: false,
};
CardPreview.propTypes = {
CardDragPreview.propTypes = {
isSibling: PropTypes.bool,
labelField: PropTypes.string.isRequired,
transparent: PropTypes.bool,
};
export default CardPreview;

View File

@ -0,0 +1,75 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { pxToRem } from '@strapi/helper-plugin';
import { Flex, Typography, IconButton } from '@strapi/design-system';
import { Trash, Drag, CarretDown } from '@strapi/icons';
const DropdownIconWrapper = styled(Flex)`
border-radius: 50%;
svg {
height: ${6 / 16}rem;
width: ${11 / 16}rem;
> path {
fill: ${({ theme }) => theme.colors.neutral600};
}
}
`;
// TODO: we shouldn't have to reset a whole button
const ToggleButton = styled.button`
border: none;
background: transparent;
display: block;
width: 100%;
text-align: unset;
padding: 0;
`;
export function ComponentDragPreview({ displayedValue }) {
return (
<Flex
background="neutral0"
borderColor="neutral200"
justifyContent="space-between"
gap={3}
padding={3}
width={pxToRem(300)}
>
<ToggleButton type="button">
<Flex gap={6}>
<DropdownIconWrapper
alignItems="center"
justifyContent="center"
background="neutral200"
height={pxToRem(32)}
width={pxToRem(32)}
>
<CarretDown />
</DropdownIconWrapper>
<Flex maxWidth={pxToRem(150)}>
<Typography textColor="neutral700" ellipsis>
{displayedValue}
</Typography>
</Flex>
</Flex>
</ToggleButton>
<Flex gap={2}>
<IconButton noBorder>
<Trash />
</IconButton>
<IconButton noBorder>
<Drag />
</IconButton>
</Flex>
</Flex>
);
}
ComponentDragPreview.propTypes = {
displayedValue: PropTypes.string.isRequired,
};

View File

@ -4,10 +4,13 @@ import PropTypes from 'prop-types';
import { Box, Flex, IconButton, Typography, Status, Icon } from '@strapi/design-system';
import { Drag, Cross } from '@strapi/icons';
import { getTrad } from '../../utils';
import { PUBLICATION_STATES } from '../RelationInputDataManager/constants';
import { ChildrenWrapper, FlexWrapper } from '../RelationInput/components/RelationItem';
import { LinkEllipsis, DisconnectButton } from '../RelationInput';
import { getTrad } from '../../../utils';
import { PUBLICATION_STATES } from '../../../components/RelationInputDataManager/constants';
import {
ChildrenWrapper,
FlexWrapper,
} from '../../../components/RelationInput/components/RelationItem';
import { LinkEllipsis, DisconnectButton } from '../../../components/RelationInput';
export const RelationDragPreview = ({ status, displayedValue, width }) => {
const { formatMessage } = useIntl();

View File

@ -12,7 +12,7 @@ import { useIntl } from 'react-intl';
import sortBy from 'lodash/sortBy';
import permissions from '../../../permissions';
import getTrad from '../../utils/getTrad';
import DragLayer from '../../components/DragLayer';
import { DragLayer } from '../../../components/DragLayer';
import ModelsContext from '../../contexts/ModelsContext';
import CollectionTypeRecursivePath from '../CollectionTypeRecursivePath';
import ComponentSettingsView from '../ComponentSetttingsView';
@ -22,8 +22,45 @@ import SingleTypeRecursivePath from '../SingleTypeRecursivePath';
import LeftMenu from './LeftMenu';
import useContentManagerInitData from './useContentManagerInitData';
import ItemTypes from '../../utils/ItemTypes';
import { CardDragPreview } from './components/CardDragPreview';
import { ComponentDragPreview } from './components/ComponentDragPreview';
import { RelationDragPreview } from './components/RelationDragPreview';
const cmPermissions = permissions.contentManager;
function renderDraglayerItem({ type, item }) {
if ([ItemTypes.EDIT_FIELD, ItemTypes.FIELD].includes(type)) {
return <CardDragPreview labelField={item.labelField} />;
}
/**
* Because a user may have multiple relations / dynamic zones / repeable fields in the same content type,
* we append the fieldName for the item type to make them unique, however, we then want to extract that
* first type to apply the correct preview.
*/
const [actualType] = type.split('_');
switch (actualType) {
case ItemTypes.COMPONENT:
case ItemTypes.DYNAMIC_ZONE:
return <ComponentDragPreview displayedValue={item.displayedValue} />;
case ItemTypes.RELATION:
return (
<RelationDragPreview
displayedValue={item.displayedValue}
status={item.status}
width={item.width}
/>
);
default:
return null;
}
}
const App = () => {
const contentTypeMatch = useRouteMatch(`/content-manager/:kind/:uid`);
const { status, collectionTypeLinks, singleTypeLinks, models, refetchData } =
@ -85,7 +122,7 @@ const App = () => {
return (
<Layout sideNav={<LeftMenu />}>
<DragLayer />
<DragLayer renderItem={renderDraglayerItem} />
<ModelsContext.Provider value={{ refetchData }}>
<Switch>
<Route path="/content-manager/components/:uid/configurations/edit">

View File

@ -5,7 +5,7 @@ import { Box, Flex, Typography, Grid, GridItem } from '@strapi/design-system';
import { Cog } from '@strapi/icons';
import { useIntl } from 'react-intl';
import get from 'lodash/get';
import useLayoutDnd from '../../../hooks/useLayoutDnd';
import { useLayoutDnd } from '../hooks/useLayoutDnd';
import getTrad from '../../../utils/getTrad';
const ComponentFieldList = ({ componentUid }) => {

View File

@ -7,7 +7,7 @@ import { Flex, Box, GridItem } from '@strapi/design-system';
import { Drag } from '@strapi/icons';
import { ItemTypes } from '../../../utils';
import FieldButtonContent from './FieldButtonContent';
import { useLayoutDnd } from '../../../hooks';
import { useLayoutDnd } from '../hooks/useLayoutDnd';
const Wrapper = styled(Flex)`
position: relative;

View File

@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import { Box, Flex, Typography } from '@strapi/design-system';
import { ComponentIcon } from '../../../components/ComponentIcon';
import useLayoutDnd from '../../../hooks/useLayoutDnd';
import { useLayoutDnd } from '../hooks/useLayoutDnd';
const CustomLink = styled(Flex)`
text-decoration: none;

View File

@ -14,7 +14,7 @@ import {
} from '@strapi/design-system';
import styled from 'styled-components';
import { getTrad } from '../../../utils';
import { useLayoutDnd } from '../../../hooks';
import { useLayoutDnd } from '../hooks/useLayoutDnd';
import FieldTypeIcon from '../../../components/FieldTypeIcon';
import ModalForm from './ModalForm';

View File

@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import LayoutDndContext from '../../contexts/LayoutDnd';
function LayoutDndProvider({
export const LayoutDndContext = React.createContext();
export function LayoutDndProvider({
attributes,
buttonData,
children,
@ -71,5 +72,3 @@ LayoutDndProvider.propTypes = {
selectedItemName: PropTypes.string,
setEditFieldToSelect: PropTypes.func,
};
export default LayoutDndProvider;

View File

@ -10,7 +10,7 @@ import { useIntl } from 'react-intl';
import get from 'lodash/get';
import { Pencil } from '@strapi/icons';
import getTrad from '../../../utils/getTrad';
import useLayoutDnd from '../../../hooks/useLayoutDnd';
import { useLayoutDnd } from '../hooks/useLayoutDnd';
const permissions = [{ action: 'plugin::content-type-builder.read', subject: null }];

View File

@ -4,7 +4,7 @@ import get from 'lodash/get';
import { GridItem, Select, Option } from '@strapi/design-system';
import { useSelector, shallowEqual } from 'react-redux';
import { useIntl } from 'react-intl';
import { useLayoutDnd } from '../../../hooks';
import { useLayoutDnd } from '../hooks/useLayoutDnd';
import { createPossibleMainFieldsForModelsAndComponents, getInputProps } from '../utils';
import { makeSelectModelAndComponentSchemas, selectFieldSizes } from '../../App/selectors';
import getTrad from '../../../utils/getTrad';

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import get from 'lodash/get';
import { useLayoutDnd } from '../../../hooks';
import { useLayoutDnd } from '../hooks/useLayoutDnd';
import DisplayedFieldButton from './DisplayedFieldButton';
const RowItemsLayout = ({ rowItem, onRemoveField, rowId, rowIndex, index, lastIndex }) => {

View File

@ -0,0 +1,6 @@
import * as React from 'react';
import { LayoutDndContext } from '../components/LayoutDndProvider';
export function useLayoutDnd() {
return React.useContext(LayoutDndContext);
}

View File

@ -32,7 +32,7 @@ import reducer, { initialState } from './reducer';
import init from './init';
import DisplayedFields from './components/DisplayedFields';
import ModalForm from './components/FormModal';
import LayoutDndProvider from '../../components/LayoutDndProvider';
import { LayoutDndProvider } from './components/LayoutDndProvider';
import { unformatLayout } from './utils/layout';
import putCMSettingsEV from './utils/api';
import { selectFieldSizes } from '../App/selectors';

View File

@ -6,8 +6,8 @@ import { getEmptyImage } from 'react-dnd-html5-backend';
import { useIntl } from 'react-intl';
import { Flex, Box, Typography } from '@strapi/design-system';
import { Pencil, Cross, Drag } from '@strapi/icons';
import CardPreview from './CardPreview';
import ellipsisCardTitle from '../utils/ellipsisCardTitle';
import { CardDragPreview } from '../../App/components/CardDragPreview';
import { getTrad, ItemTypes } from '../../../utils';
const ActionButton = styled.button`
@ -85,7 +85,6 @@ const DraggableCard = ({
const dropRef = useRef(null);
const [, forceRerenderAfterDnd] = useState(false);
const editButtonRef = useRef();
const cardEllipsisTitle = ellipsisCardTitle(labelField);
const handleClickEditRow = () => {
if (editButtonRef.current) {
@ -93,6 +92,7 @@ const DraggableCard = ({
}
};
// TODO: this can be simplified a lot by using the useDragAndDrop() hook
const [, drop] = useDrop({
accept: ItemTypes.FIELD,
hover(item, monitor) {
@ -176,8 +176,8 @@ const DraggableCard = ({
return (
<FieldWrapper ref={refs ? refs.dropRef : null}>
{isDragging && <CardPreview transparent labelField={cardEllipsisTitle} />}
{!isDragging && isDraggingSibling && <CardPreview isSibling labelField={cardEllipsisTitle} />}
{isDragging && <CardDragPreview transparent labelField={labelField} />}
{!isDragging && isDraggingSibling && <CardDragPreview isSibling labelField={labelField} />}
{!isDragging && !isDraggingSibling && (
<FieldContainer
@ -204,7 +204,7 @@ const DraggableCard = ({
>
<Drag />
</DragButton>
<Typography fontWeight="bold">{cardEllipsisTitle}</Typography>
<Typography fontWeight="bold">{labelField}</Typography>
</Flex>
<Flex paddingLeft={3}>
<ActionButton

View File

@ -1,19 +0,0 @@
import ellipsisCardTitle from '../utils/ellipsisCardTitle';
describe('CONTENT MANAGER | ListSettingsView | ellipsisCardTitle', () => {
it('should return the title without an ellipsis if the title length < 20', () => {
const title = 'michka';
const result = ellipsisCardTitle(title);
const expected = 'michka';
expect(result).toEqual(expected);
});
it('should return the title with an ellipsis if the title length > 20', () => {
const title = 'michka_des_ronrons_celestes';
const result = ellipsisCardTitle(title);
const expected = `${title.substring(0, 20)}...`;
expect(result).toEqual(expected);
});
});

View File

@ -1,7 +0,0 @@
const ellipsisCardTitle = (title) => {
const formatedTitle = title.length > 20 ? `${title.substring(0, 20)}...` : title;
return formatedTitle;
};
export default ellipsisCardTitle;

View File

@ -71,7 +71,6 @@ describe('field sizes service', () => {
const { setCustomFieldInputSizes, getAllFieldSizes } = createFieldSizesService({ strapi });
setCustomFieldInputSizes();
const fieldSizes = getAllFieldSizes();
console.log(fieldSizes);
expect(fieldSizes).not.toHaveProperty('plugin::mycustomfields.color');
expect(fieldSizes['plugin::mycustomfields.smallColor'].default).toBe(4);

View File

@ -101,7 +101,7 @@ module.exports = function createComponentBuilder() {
contentType
.setUID(uid)
.set('kind', infos.kind || typeKinds.COLLECTION_TYPE)
.set('collectionName', nameToCollectionName(infos.pluralName))
.set('collectionName', infos.collectionName || nameToCollectionName(infos.pluralName))
.set('info', {
singularName: infos.singularName,
pluralName: infos.pluralName,

View File

@ -34,8 +34,8 @@ import pxToRem from '../utils/pxToRem';
/**
* @preserve
* @typedef {Object} AutoReloadOverlayBlockerContextValue
* @property {(config: AutoReloadOverlayBlockerConfig) => void} lockApp
* @property {() => void} unlockApp
* @property {(config: AutoReloadOverlayBlockerConfig) => void} lockAppWithAutoreload
* @property {() => void} unlockAppWithAutoreload
*/
/**
@ -111,8 +111,8 @@ const AutoReloadOverlayBlockerProvider = ({ children }) => {
const autoReloadValue = React.useMemo(
() => ({
lockApp: lockAppWithAutoreload,
unlockApp: unlockAppWithAutoreload,
lockAppWithAutoreload,
unlockAppWithAutoreload,
}),
[lockAppWithAutoreload, unlockAppWithAutoreload]
);

View File

@ -5,6 +5,7 @@ const { existsSync } = require('fs-extra');
const _ = require('lodash');
const fse = require('fs-extra');
const { isKebabCase, importDefault } = require('@strapi/utils');
const { isEmpty } = require('lodash/fp');
const DEFAULT_CONTENT_TYPE = {
schema: {},
@ -115,6 +116,10 @@ const loadContentTypes = async (dir) => {
const contentTypeName = normalizeName(fd.name);
const contentType = await loadDir(join(dir, fd.name));
if (isEmpty(contentType) || isEmpty(contentType.schema)) {
throw new Error(`Could not load content type found at ${dir}`);
}
contentTypes[normalizeName(contentTypeName)] = _.defaults(contentType, DEFAULT_CONTENT_TYPE);
}

View File

@ -68,7 +68,6 @@ describe('Utils', () => {
describe('Get Definition Attributes Count', () => {
const createMainNode = (members = []) => {
return factory.createInterfaceDeclaration(
undefined,
undefined,
factory.createIdentifier('Foo'),
undefined,
@ -79,7 +78,6 @@ describe('Utils', () => {
const createPropertyDeclaration = (name, type) => {
return factory.createPropertyDeclaration(
undefined,
undefined,
factory.createIdentifier(name),
undefined,