mirror of
https://github.com/strapi/strapi.git
synced 2025-08-12 10:48:12 +00:00
Reskin and refacto the left admin menu and add Single type section
Signed-off-by: HichamELBSI <elabbassih@gmail.com>
This commit is contained in:
parent
de9521ce6b
commit
4f5cd5a209
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { PropTypes } from 'prop-types';
|
import { PropTypes } from 'prop-types';
|
||||||
import LeftMenuLink from '../LeftMenuLink';
|
import LeftMenuLink from '../LeftMenuLink';
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
@ -13,7 +14,8 @@ import messages from './messages.json';
|
|||||||
|
|
||||||
defineMessages(messages);
|
defineMessages(messages);
|
||||||
|
|
||||||
function LeftMenuFooter({ version, ...rest }) {
|
const LeftMenuFooter = ({ version }) => {
|
||||||
|
const location = useLocation();
|
||||||
const staticLinks = [
|
const staticLinks = [
|
||||||
{
|
{
|
||||||
icon: 'book',
|
icon: 'book',
|
||||||
@ -32,15 +34,20 @@ function LeftMenuFooter({ version, ...rest }) {
|
|||||||
<ul className="list">
|
<ul className="list">
|
||||||
{staticLinks.map(link => (
|
{staticLinks.map(link => (
|
||||||
<LeftMenuLink
|
<LeftMenuLink
|
||||||
{...rest}
|
location={location}
|
||||||
{...link}
|
iconName={link.icon}
|
||||||
label={messages[link.label].id}
|
label={messages[link.label].id}
|
||||||
key={link.label}
|
key={link.label}
|
||||||
|
destination={link.destination}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<div className="poweredBy">
|
<div className="poweredBy">
|
||||||
<FormattedMessage {...messages.poweredBy} key="poweredBy" />
|
<FormattedMessage
|
||||||
|
id={messages.poweredBy.id}
|
||||||
|
defaultMessage={messages.poweredBy.defaultMessage}
|
||||||
|
key="poweredBy"
|
||||||
|
/>
|
||||||
<a
|
<a
|
||||||
key="website"
|
key="website"
|
||||||
href="https://strapi.io"
|
href="https://strapi.io"
|
||||||
@ -61,7 +68,7 @@ function LeftMenuFooter({ version, ...rest }) {
|
|||||||
</div>
|
</div>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
LeftMenuFooter.propTypes = {
|
LeftMenuFooter.propTypes = {
|
||||||
version: PropTypes.string.isRequired,
|
version: PropTypes.string.isRequired,
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* LeftMenuHeader
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
|
|
||||||
function LeftMenuHeader() {
|
const LeftMenuHeader = () => (
|
||||||
return (
|
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Link to="/" className="leftMenuHeaderLink">
|
<Link to="/" className="leftMenuHeaderLink">
|
||||||
<span className="projectName" />
|
<span className="projectName" />
|
||||||
</Link>
|
</Link>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
export default LeftMenuHeader;
|
export default LeftMenuHeader;
|
||||||
|
39
packages/strapi-admin/admin/src/components/LeftMenuLink/A.js
Normal file
39
packages/strapi-admin/admin/src/components/LeftMenuLink/A.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const A = styled.a`
|
||||||
|
position: relative;
|
||||||
|
padding-top: 0.8rem;
|
||||||
|
padding-bottom: 0.2rem;
|
||||||
|
padding-left: 1.6rem;
|
||||||
|
min-height: 3.6rem;
|
||||||
|
border-left: 0.3rem solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: ${props => props.theme.main.colors.leftMenu['link-color']};
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${props => props.theme.main.colors.white};
|
||||||
|
background: ${props => props.theme.main.colors.leftMenu['link-hover']};
|
||||||
|
|
||||||
|
border-left: 0.3rem solid ${props => props.theme.main.colors.strapi.blue};
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: ${props => props.theme.main.colors.white};
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: ${props => props.theme.main.colors.leftMenu['link-color']};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.linkActive {
|
||||||
|
color: white !important;
|
||||||
|
border-left: 0.3rem solid ${props => props.theme.main.colors.strapi.blue};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default A;
|
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
|
const FaIcon = styled(({ small, ...props }) => <FontAwesomeIcon {...props} />)`
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 0.9rem + 0.5rem);
|
||||||
|
left: 1.6rem;
|
||||||
|
margin-right: 1.2rem;
|
||||||
|
font-size: ${props => (props.small ? '1rem' : '1.4rem')};
|
||||||
|
width: 1.4rem;
|
||||||
|
padding-bottom: 0.2rem;
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LeftMenuIcon = ({ icon }) => (
|
||||||
|
<FaIcon small={icon === 'circle'} icon={icon} />
|
||||||
|
);
|
||||||
|
|
||||||
|
LeftMenuIcon.propTypes = {
|
||||||
|
icon: PropTypes.string,
|
||||||
|
};
|
||||||
|
LeftMenuIcon.defaultProps = {
|
||||||
|
icon: 'circle',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LeftMenuIcon;
|
@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* LeftMenuLink
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { startsWith } from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Link, withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import en from '../../translations/en.json';
|
||||||
|
import LeftMenuIcon from './LeftMenuIcon';
|
||||||
|
import A from './A';
|
||||||
|
|
||||||
|
const LinkLabel = styled.span`
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 1rem;
|
||||||
|
padding-left: 2.6rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LeftMenuLinkContent = ({
|
||||||
|
destination,
|
||||||
|
iconName,
|
||||||
|
label,
|
||||||
|
location,
|
||||||
|
source,
|
||||||
|
suffixUrlToReplaceForLeftMenuHighlight,
|
||||||
|
}) => {
|
||||||
|
const isLinkActive = startsWith(
|
||||||
|
location.pathname.replace('/admin', '').concat('/'),
|
||||||
|
|
||||||
|
destination.replace(suffixUrlToReplaceForLeftMenuHighlight, '').concat('/')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if messageId exists in en locale to prevent warning messages
|
||||||
|
const content = en[label] ? (
|
||||||
|
<FormattedMessage
|
||||||
|
id={label}
|
||||||
|
defaultMessage="{label}"
|
||||||
|
values={{
|
||||||
|
label: `${label}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{message => <LinkLabel>{message}</LinkLabel>}
|
||||||
|
</FormattedMessage>
|
||||||
|
) : (
|
||||||
|
<LinkLabel>{label}</LinkLabel>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create external or internal link.
|
||||||
|
return destination.includes('http') ? (
|
||||||
|
<A
|
||||||
|
className={isLinkActive ? 'linkActive' : ''}
|
||||||
|
href={destination}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<LeftMenuIcon icon={iconName} />
|
||||||
|
{content}
|
||||||
|
</A>
|
||||||
|
) : (
|
||||||
|
<A
|
||||||
|
as={Link}
|
||||||
|
className={isLinkActive ? 'linkActive' : ''}
|
||||||
|
to={{
|
||||||
|
pathname: destination,
|
||||||
|
search: source ? `?source=${source}` : '',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LeftMenuIcon icon={iconName} />
|
||||||
|
{content}
|
||||||
|
</A>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
LeftMenuLinkContent.propTypes = {
|
||||||
|
destination: PropTypes.string.isRequired,
|
||||||
|
iconName: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
location: PropTypes.shape({
|
||||||
|
pathname: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
source: PropTypes.string,
|
||||||
|
suffixUrlToReplaceForLeftMenuHighlight: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
LeftMenuLinkContent.defaultProps = {
|
||||||
|
source: '',
|
||||||
|
suffixUrlToReplaceForLeftMenuHighlight: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRouter(LeftMenuLinkContent);
|
@ -1,129 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const Li = styled.li`
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.dotted-link {
|
|
||||||
background: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
left: calc(100% - 4px);
|
|
||||||
display: inline-block;
|
|
||||||
width: auto;
|
|
||||||
height: 20px;
|
|
||||||
transition: right 1s ease-in-out;
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: inline-block;
|
|
||||||
overflow: hidden;
|
|
||||||
width: auto;
|
|
||||||
height: 20px;
|
|
||||||
padding: 0 14px 0 10px;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 20px;
|
|
||||||
background: #0097f7;
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: transform 0.3s ease-in-out;
|
|
||||||
white-space: pre;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateX(calc(-100% + 9px));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
position: relative;
|
|
||||||
padding-top: 0.8rem;
|
|
||||||
padding-bottom: 0.2rem;
|
|
||||||
padding-left: 1.6rem;
|
|
||||||
min-height: 3.6rem;
|
|
||||||
border-left: 0.3rem solid transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
color: ${props => props.theme.main.colors.leftMenu['link-color']};
|
|
||||||
text-decoration: none;
|
|
||||||
display: block;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: ${props => props.theme.main.colors.white};
|
|
||||||
background: ${props => props.theme.main.colors.leftMenu['link-hover']};
|
|
||||||
|
|
||||||
border-left: 0.3rem solid ${props => props.theme.main.colors.strapi.blue};
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
color: ${props => props.theme.main.colors.white};
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:visited {
|
|
||||||
color: ${props => props.theme.main.colors.leftMenu['link-color']};
|
|
||||||
}
|
|
||||||
span {
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
padding-right: 1rem;
|
|
||||||
padding-left: 2.6rem;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkActive {
|
|
||||||
color: white !important;
|
|
||||||
border-left: 0.3rem solid ${props => props.theme.main.colors.strapi.blue};
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkIcon {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(50% - 0.9rem + 0.5rem);
|
|
||||||
left: 1.6rem;
|
|
||||||
margin-right: 1.2rem;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
width: 1.4rem;
|
|
||||||
padding-bottom: 0.2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkLabel {
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
padding-right: 1rem;
|
|
||||||
padding-left: 2.6rem;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Li.defaultProps = {
|
|
||||||
theme: {
|
|
||||||
main: {
|
|
||||||
colors: {
|
|
||||||
leftMenu: {},
|
|
||||||
strapi: {},
|
|
||||||
},
|
|
||||||
sizes: {
|
|
||||||
header: {},
|
|
||||||
leftMenu: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Li.propTypes = {
|
|
||||||
theme: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Li;
|
|
@ -0,0 +1,33 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Plugin = styled.div`
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: calc(100% - 4px);
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
height: 20px;
|
||||||
|
transition: right 1s ease-in-out;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
width: auto;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 14px 0 10px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
background: #0097f7;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
white-space: pre;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateX(calc(-100% + 9px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Plugin;
|
@ -5,98 +5,59 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { startsWith, upperFirst } from 'lodash';
|
import { upperFirst } from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import en from '../../translations/en.json';
|
|
||||||
import Li from './Li';
|
|
||||||
|
|
||||||
/* eslint-disable */
|
import LeftMenuLinkContent from './LeftMenuLinkContent';
|
||||||
|
import Plugin from './Plugin';
|
||||||
function LeftMenuLink(props) {
|
|
||||||
const isLinkActive = startsWith(
|
|
||||||
props.location.pathname.replace('/admin', '').concat('/'),
|
|
||||||
|
|
||||||
props.destination
|
|
||||||
.replace(props.suffixUrlToReplaceForLeftMenuHighlight, '')
|
|
||||||
.concat('/')
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const LeftMenuLink = ({
|
||||||
|
destination,
|
||||||
|
iconName,
|
||||||
|
label,
|
||||||
|
location,
|
||||||
|
source,
|
||||||
|
suffixUrlToReplaceForLeftMenuHighlight,
|
||||||
|
}) => {
|
||||||
const plugin =
|
const plugin =
|
||||||
props.source !== 'content-manager' && props.source !== '' ? (
|
source !== 'content-manager' && source !== '' ? (
|
||||||
<div className="plugin">
|
<Plugin>
|
||||||
<span>{upperFirst(props.source.split('-').join(' '))}</span>
|
<span>{upperFirst(source.split('-').join(' '))}</span>
|
||||||
</div>
|
</Plugin>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if messageId exists in en locale to prevent warning messages
|
|
||||||
const content = en[props.label] ? (
|
|
||||||
<FormattedMessage
|
|
||||||
id={props.label}
|
|
||||||
defaultMessage="{label}"
|
|
||||||
values={{
|
|
||||||
label: `${props.label}`,
|
|
||||||
}}
|
|
||||||
className="linkLabel"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<span className="linkLabel">{props.label}</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
// Icon.
|
|
||||||
|
|
||||||
const icon = <FontAwesomeIcon className={`linkIcon`} icon={props.icon} />;
|
|
||||||
|
|
||||||
// Create external or internal link.
|
|
||||||
const link = props.destination.includes('http') ? (
|
|
||||||
<a
|
|
||||||
className={`link ${isLinkActive ? 'linkActive' : ''}`}
|
|
||||||
href={props.destination}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
{content}
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<Link
|
|
||||||
className={`link ${isLinkActive ? 'linkActive' : ''}`}
|
|
||||||
to={{
|
|
||||||
pathname: props.destination,
|
|
||||||
search: props.source ? `?source=${props.source}` : '',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
{content}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Li>
|
<>
|
||||||
{link}
|
<LeftMenuLinkContent
|
||||||
{plugin}
|
destination={destination}
|
||||||
</Li>
|
iconName={iconName}
|
||||||
);
|
label={label}
|
||||||
|
location={location}
|
||||||
|
source={source}
|
||||||
|
suffixUrlToReplaceForLeftMenuHighlight={
|
||||||
|
suffixUrlToReplaceForLeftMenuHighlight
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
|
{plugin}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
LeftMenuLink.propTypes = {
|
LeftMenuLink.propTypes = {
|
||||||
destination: PropTypes.string.isRequired,
|
destination: PropTypes.string.isRequired,
|
||||||
icon: PropTypes.string.isRequired,
|
iconName: PropTypes.string,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
location: PropTypes.shape({
|
location: PropTypes.shape({
|
||||||
pathname: PropTypes.string,
|
pathname: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
pluginSuffixUrl: PropTypes.string,
|
|
||||||
source: PropTypes.string,
|
source: PropTypes.string,
|
||||||
suffixUrlToReplaceForLeftMenuHighlight: PropTypes.string,
|
suffixUrlToReplaceForLeftMenuHighlight: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
LeftMenuLink.defaultProps = {
|
LeftMenuLink.defaultProps = {
|
||||||
pluginSuffixUrl: '',
|
iconName: 'circle',
|
||||||
source: '',
|
source: '',
|
||||||
suffixUrlToReplaceForLeftMenuHighlight: '',
|
suffixUrlToReplaceForLeftMenuHighlight: '',
|
||||||
};
|
};
|
||||||
|
@ -1,27 +1,24 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* LeftMenuLinkContainer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { get, snakeCase, isEmpty } from 'lodash';
|
||||||
import { get, snakeCase, isEmpty, map, sortBy } from 'lodash';
|
|
||||||
import { SETTINGS_BASE_URL } from '../../config';
|
import { SETTINGS_BASE_URL } from '../../config';
|
||||||
import LeftMenuLink from '../LeftMenuLink';
|
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
import messages from './messages.json';
|
import messages from './messages.json';
|
||||||
|
|
||||||
/* eslint-disable */
|
import LeftMenuLinkSection from '../LeftMenuLinkSection';
|
||||||
|
|
||||||
function LeftMenuLinkContainer({ plugins, ...rest }) {
|
const LeftMenuLinkContainer = ({ plugins }) => {
|
||||||
// Generate the list of sections
|
const location = useLocation();
|
||||||
const pluginsSections = Object.keys(plugins).reduce((acc, current) => {
|
|
||||||
|
// Generate the list of content types sections
|
||||||
|
const contentTypesSections = Object.keys(plugins).reduce((acc, current) => {
|
||||||
plugins[current].leftMenuSections.forEach((section = {}) => {
|
plugins[current].leftMenuSections.forEach((section = {}) => {
|
||||||
if (!isEmpty(section.links)) {
|
if (!isEmpty(section.links)) {
|
||||||
acc[snakeCase(section.name)] = {
|
acc[snakeCase(section.name)] = {
|
||||||
name: section.name,
|
name: section.name,
|
||||||
|
searchable: true,
|
||||||
links: get(acc[snakeCase(section.name)], 'links', []).concat(
|
links: get(acc[snakeCase(section.name)], 'links', []).concat(
|
||||||
section.links
|
section.links
|
||||||
.filter(link => link.isDisplayed !== false)
|
.filter(link => link.isDisplayed !== false)
|
||||||
@ -40,70 +37,36 @@ function LeftMenuLinkContainer({ plugins, ...rest }) {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const linkSections = Object.keys(pluginsSections).map((current, j) => {
|
// Generate the list of plugin links
|
||||||
const contentTypes = pluginsSections[current].links;
|
const pluginsLinks = Object.values(plugins)
|
||||||
|
.filter(
|
||||||
return (
|
plugin =>
|
||||||
<div key={j}>
|
|
||||||
<p className="title">
|
|
||||||
<FormattedMessage {...messages.contentTypes}>
|
|
||||||
{title => title}
|
|
||||||
</FormattedMessage>
|
|
||||||
</p>
|
|
||||||
<ul className="list models-list">
|
|
||||||
{sortBy(contentTypes, 'label').map((link, i) => (
|
|
||||||
<LeftMenuLink
|
|
||||||
{...rest}
|
|
||||||
key={`${i}-${link.label}`}
|
|
||||||
icon={link.icon || 'circle'}
|
|
||||||
label={link.label}
|
|
||||||
destination={`/plugins/${link.plugin}/${link.destination ||
|
|
||||||
link.uid}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the plugins list is empty or not and display plugins by name
|
|
||||||
const pluginsLinks = !isEmpty(plugins) ? (
|
|
||||||
map(sortBy(plugins, 'name'), plugin => {
|
|
||||||
const shouldInjectPlugin = !!plugin.mainComponent;
|
|
||||||
|
|
||||||
if (
|
|
||||||
plugin.id !== 'email' &&
|
plugin.id !== 'email' &&
|
||||||
plugin.id !== 'content-manager' &&
|
plugin.id !== 'content-manager' &&
|
||||||
shouldInjectPlugin
|
!!plugin.mainComponent
|
||||||
) {
|
)
|
||||||
const pluginSuffixUrl = plugin.suffixUrl
|
.map(plugin => {
|
||||||
? plugin.suffixUrl(plugins)
|
const pluginSuffixUrl = plugin.suffixUrl ? plugin.suffixUrl(plugins) : '';
|
||||||
: '';
|
|
||||||
|
|
||||||
const destination = `/plugins/${get(plugin, 'id')}${pluginSuffixUrl}`;
|
return {
|
||||||
|
icon: get(plugin, 'icon') || 'plug',
|
||||||
|
label: get(plugin, 'name'),
|
||||||
|
destination: `/plugins/${get(plugin, 'id')}${pluginSuffixUrl}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
const menu = {
|
||||||
<LeftMenuLink
|
...contentTypesSections,
|
||||||
{...rest}
|
plugins: {
|
||||||
key={get(plugin, 'id')}
|
searchable: false,
|
||||||
icon={get(plugin, 'icon') || 'plug'}
|
name: 'plugins',
|
||||||
label={get(plugin, 'name')}
|
emptyLinksListMessage: messages.noPluginsInstalled.id,
|
||||||
destination={destination}
|
links: pluginsLinks,
|
||||||
pluginSuffixUrl={pluginSuffixUrl}
|
},
|
||||||
suffixUrlToReplaceForLeftMenuHighlight={
|
general: {
|
||||||
plugin.suffixUrlToReplaceForLeftMenuHighlight || ''
|
searchable: false,
|
||||||
}
|
name: 'general',
|
||||||
/>
|
links: [
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<li key="emptyList" className="noPluginsInstalled">
|
|
||||||
<FormattedMessage {...messages.noPluginsInstalled} key="noPlugins" />.
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
|
|
||||||
const staticLinks = [
|
|
||||||
{
|
{
|
||||||
icon: 'list',
|
icon: 'list',
|
||||||
label: messages.listPlugins.id,
|
label: messages.listPlugins.id,
|
||||||
@ -119,30 +82,25 @@ function LeftMenuLinkContainer({ plugins, ...rest }) {
|
|||||||
label: messages.settings.id,
|
label: messages.settings.id,
|
||||||
destination: SETTINGS_BASE_URL,
|
destination: SETTINGS_BASE_URL,
|
||||||
},
|
},
|
||||||
];
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{linkSections}
|
{Object.keys(menu).map(current => (
|
||||||
<div>
|
<LeftMenuLinkSection
|
||||||
<p className="title">
|
key={current}
|
||||||
<FormattedMessage {...messages.plugins} />
|
links={menu[current].links}
|
||||||
</p>
|
section={current}
|
||||||
<ul className="list">{pluginsLinks}</ul>
|
location={location}
|
||||||
</div>
|
searchable={menu[current].searchable}
|
||||||
<div>
|
emptyLinksListMessage={menu[current].emptyLinksListMessage}
|
||||||
<p className="title">
|
/>
|
||||||
<FormattedMessage {...messages.general} />
|
|
||||||
</p>
|
|
||||||
<ul className="list">
|
|
||||||
{staticLinks.map(link => (
|
|
||||||
<LeftMenuLink {...rest} key={link.destination} {...link} />
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
LeftMenuLinkContainer.propTypes = {
|
LeftMenuLinkContainer.propTypes = {
|
||||||
plugins: PropTypes.object.isRequired,
|
plugins: PropTypes.object.isRequired,
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
"id": "app.components.LeftMenuLinkContainer.contentTypes",
|
"id": "app.components.LeftMenuLinkContainer.contentTypes",
|
||||||
"defaultMessage": "Collection Types"
|
"defaultMessage": "Collection Types"
|
||||||
},
|
},
|
||||||
|
"singleTypes": {
|
||||||
|
"id": "app.components.LeftMenuLinkContainer.singleTypes",
|
||||||
|
"defaultMessage": "Single Types"
|
||||||
|
},
|
||||||
"listPlugins": {
|
"listPlugins": {
|
||||||
"id": "app.components.LeftMenuLinkContainer.listPlugins",
|
"id": "app.components.LeftMenuLinkContainer.listPlugins",
|
||||||
"defaultMessage": "Plugins"
|
"defaultMessage": "Plugins"
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Search = styled.input`
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 21px;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: ${props => props.theme.main.colors.white};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Search;
|
@ -0,0 +1,92 @@
|
|||||||
|
import React, { useState, createRef, useEffect } from 'react';
|
||||||
|
import { camelCase } from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
|
import messages from '../LeftMenuLinkContainer/messages.json';
|
||||||
|
import Search from './Search';
|
||||||
|
|
||||||
|
const Title = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-left: 2rem;
|
||||||
|
padding-right: 1.6rem;
|
||||||
|
padding-top: 0.7rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
color: ${props => props.theme.main.colors.leftMenu['title-color']};
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
`;
|
||||||
|
const SearchButton = styled.button`
|
||||||
|
padding: 0 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LeftMenuLinkHeader = ({ section, searchable, setSearch, search }) => {
|
||||||
|
const [showSearch, setShowSearch] = useState(false);
|
||||||
|
const ref = createRef();
|
||||||
|
const { id, defaultMessage } = messages[camelCase(section)];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showSearch && ref.current) {
|
||||||
|
ref.current.focus();
|
||||||
|
}
|
||||||
|
}, [ref, showSearch]);
|
||||||
|
|
||||||
|
const toggleSearch = () => {
|
||||||
|
setShowSearch(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = ({ target: { value } }) => {
|
||||||
|
setSearch(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSearch = () => {
|
||||||
|
setSearch('');
|
||||||
|
setShowSearch(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return !showSearch ? (
|
||||||
|
<Title>
|
||||||
|
<FormattedMessage id={id} defaultMessage={defaultMessage} />
|
||||||
|
{searchable && (
|
||||||
|
<SearchButton onClick={toggleSearch}>
|
||||||
|
<FontAwesomeIcon icon="search" />
|
||||||
|
</SearchButton>
|
||||||
|
)}
|
||||||
|
</Title>
|
||||||
|
) : (
|
||||||
|
<Title>
|
||||||
|
<div>
|
||||||
|
<FontAwesomeIcon icon="search" />
|
||||||
|
</div>
|
||||||
|
<Search
|
||||||
|
ref={ref}
|
||||||
|
onChange={handleChange}
|
||||||
|
value={search}
|
||||||
|
placeholder="search…"
|
||||||
|
/>
|
||||||
|
<SearchButton onClick={clearSearch}>
|
||||||
|
<FontAwesomeIcon icon="times" />
|
||||||
|
</SearchButton>
|
||||||
|
</Title>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
LeftMenuLinkHeader.propTypes = {
|
||||||
|
section: PropTypes.string.isRequired,
|
||||||
|
searchable: PropTypes.bool,
|
||||||
|
setSearch: PropTypes.func,
|
||||||
|
search: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
LeftMenuLinkHeader.defaultProps = {
|
||||||
|
search: null,
|
||||||
|
searchable: false,
|
||||||
|
setSearch: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LeftMenuLinkHeader;
|
@ -0,0 +1,94 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import matchSorter from 'match-sorter';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { sortBy } from 'lodash';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import LeftMenuLink from '../LeftMenuLink';
|
||||||
|
import LeftMenuLinkHeader from '../LeftMenuLinkHeader';
|
||||||
|
|
||||||
|
const LeftMenuListLink = styled.div`
|
||||||
|
max-height: 180px;
|
||||||
|
overflow: auto;
|
||||||
|
`;
|
||||||
|
const EmptyLinksList = styled.div`
|
||||||
|
color: ${props => props.theme.main.colors.white};
|
||||||
|
padding-left: 1.6rem;
|
||||||
|
padding-right: 1.6rem;
|
||||||
|
font-weight: 300;
|
||||||
|
min-height: 3.6rem;
|
||||||
|
padding-top: 0.2rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LeftMenuLinksSection = ({
|
||||||
|
section,
|
||||||
|
searchable,
|
||||||
|
location,
|
||||||
|
links,
|
||||||
|
emptyLinksListMessage,
|
||||||
|
}) => {
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
|
const filteredList = sortBy(
|
||||||
|
matchSorter(links, search, {
|
||||||
|
keys: ['label'],
|
||||||
|
}),
|
||||||
|
'label'
|
||||||
|
);
|
||||||
|
|
||||||
|
const getLinkDestination = link => {
|
||||||
|
return ['plugins', 'general'].includes(section)
|
||||||
|
? link.destination
|
||||||
|
: `/plugins/${link.plugin}/${link.destination || link.uid}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<LeftMenuLinkHeader
|
||||||
|
section={section}
|
||||||
|
searchable={searchable}
|
||||||
|
setSearch={setSearch}
|
||||||
|
search={search}
|
||||||
|
/>
|
||||||
|
<LeftMenuListLink>
|
||||||
|
{filteredList.length > 0 ? (
|
||||||
|
filteredList.map((link, index) => (
|
||||||
|
<LeftMenuLink
|
||||||
|
location={location}
|
||||||
|
// There is no id or unique value in the link object for the moment.
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
key={index}
|
||||||
|
iconName={link.icon}
|
||||||
|
label={link.label}
|
||||||
|
destination={getLinkDestination(link)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<EmptyLinksList>
|
||||||
|
<FormattedMessage
|
||||||
|
id={emptyLinksListMessage}
|
||||||
|
defaultMessage="No plugins installed yet"
|
||||||
|
/>
|
||||||
|
</EmptyLinksList>
|
||||||
|
)}
|
||||||
|
</LeftMenuListLink>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
LeftMenuLinksSection.propTypes = {
|
||||||
|
section: PropTypes.string.isRequired,
|
||||||
|
searchable: PropTypes.bool.isRequired,
|
||||||
|
location: PropTypes.shape({
|
||||||
|
pathname: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
emptyLinksListMessage: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
LeftMenuLinksSection.defaultProps = {
|
||||||
|
emptyLinksListMessage: 'components.ListRow.empty',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LeftMenuLinksSection;
|
@ -5,20 +5,23 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withRouter } from 'react-router-dom';
|
import PropTypes from 'prop-types';
|
||||||
import LeftMenuHeader from '../../components/LeftMenuHeader';
|
import LeftMenuHeader from '../../components/LeftMenuHeader';
|
||||||
import LeftMenuLinkContainer from '../../components/LeftMenuLinkContainer';
|
import LeftMenuLinkContainer from '../../components/LeftMenuLinkContainer';
|
||||||
import LeftMenuFooter from '../../components/LeftMenuFooter';
|
import LeftMenuFooter from '../../components/LeftMenuFooter';
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
|
|
||||||
function LeftMenu(props) {
|
const LeftMenu = ({ version, plugins }) => (
|
||||||
return (
|
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<LeftMenuHeader key="header" {...props} />
|
<LeftMenuHeader />
|
||||||
<LeftMenuLinkContainer {...props} />
|
<LeftMenuLinkContainer plugins={plugins} />
|
||||||
<LeftMenuFooter key="footer" {...props} />
|
<LeftMenuFooter key="footer" version={version} />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
export default withRouter(LeftMenu);
|
LeftMenu.propTypes = {
|
||||||
|
version: PropTypes.string.isRequired,
|
||||||
|
plugins: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LeftMenu;
|
||||||
|
@ -77,6 +77,7 @@
|
|||||||
"app.components.LeftMenuLinkContainer.installNewPlugin": "Marketplace",
|
"app.components.LeftMenuLinkContainer.installNewPlugin": "Marketplace",
|
||||||
"app.components.LeftMenuLinkContainer.listPlugins": "Plugins",
|
"app.components.LeftMenuLinkContainer.listPlugins": "Plugins",
|
||||||
"app.components.LeftMenuLinkContainer.contentTypes": "Collection Types",
|
"app.components.LeftMenuLinkContainer.contentTypes": "Collection Types",
|
||||||
|
"app.components.LeftMenuLinkContainer.singleTypes": "Single Types",
|
||||||
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "No plugins installed yet",
|
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "No plugins installed yet",
|
||||||
"app.components.LeftMenuLinkContainer.plugins": "Plugins",
|
"app.components.LeftMenuLinkContainer.plugins": "Plugins",
|
||||||
"app.components.LeftMenuLinkContainer.settings": "Settings",
|
"app.components.LeftMenuLinkContainer.settings": "Settings",
|
||||||
|
@ -21,11 +21,18 @@ const Initializer = ({ updatePlugin }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await request(requestURL, { method: 'GET' });
|
const { data } = await request(requestURL, { method: 'GET' });
|
||||||
|
|
||||||
const menu = [
|
const menu = [
|
||||||
{
|
{
|
||||||
name: 'Content Types',
|
name: 'Content Types',
|
||||||
links: data,
|
links: data.filter(
|
||||||
|
contentType => contentType.schema.kind === 'collectionType'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Single Types',
|
||||||
|
links: data.filter(
|
||||||
|
contentType => contentType.schema.kind === 'singleType'
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user