mirror of
https://github.com/strapi/strapi.git
synced 2026-01-06 12:13:52 +00:00
Design lang page
This commit is contained in:
parent
9614a7afe7
commit
4f1b2973fc
1
examples/getstarted/config/locales/af.json
Normal file
1
examples/getstarted/config/locales/af.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -22,16 +22,16 @@ window.strapi = Object.assign(window.strapi || {}, {
|
||||
module.exports = {
|
||||
'strapi-plugin-users-permissions': require('../../../strapi-plugin-users-permissions/admin/src')
|
||||
.default,
|
||||
'strapi-plugin-content-manager': require('../../../strapi-plugin-content-manager/admin/src')
|
||||
.default,
|
||||
// 'strapi-plugin-content-manager': require('../../../strapi-plugin-content-manager/admin/src')
|
||||
// .default,
|
||||
'strapi-plugin-content-type-builder': require('../../../strapi-plugin-content-type-builder/admin/src')
|
||||
.default,
|
||||
'strapi-plugin-documentation': require('../../../strapi-plugin-documentation/admin/src')
|
||||
.default,
|
||||
// 'strapi-plugin-documentation': require('../../../strapi-plugin-documentation/admin/src')
|
||||
// .default,
|
||||
'strapi-plugin-settings-manager': require('../../../strapi-plugin-settings-manager/admin/src')
|
||||
.default,
|
||||
'strapi-plugin-email': require('../../../strapi-plugin-email/admin/src')
|
||||
.default,
|
||||
'strapi-plugin-upload': require('../../../strapi-plugin-upload/admin/src')
|
||||
.default,
|
||||
// 'strapi-plugin-email': require('../../../strapi-plugin-email/admin/src')
|
||||
// .default,
|
||||
// 'strapi-plugin-upload': require('../../../strapi-plugin-upload/admin/src')
|
||||
// .default,
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { sizes } from 'strapi-helper-plugin';
|
||||
import sizes from '../../assets/styles/sizes';
|
||||
|
||||
const StyledViewContainer = styled.div`
|
||||
min-height: calc(100vh - ${sizes.header.height});
|
||||
@ -93,6 +93,7 @@ export { default as PluginHeader } from './components/PluginHeader';
|
||||
export { default as PopUpWarning } from './components/PopUpWarning';
|
||||
export { default as StyledLeftMenu } from './components/StyledLeftMenu';
|
||||
export { default as TrashButton } from './components/TrashButton';
|
||||
export { default as ViewContainer } from './components/ViewContainer';
|
||||
|
||||
// Utils
|
||||
export { default as auth } from './utils/auth';
|
||||
|
||||
@ -7,10 +7,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import StyledViewContainer from './StyledViewContainer';
|
||||
// import StyledViewContainer from './StyledViewContainer';
|
||||
import LeftMenu from '../LeftMenu';
|
||||
|
||||
import { PluginHeader, getQueryParameters } from 'strapi-helper-plugin';
|
||||
import {
|
||||
PluginHeader,
|
||||
getQueryParameters,
|
||||
ViewContainer as StyledViewContainer,
|
||||
} from 'strapi-helper-plugin';
|
||||
|
||||
function ViewContainer({
|
||||
children,
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
/**
|
||||
*
|
||||
* ListRow
|
||||
*
|
||||
*/
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { colors } from 'strapi-helper-plugin';
|
||||
|
||||
const ListRow = 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) {
|
||||
width: 25rem;
|
||||
p {
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
&.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default ListRow;
|
||||
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
const MenuSection = ({ name, items }) => {
|
||||
return (
|
||||
<section>
|
||||
<h3>
|
||||
<FormattedMessage id={`${pluginId}.${name}`} />
|
||||
</h3>
|
||||
<ul className="menu-list">
|
||||
{items.map(link => {
|
||||
return (
|
||||
<li key={link.slug}>
|
||||
<NavLink to={`/plugins/${pluginId}/${link.slug}`}>
|
||||
<p>
|
||||
<i className={`fa fa-${link.icon}`} />
|
||||
<FormattedMessage id={`${pluginId}.${link.name}`} />
|
||||
</p>
|
||||
</NavLink>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
MenuSection.propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default MenuSection;
|
||||
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
const Action = ({ isActive }) => {
|
||||
const trad = isActive ? 'default' : 'set';
|
||||
const tradId = `${pluginId}.list.languages.${trad}.languages`;
|
||||
const color = isActive ? '#49515a' : '#1c5de7';
|
||||
const fontStyle = isActive ? 'italic' : '';
|
||||
|
||||
return (
|
||||
<FormattedMessage id={tradId}>
|
||||
{msg => <span style={{ fontStyle, color }}>{msg}</span>}
|
||||
</FormattedMessage>
|
||||
);
|
||||
};
|
||||
|
||||
Action.propTypes = {
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default Action;
|
||||
@ -0,0 +1,7 @@
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
const Flags = createGlobalStyle`
|
||||
@import url('https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.3.0/css/flag-icon.css');
|
||||
`;
|
||||
|
||||
export default Flags;
|
||||
@ -0,0 +1,155 @@
|
||||
import React, { useCallback, useEffect, useReducer } from 'react';
|
||||
// import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { get } from 'lodash';
|
||||
import {
|
||||
LoadingIndicatorPage,
|
||||
PluginHeader,
|
||||
List,
|
||||
ListHeader,
|
||||
ListTitle,
|
||||
ListWrapper,
|
||||
} from 'strapi-helper-plugin';
|
||||
import pluginId from '../../pluginId';
|
||||
import useFetch from '../../hooks/useFetch';
|
||||
import ListRow from '../../components/ListRow';
|
||||
import getFlag, { formatLanguageLocale } from '../../utils/getFlag';
|
||||
import filterLanguages from './utils/filterLanguages';
|
||||
import reducer, { initialState } from './reducer';
|
||||
import Action from './Action';
|
||||
import Flags from './Flags';
|
||||
|
||||
const getTrad = key => `${pluginId}.${key}`;
|
||||
|
||||
const LanguagePage = () => {
|
||||
const [reducerState, dispatch] = useReducer(reducer, initialState);
|
||||
const { modifiedData, allLanguages } = reducerState.toJS();
|
||||
const { data, isLoading } = useFetch(['languages', 'i18n']);
|
||||
const findLangTrad = useCallback(
|
||||
lang => {
|
||||
const trad = get(allLanguages, [
|
||||
'sections',
|
||||
'0',
|
||||
'items',
|
||||
'0',
|
||||
'items',
|
||||
]).find(obj => obj.value === formatLanguageLocale(lang).join('_'));
|
||||
|
||||
return trad;
|
||||
},
|
||||
[allLanguages]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
const [{ languages }, result] = data;
|
||||
|
||||
dispatch({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
languages,
|
||||
allLanguages: result,
|
||||
availableLanguages: filterLanguages(languages, result),
|
||||
});
|
||||
}
|
||||
|
||||
return () => {};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isLoading]);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
// return null;
|
||||
const buttonProps = {
|
||||
kind: 'secondaryHotlineAdd',
|
||||
label: getTrad('list.languages.button.label'),
|
||||
};
|
||||
const availableLanguagesLength = modifiedData.length;
|
||||
const listTitleSuffix = availableLanguagesLength > 1 ? 'plural' : 'singular';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flags />
|
||||
<PluginHeader
|
||||
description={{ id: getTrad('form.language.description') }}
|
||||
title={{ id: getTrad('form.language.name') }}
|
||||
actions={[
|
||||
{
|
||||
label: `${pluginId}.form.button.cancel`,
|
||||
|
||||
kind: 'secondary',
|
||||
type: 'button',
|
||||
},
|
||||
{
|
||||
label: `${pluginId}.form.button.save`,
|
||||
|
||||
kind: 'primary',
|
||||
type: 'submit',
|
||||
id: 'saveData',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<ListWrapper>
|
||||
<ListHeader
|
||||
button={{ ...buttonProps, style: { right: '15px', top: '18px' } }}
|
||||
style={{ paddingBottom: '1rem' }}
|
||||
>
|
||||
<div className="list-header-title">
|
||||
<FormattedMessage
|
||||
id={getTrad(`list.languages.title.${listTitleSuffix}`)}
|
||||
>
|
||||
{title => (
|
||||
<ListTitle>
|
||||
{availableLanguagesLength} {title}
|
||||
</ListTitle>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
</ListHeader>
|
||||
<List>
|
||||
<table>
|
||||
<tbody>
|
||||
{modifiedData.map(lang => {
|
||||
const langArray = formatLanguageLocale(lang.name);
|
||||
const flag = getFlag(langArray);
|
||||
|
||||
return (
|
||||
<ListRow key={lang.name} className="clickable">
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id={getTrad(findLangTrad(lang.name).name)}
|
||||
>
|
||||
{msg => (
|
||||
<p style={{ fontWeight: 500 }}>
|
||||
<span
|
||||
className={`flag-icon flag-icon-${flag}`}
|
||||
style={{ marginRight: 39 }}
|
||||
/>
|
||||
{msg}
|
||||
</p>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
</td>
|
||||
<td>{lang.name}</td>
|
||||
<td>
|
||||
<Action isActive={lang.active} />
|
||||
</td>
|
||||
<td>
|
||||
<button type="button">
|
||||
<i className="fa fa-trash link-icon" />
|
||||
</button>
|
||||
</td>
|
||||
</ListRow>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</List>
|
||||
</ListWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguagePage;
|
||||
@ -0,0 +1,34 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import { get } from 'lodash';
|
||||
|
||||
const initialState = fromJS({
|
||||
availableLanguages: {},
|
||||
allLanguages: {},
|
||||
initialData: [],
|
||||
modifiedData: [],
|
||||
selectOptions: [],
|
||||
});
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'GET_DATA_SUCCEEDED':
|
||||
return state
|
||||
.update('allLanguages', () => fromJS(action.allLanguages))
|
||||
.update('modifiedData', () => fromJS(action.languages))
|
||||
.update('initialData', () => fromJS(action.languages))
|
||||
.update('selectOptions', () =>
|
||||
fromJS(
|
||||
get(
|
||||
action.availableLanguages,
|
||||
['sections', '0', 'items', '0', 'items'],
|
||||
[]
|
||||
)
|
||||
)
|
||||
);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
||||
export { initialState };
|
||||
@ -0,0 +1,22 @@
|
||||
import { cloneDeep, get, set } from 'lodash';
|
||||
|
||||
const filterLanguages = (appLanguages, availableLanguages) => {
|
||||
const ret = cloneDeep(availableLanguages);
|
||||
const filteredLanguages = get(
|
||||
ret,
|
||||
['sections', '0', 'items', '0', 'items'],
|
||||
[]
|
||||
).filter(lang => {
|
||||
const i = appLanguages.findIndex(obj => {
|
||||
return obj.name === lang.value.toLowerCase();
|
||||
});
|
||||
|
||||
return i === -1;
|
||||
});
|
||||
|
||||
set(ret, ['sections', '0', 'items', '0', 'items'], filteredLanguages);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
export default filterLanguages;
|
||||
@ -0,0 +1,110 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { get } from 'lodash';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
LoadingIndicatorPage,
|
||||
request,
|
||||
StyledLeftMenu,
|
||||
ViewContainer,
|
||||
} from 'strapi-helper-plugin';
|
||||
import pluginId from '../../pluginId';
|
||||
import MenuSection from '../../components/MenuSection';
|
||||
import LanguagePage from '../LanguagePage';
|
||||
/* eslint-disable */
|
||||
const Main = ({
|
||||
global: { currentEnvironment },
|
||||
history: { push },
|
||||
location: { pathname },
|
||||
}) => {
|
||||
const [{ menuSections, environments, isLoading }, setState] = useState({
|
||||
menuSections: [],
|
||||
environments: [],
|
||||
isLoading: true,
|
||||
});
|
||||
const abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
|
||||
useEffect(() => {
|
||||
const getData = async () => {
|
||||
try {
|
||||
const endPoints = ['menu', 'configurations/environments'];
|
||||
const [
|
||||
{ sections: menuSections },
|
||||
{ environments },
|
||||
] = await Promise.all(
|
||||
endPoints.map(endPoint =>
|
||||
request(`/${pluginId}/${endPoint}`, { method: 'GET', signal })
|
||||
)
|
||||
);
|
||||
|
||||
// Redirect to first link item
|
||||
if (pathname.split('/').length === 3) {
|
||||
const firstLink = get(
|
||||
menuSections,
|
||||
[0, 'items', 0, 'slug'],
|
||||
'application'
|
||||
);
|
||||
|
||||
push(`/plugins/${pluginId}/${firstLink}`);
|
||||
}
|
||||
|
||||
setState({ menuSections, environments, isLoading: false });
|
||||
} catch (err) {
|
||||
strapi.notification.error(`${pluginId}.strapi.notification.error`);
|
||||
}
|
||||
};
|
||||
|
||||
getData();
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
// console.log({ menuSections });
|
||||
|
||||
return (
|
||||
<ViewContainer>
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<StyledLeftMenu className="col-3">
|
||||
{menuSections.map(section => (
|
||||
<MenuSection key={section.name} {...section} />
|
||||
))}
|
||||
</StyledLeftMenu>
|
||||
<div className="col-9">
|
||||
<div className="components-container">
|
||||
<Switch>
|
||||
<Route
|
||||
path={`/plugins/${pluginId}/languages`}
|
||||
component={LanguagePage}
|
||||
exact
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ViewContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Main.propTypes = {
|
||||
global: PropTypes.shape({
|
||||
currentEnvironment: PropTypes.string.isRequired,
|
||||
}),
|
||||
history: PropTypes.shape({
|
||||
push: PropTypes.func.isRequired,
|
||||
}),
|
||||
location: PropTypes.shape({
|
||||
pathname: PropTypes.string.isRequired,
|
||||
}),
|
||||
};
|
||||
|
||||
export default Main;
|
||||
@ -0,0 +1,38 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { request } from 'strapi-helper-plugin';
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
const useFetch = endPoints => {
|
||||
const abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
const [state, setState] = useState({ data: {}, isLoading: true });
|
||||
|
||||
useEffect(() => {
|
||||
const getData = async () => {
|
||||
try {
|
||||
const data = await Promise.all(
|
||||
endPoints.map(endPoint =>
|
||||
request(`/${pluginId}/configurations/${endPoint}`, {
|
||||
method: 'GET',
|
||||
signal,
|
||||
})
|
||||
)
|
||||
);
|
||||
setState({ data, isLoading: false });
|
||||
} catch (err) {
|
||||
strapi.notification.error(`${pluginId}.strapi.notification.error`);
|
||||
}
|
||||
};
|
||||
|
||||
getData();
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return { data: state.data, isLoading: state.isLoading };
|
||||
};
|
||||
|
||||
export default useFetch;
|
||||
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import pluginPkg from '../../package.json';
|
||||
import pluginId from './pluginId';
|
||||
import App from './containers/App';
|
||||
// import App from './containers/App';
|
||||
import App from './containers/Main';
|
||||
import Initializer from './containers/Initializer';
|
||||
import lifecycles from './lifecycles';
|
||||
import trads from './translations';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user