Merge branch 'v4/ds-migration' of github.com:strapi/strapi into migration/cm-table

This commit is contained in:
soupette 2021-09-13 07:20:25 +02:00
commit 2a5d4938c5
14 changed files with 340 additions and 414 deletions

View File

@ -88,6 +88,8 @@ const SocialLinks = () => {
return (
<Box
as="aside"
aria-labelledby="join-the-community"
background="neutral0"
hasRadius
paddingRight={5}
@ -99,7 +101,7 @@ const SocialLinks = () => {
<Box paddingBottom={7}>
<Stack size={5}>
<Stack size={3}>
<H3>
<H3 as="h2" id="join-the-community">
{formatMessage({
id: 'app.components.HomePage.community',
defaultMessage: 'Join the community',

View File

@ -60,7 +60,7 @@ const HomePage = () => {
</FormattedMessage>
<Main labelledBy="homepage">
<LogoContainer>
<img alt="Strapi logo" src={Logo} />
<img alt="" aria-hidden src={Logo} />
</LogoContainer>
<Box padding={10}>
<Grid>

View File

@ -917,7 +917,8 @@ describe('Homepage', () => {
<div
class=""
>
<div
<aside
aria-labelledby="join-the-community"
class="c34"
>
<div
@ -929,11 +930,12 @@ describe('Homepage', () => {
<div
class="c36"
>
<h3
<h2
class="c37"
id="join-the-community"
>
Join the community
</h3>
</h2>
<span
class="c12 c38"
>
@ -1288,7 +1290,7 @@ describe('Homepage', () => {
</div>
</div>
</div>
</div>
</aside>
</div>
</div>
</div>

View File

@ -0,0 +1,76 @@
import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { Box } from '@strapi/parts';
import {
Component,
CT,
Date,
Boolean,
DynamicZone,
Email,
Enumeration,
Json,
LongDescription,
Media,
Numbers,
Password,
Relation,
St,
Text,
Uid,
} from '@strapi/icons';
import { pxToRem } from '@strapi/helper-plugin';
const iconByTypes = {
biginteger: Numbers,
boolean: Boolean,
component: Component,
contentType: CT,
date: Date,
datetime: Date,
decimal: Numbers,
dynamiczone: DynamicZone,
email: Email,
enum: Enumeration,
enumeration: Enumeration,
file: Media,
files: Media,
float: Numbers,
integer: Numbers,
json: Json,
JSON: Json,
media: Media,
number: Numbers,
password: Password,
relation: Relation,
richtext: LongDescription,
singleType: St,
string: Text,
text: Text,
time: Date,
timestamp: Date,
uid: Uid,
};
const IconBox = styled(Box)`
width: ${pxToRem(32)};
height: ${pxToRem(24)};
box-sizing: content-box;
`;
const AttributeIcon = ({ type, ...rest }) => {
const Compo = iconByTypes[type];
if (!iconByTypes[type]) {
return null;
}
return <IconBox as={Compo} {...rest} />;
};
AttributeIcon.propTypes = {
type: PropTypes.string.isRequired,
};
export default AttributeIcon;

View File

@ -6,6 +6,7 @@
/* eslint-disable import/no-cycle */
import React from 'react';
import { get } from 'lodash';
import { Box } from '@strapi/parts';
import PropTypes from 'prop-types';
import List from '../List';
import useDataManager from '../../hooks/useDataManager';
@ -28,6 +29,19 @@ function ComponentList({
schema: { attributes: [] },
});
// ! TEMPORARY DISABLE NESTED COMPONENT
const bool = true; // prevent eslint from crying
if (bool) {
return (
<tr>
<td colSpan={12} style={{ padding: 10 }}>
<Box background="neutral100" style={{ height: 40 }} />
</td>
</tr>
);
}
return (
<tr className="component-row">
<Td colSpan={12} isChildOfDynamicZone={isFromDynamicZone}>

View File

@ -0,0 +1,50 @@
/**
*
* Wrapper
*
*/
import { Box } from '@strapi/parts';
import styled from 'styled-components';
const BoxWrapper = styled(Box)`
overflow-x: auto;
table {
width: 100%;
white-space: nowrap;
}
thead {
border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};
}
tr:not(:last-child) {
border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};
}
tr {
& td,
& th {
padding: ${({ theme }) => theme.spaces[4]};
}
& td:first-of-type,
& th:first-of-type {
padding: 0 ${({ theme }) => theme.spaces[1]};
}
}
th,
td {
vertical-align: middle;
text-align: left;
color: ${({ theme }) => theme.colors.neutral600};
outline-offset: -4px;
}
`;
BoxWrapper.defaultProps = {
isFromDynamicZone: false,
};
export default BoxWrapper;

View File

@ -1,131 +0,0 @@
/**
*
* Wrapper
*
*/
import styled from 'styled-components';
import { List } from '@buffetjs/styles';
const Wrapper = styled(List)`
table-layout: fixed;
tbody {
td:first-of-type:not(:last-of-type) {
width: 73px;
padding-left: 30px;
> svg {
width: auto;
height: 16px;
position: absolute;
left: -4px;
top: 16px;
display: none;
}
}
td[colspan='12'] {
position: relative;
padding: 0 0 0 50px;
> div {
box-shadow: none;
}
}
tr.component-row {
&:not(:first-of-type) {
&::before {
background-color: transparent;
}
}
table tr td:first-of-type:not(:last-of-type) {
width: 79px;
padding-left: 36px;
svg {
display: block;
}
}
}
table + div button {
position: relative;
background-color: transparent;
text-transform: initial;
color: #9ea7b8;
text-align: left;
padding-left: 35px;
border-color: transparent;
svg {
position: absolute;
top: 0;
left: 0;
}
}
tr.dynamiczone-row {
&:not(:first-of-type) {
&::before {
background-color: transparent;
}
}
> td[colspan='12'] {
padding-left: 0;
padding-right: 0;
}
.tabs-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
padding-top: 18px;
padding-left: 86px;
padding-right: 30px;
.nav-tabs {
border-bottom: 0;
}
ul.nav {
width: 100%;
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
li {
margin-right: 9px;
}
}
& + .tab-content {
padding-top: 126px;
position: relative;
z-index: 1;
}
}
}
}
& + .plus-icon {
width: 27px;
height: 27px;
border-radius: 18px;
position: absolute;
bottom: 14px;
left: 34px;
background-color: ${({ isFromDynamicZone }) => (isFromDynamicZone ? '#AED4FB' : '#f3f4f4')};
color: transparent;
text-align: center;
line-height: 27px;
display: flex;
cursor: pointer;
svg {
margin: auto;
width: 11px;
height: 11px;
}
}
`;
Wrapper.defaultProps = {
isFromDynamicZone: false,
};
export default Wrapper;

View File

@ -8,22 +8,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { ListButton, useTracking } from '@strapi/helper-plugin';
import { Button } from '@buffetjs/core';
import { Plus } from '@buffetjs/icons';
import { useTracking } from '@strapi/helper-plugin';
import { TableLabel, TFooter, Box, TextButton } from '@strapi/parts';
import { AddIcon } from '@strapi/icons';
import { useIntl } from 'react-intl';
import pluginId from '../../pluginId';
import useListView from '../../hooks/useListView';
import useDataManager from '../../hooks/useDataManager';
import DynamicZoneList from '../DynamicZoneList';
import ComponentList from '../ComponentList';
import Wrapper from './List';
import BoxWrapper from './BoxWrapper';
import getTrad from '../../utils/getTrad';
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
function List({
className,
customRowComponent,
items,
addComponentToDZ,
@ -160,106 +159,114 @@ function List({
);
};
/* eslint-disable indent */
const addButtonProps = {
icon: !isSub ? <Plus fill="#007eff" width="11px" height="11px" /> : false,
color: 'primary',
label: isInDevelopmentMode
? formatMessage({
id: !isSub
? `${pluginId}.form.button.add.field.to.${
modifiedData.contentType
? modifiedData.contentType.schema.kind
: editTarget || 'collectionType'
}`
: `${pluginId}.form.button.add.field.to.component`,
defaultMessage: 'Add another field',
})
: null,
onClick: onClickAddField,
};
/* eslint-enable indent */
if (!targetUid) {
return null;
}
return (
<>
<Wrapper className={className} isFromDynamicZone={isFromDynamicZone}>
<table>
<tbody>
{items.map(item => {
const { type } = item;
const CustomRow = customRowComponent;
<BoxWrapper
isFromDynamicZone={isFromDynamicZone}
background="neutral0"
shadow="filterShadow"
hasRadius
>
<Box paddingLeft={6} paddingRight={6}>
<table>
<thead>
<tr>
<th>
<TableLabel textColor="neutral600">
{formatMessage({ id: 'table.headers.name', defaultMessage: 'Name' })}
</TableLabel>
</th>
<th colSpan="2">
<TableLabel textColor="neutral600">
{formatMessage({ id: 'table.headers.type', defaultMessage: 'Type' })}
</TableLabel>
</th>
</tr>
</thead>
<tbody>
{items.map(item => {
const { type } = item;
const CustomRow = customRowComponent;
return (
<React.Fragment key={item.name}>
<CustomRow
{...item}
dzName={dzName}
isNestedInDZComponent={isNestedInDZComponent}
targetUid={targetUid}
mainTypeName={mainTypeName}
editTarget={editTarget}
firstLoopComponentName={firstLoopComponentName}
firstLoopComponentUid={firstLoopComponentUid}
isFromDynamicZone={isFromDynamicZone}
secondLoopComponentName={secondLoopComponentName}
secondLoopComponentUid={secondLoopComponentUid}
/>
{type === 'component' && (
<ComponentList
return (
<React.Fragment key={item.name}>
<CustomRow
{...item}
customRowComponent={customRowComponent}
targetUid={targetUid}
dzName={dzName}
isNestedInDZComponent={isFromDynamicZone}
isNestedInDZComponent={isNestedInDZComponent}
targetUid={targetUid}
mainTypeName={mainTypeName}
editTarget={editTarget}
firstLoopComponentName={firstLoopComponentName}
firstLoopComponentUid={firstLoopComponentUid}
isFromDynamicZone={isFromDynamicZone}
secondLoopComponentName={secondLoopComponentName}
secondLoopComponentUid={secondLoopComponentUid}
/>
)}
{type === 'dynamiczone' && (
<DynamicZoneList
{...item}
customRowComponent={customRowComponent}
addComponent={addComponentToDZ}
targetUid={targetUid}
mainTypeName={mainTypeName}
/>
)}
</React.Fragment>
);
})}
</tbody>
</table>
{type === 'component' && (
<ComponentList
{...item}
customRowComponent={customRowComponent}
targetUid={targetUid}
dzName={dzName}
isNestedInDZComponent={isFromDynamicZone}
mainTypeName={mainTypeName}
editTarget={editTarget}
firstLoopComponentName={firstLoopComponentName}
firstLoopComponentUid={firstLoopComponentUid}
/>
)}
{type === 'dynamiczone' && (
<DynamicZoneList
{...item}
customRowComponent={customRowComponent}
addComponent={addComponentToDZ}
targetUid={targetUid}
mainTypeName={mainTypeName}
/>
)}
</React.Fragment>
);
})}
</tbody>
</table>
</Box>
{isMain && isInDevelopmentMode && (
<ListButton>
<Button {...addButtonProps} />
</ListButton>
<TFooter icon={<AddIcon />} onClick={onClickAddField}>
{formatMessage({
id: getTrad(
`form.button.add.field.to.${
modifiedData.contentType
? modifiedData.contentType.schema.kind
: editTarget || 'collectionType'
}`
),
defaultMessage: 'Add another field',
})}
</TFooter>
)}
{!isMain && (
<ListButton>
<Button {...addButtonProps} />
</ListButton>
{isSub && isInDevelopmentMode && (
<TextButton startIcon={<AddIcon />} onClick={onClickAddField}>
{formatMessage({
id: getTrad(`form.button.add.field.to.component`),
defaultMessage: 'Add another field',
})}
</TextButton>
)}
</Wrapper>
{isSub && (
<div className="plus-icon" onClick={onClickAddField}>
{isInDevelopmentMode && <Plus fill={isFromDynamicZone ? '#007EFF' : '#b4b6ba'} />}
</div>
)}
</BoxWrapper>
</>
);
}
List.defaultProps = {
addComponentToDZ: () => {},
className: null,
customRowComponent: null,
dzName: null,
firstLoopComponentName: null,
@ -276,7 +283,6 @@ List.defaultProps = {
List.propTypes = {
addComponentToDZ: PropTypes.func,
className: PropTypes.string,
customRowComponent: PropTypes.func,
dzName: PropTypes.string,
editTarget: PropTypes.string.isRequired,

View File

@ -1,9 +0,0 @@
import styled from 'styled-components';
import { Button } from '@buffetjs/core';
const ListHeaderButton = styled(Button)`
padding-left: 15px;
padding-right: 15px;
`;
export default ListHeaderButton;

View File

@ -0,0 +1,12 @@
/**
*
* Wrapper
*
*/
import styled from 'styled-components';
import { Box } from '@strapi/parts';
const BoxWrapper = styled(Box)``;
export default BoxWrapper;

View File

@ -1,76 +0,0 @@
/**
*
* Wrapper
*
*/
import styled from 'styled-components';
import { colors } from '@strapi/helper-plugin';
/* eslint-disable indent */
const Wrapper = styled.tr`
background-color: transparent;
p {
margin-bottom: 0;
}
img {
width: 35px;
}
button {
cursor: pointer;
}
td:first-of-type {
padding-left: 3rem;
position: relative;
img {
width: 35px;
height: 20px;
position: absolute;
top: calc(50% - 10px);
left: 3rem;
}
img + p {
width: 237px;
padding-left: calc(3rem + 35px);
}
}
td:nth-child(2) {
${({ loopNumber }) => {
return `
width: calc(25rem - ${5 * loopNumber}rem);
`;
}}
p {
font-weight: 500;
}
}
td:last-child {
text-align: right;
&:not(:first-of-type) {
font-size: 10px;
}
}
&.relation-row {
background: linear-gradient(135deg, rgba(28, 93, 231, 0.05), rgba(239, 243, 253, 0));
}
&.clickable {
&:hover {
cursor: pointer;
background-color: ${colors.grey};
& + tr {
&::before {
background-color: transparent;
}
}
}
}
.button-container {
svg {
color: #333740;
vertical-align: middle;
}
}
`;
export default Wrapper;

View File

@ -1,16 +1,18 @@
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { FormattedMessage } from 'react-intl';
import { AttributeIcon, IconLinks } from '@buffetjs/core';
import upperFirst from 'lodash/upperFirst';
import { useIntl } from 'react-intl';
import { IconButton, Stack, Text, Row } from '@strapi/parts';
import { EditIcon, DeleteIcon } from '@strapi/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import pluginId from '../../pluginId';
import useDataManager from '../../hooks/useDataManager';
import getAttributeDisplayedType from '../../utils/getAttributeDisplayedType';
import getTrad from '../../utils/getTrad';
import Curve from '../../icons/Curve';
// import Curve from '../../icons/Curve';
import UpperFist from '../UpperFirst';
import Wrapper from './Wrapper';
import BoxWrapper from './BoxWrapper';
import AttributeIcon from '../AttributeIcon';
function ListRow({
configurable,
@ -33,6 +35,7 @@ function ListRow({
relation,
}) {
const { contentTypes, isInDevelopmentMode, modifiedData, removeAttribute } = useDataManager();
const { formatMessage } = useIntl();
const isMorph = type === 'relation' && relation.includes('morph');
const ico = ['integer', 'biginteger', 'float', 'decimal'].includes(type) ? 'number' : type;
@ -198,97 +201,90 @@ function ListRow({
}
return (
<Wrapper
<BoxWrapper
as="tr"
onClick={handleClick}
className={[target ? 'relation-row' : '', configurable ? 'clickable' : '']}
// PaddingLeft handled here in nested component
loopNumber={loopNumber}
>
<td>
<AttributeIcon key={src} type={src} />
<Curve fill={isFromDynamicZone ? '#AED4FB' : '#f3f4f4'} />
</td>
<td style={{ fontWeight: 600 }}>
<p>{name}</p>
<Stack paddingLeft={2} size={4} horizontal>
<AttributeIcon key={src} type={src} />
<Text bold>{upperFirst(name)}</Text>
{/* <Curve fill={isFromDynamicZone ? '#AED4FB' : '#f3f4f4'} /> */}
</Stack>
</td>
<td>
{target ? (
<div>
<FormattedMessage
id={`${pluginId}.modelPage.attribute.${
isMorph ? 'relation-polymorphic' : 'relationWith'
}`}
/>
<Text>
{formatMessage({
id: getTrad(
`modelPage.attribute.${isMorph ? 'relation-polymorphic' : 'relationWith'}`
),
defaultMessage: 'Relation with',
})}
&nbsp;
<FormattedMessage id={`${pluginId}.from`}>
{msg => (
<span style={{ fontStyle: 'italic' }}>
<UpperFist content={contentTypeFriendlyName} />
&nbsp;
{plugin && `(${msg}: ${plugin})`}
</span>
)}
</FormattedMessage>
</div>
<span style={{ fontStyle: 'italic' }}>
<UpperFist content={contentTypeFriendlyName} />
&nbsp;
{plugin &&
`(${formatMessage({
id: getTrad(`from`),
defaultMessage: 'from',
})}: ${plugin})`}
</span>
</Text>
) : (
<>
<FormattedMessage id={`${pluginId}.attribute.${readableType}`} defaultMessage={type} />
<Text>
{formatMessage({
id: getTrad(`attribute.${readableType}`),
defaultMessage: type,
})}
&nbsp;
{repeatable && <FormattedMessage id={getTrad('component.repeatable')} />}
</>
{repeatable &&
formatMessage({
id: getTrad('component.repeatable'),
defaultMessage: '(repeatable)',
})}
</Text>
)}
</td>
<td className="button-container">
<td>
{isInDevelopmentMode && (
<>
<Row justifyContent="flex-end">
{configurable ? (
<>
{!isMorph ? (
<IconLinks
links={[
{
icon: <FontAwesomeIcon icon="pencil-alt" />,
onClick: () => handleClick(),
},
{
icon: <FontAwesomeIcon icon="trash-alt" />,
onClick: e => {
e.stopPropagation();
removeAttribute(
editTarget,
name,
secondLoopComponentUid || firstLoopComponentUid || ''
);
},
},
]}
/>
) : (
<IconLinks
links={[
{
icon: <FontAwesomeIcon icon="trash-alt" />,
onClick: e => {
e.stopPropagation();
removeAttribute(
editTarget,
name,
secondLoopComponentUid || firstLoopComponentUid || ''
);
},
},
]}
<Stack horizontal size={1}>
{!isMorph && (
<IconButton
onClick={handleClick}
label={formatMessage({ id: 'app.utils.edit', formatMessage: 'Edit' })}
noBorder
icon={<EditIcon />}
/>
)}
</>
<IconButton
onClick={e => {
e.stopPropagation();
removeAttribute(
editTarget,
name,
secondLoopComponentUid || firstLoopComponentUid || ''
);
}}
label={formatMessage({ id: 'app.utils.delete', defaultMessage: 'Delete' })}
noBorder
icon={<DeleteIcon />}
/>
</Stack>
) : (
<button type="button">
<FontAwesomeIcon icon="lock" />
</button>
// ! TODO ASK DESIGN TO PUT LOCK ICON INSIDE DS
<FontAwesomeIcon icon="lock" />
)}
</>
</Row>
)}
</td>
</Wrapper>
</BoxWrapper>
);
}

View File

@ -1,16 +1,14 @@
import React, { useEffect, useState } from 'react';
import { useTracking } from '@strapi/helper-plugin';
import { AddIcon, BackIcon, CheckIcon, EditIcon } from '@strapi/icons';
import { Box, Button, ContentLayout, HeaderLayout, Link, Row, Stack } from '@strapi/parts';
import { Button, ContentLayout, HeaderLayout, Link, Row, Stack } from '@strapi/parts';
import { get, has, isEqual, upperFirst } from 'lodash';
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { Prompt, useHistory, useLocation } from 'react-router-dom';
import List from '../../components/List';
import ListRow from '../../components/ListRow';
import ListViewContext from '../../contexts/ListViewContext';
import useDataManager from '../../hooks/useDataManager';
import pluginId from '../../pluginId';
import getAttributeDisplayedType from '../../utils/getAttributeDisplayedType';
import getTrad from '../../utils/getTrad';
import makeSearch from '../../utils/makeSearch';
@ -152,7 +150,7 @@ const ListView = () => {
return (
description ||
formatMessage({
id: `${pluginId}.modelPage.contentHeader.emptyDescription.description`,
id: getTrad('modelPage.contentHeader.emptyDescription.description'),
})
);
};
@ -202,20 +200,6 @@ const ListView = () => {
});
};
const CustomRow = props => {
const { name } = props;
return <ListRow {...props} attributeName={name} name={name} onClick={handleClickEditField} />;
};
CustomRow.defaultProps = {
name: null,
};
CustomRow.propTypes = {
name: PropTypes.string,
};
return (
<ListViewContext.Provider value={{ openModalAddField: handleClickAddField }}>
<>
@ -298,23 +282,21 @@ const ListView = () => {
handleClickAddField(forTarget, targetUid, headerDisplayObject);
}}
>
{formatMessage({ id: `${pluginId}.button.attributes.add.another` })}
{formatMessage({ id: getTrad('button.attributes.add.another') })}
</Button>
</Stack>
</Row>
<Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
<List
items={attributes}
customRowComponent={props => <CustomRow {...props} />}
addComponentToDZ={handleClickAddComponentToDZ}
targetUid={targetUid}
dataType={forTarget}
dataTypeName={currentDataName}
mainTypeName={currentDataName}
editTarget={forTarget}
isMain
/>
</Box>
<List
items={attributes}
customRowComponent={props => <ListRow {...props} onClick={handleClickEditField} />}
addComponentToDZ={handleClickAddComponentToDZ}
targetUid={targetUid}
dataType={forTarget}
dataTypeName={currentDataName}
mainTypeName={currentDataName}
editTarget={forTarget}
isMain
/>
</Stack>
</ContentLayout>
</>

View File

@ -182,5 +182,7 @@
"relation.oneToOne": "has and belongs to one",
"relation.oneWay": "has one",
"table.attributes.title.plural": "{number} fields",
"table.attributes.title.singular": "{number} field"
"table.attributes.title.singular": "{number} field",
"table.headers.name": "Name",
"table.headers.type": "Type"
}