mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-03-16 22:10:28 +00:00
* Remove useless CSS declaration * Remove commented out styles * Merge duplicate declarations * Remove even more commented out code * Move footer mq to footer declaration * Remove more useless code * Stop vendor prefixing for IE below 11 * Remove useless vendor prefixing * Merge identical declarations * Fix 1px overflow in content wrapper * Fix explorer scrolling when open on mobile * Remove unused import * Add Redux performance measurements to explorer menu * Rewrite explorer reducer to avoid unnecessary operations * Stop changing reducer state on every action regardless of type * Remove redundant children.isFetching property in nodes reducer * Remove redundant children.isLoaded property in nodes reducer * Remove redundant children.isError property in nodes reducer * Refactor nodes explorer reducer with sub-reducer * Fix linting issue * Remove unused class name * Change default icon className from empty string to null * Remove old TODO comment * Hoist icons in ExplorerItem component for better performance * Add comment * Add tooling for performance measurement of React components * Clean up explorer panel component definition * Make performance measurements opt-in * Improve alignment of page explorer menu on mobile * Close explorer on touchend rather than touchstart * Comment out performance measurement code * Remove fade transition completely
This commit is contained in:
parent
ea8ab5de45
commit
61b6de2e4e
24 changed files with 278 additions and 211 deletions
|
|
@ -3,6 +3,7 @@ $c-explorer-bg-dark: $color-grey-1;
|
|||
$c-explorer-bg-active: rgba(0,0,0,0.425);
|
||||
$c-explorer-secondary: #a5a5a5;
|
||||
$c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
|
||||
$menu-footer-height: 50px;
|
||||
|
||||
@import 'ExplorerItem';
|
||||
|
||||
|
|
@ -92,7 +93,6 @@ $c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
|
|||
|
||||
.c-explorer__header {
|
||||
display: block;
|
||||
height: 50px;
|
||||
background-color: $c-explorer-bg-dark;
|
||||
border-bottom: 1px solid $c-explorer-bg-dark;
|
||||
color: $color-white;
|
||||
|
|
@ -114,7 +114,7 @@ $c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
|
|||
}
|
||||
|
||||
.c-explorer__header__inner {
|
||||
padding: 1rem;
|
||||
padding: 1em .75em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
|
@ -124,17 +124,24 @@ $c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
|
|||
margin-right: .25rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@include medium {
|
||||
padding: 1em 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.c-explorer__placeholder {
|
||||
padding: 1rem;
|
||||
padding: 1em;
|
||||
color: $color-white;
|
||||
|
||||
@include medium {
|
||||
padding: 1em 1.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.c-explorer__see-more {
|
||||
display: block;
|
||||
padding: 1rem;
|
||||
height: 50px;
|
||||
padding: 1em;
|
||||
background: rgba(0,0,0,0.3);
|
||||
color: $color-white;
|
||||
|
||||
|
|
@ -152,4 +159,9 @@ $c-explorer-easing: cubic-bezier(0.075, 0.820, 0.165, 1.000);
|
|||
@include hover {
|
||||
background: $c-explorer-bg-active;
|
||||
}
|
||||
|
||||
@include medium {
|
||||
padding: 1em 1.75em;
|
||||
height: $menu-footer-height;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,23 @@ import Icon from '../../components/Icon/Icon';
|
|||
import Button from '../../components/Button/Button';
|
||||
import PublicationStatus from '../../components/PublicationStatus/PublicationStatus';
|
||||
|
||||
// Hoist icons in the explorer item, as it is re-rendered many times.
|
||||
const childrenIcon = (
|
||||
<Icon name="folder-inverse" />
|
||||
);
|
||||
|
||||
const editIcon = (
|
||||
<Icon name="edit" title={STRINGS.EDIT} />
|
||||
);
|
||||
|
||||
const nextIcon = (
|
||||
<Icon name="arrow-right" title={STRINGS.SEE_CHILDREN} />
|
||||
);
|
||||
|
||||
/**
|
||||
* One menu item in the page explorer, with different available actions
|
||||
* and information depending on the metadata of the page.
|
||||
*/
|
||||
const ExplorerItem = ({ item, onClick }) => {
|
||||
const { id, title, meta } = item;
|
||||
const hasChildren = meta.children.count > 0;
|
||||
|
|
@ -14,9 +31,7 @@ const ExplorerItem = ({ item, onClick }) => {
|
|||
return (
|
||||
<div className="c-explorer__item">
|
||||
<Button href={`${ADMIN_URLS.PAGES}${id}/`} className="c-explorer__item__link">
|
||||
{hasChildren ? (
|
||||
<Icon name="folder-inverse" className={'c-explorer__children'} />
|
||||
) : null}
|
||||
{hasChildren ? childrenIcon : null}
|
||||
|
||||
<h3 className="c-explorer__item__title">
|
||||
{title}
|
||||
|
|
@ -32,14 +47,14 @@ const ExplorerItem = ({ item, onClick }) => {
|
|||
href={`${ADMIN_URLS.PAGES}${id}/edit/`}
|
||||
className="c-explorer__item__action c-explorer__item__action--small"
|
||||
>
|
||||
<Icon name="edit" title={`${STRINGS.EDIT} '${title}'`} />
|
||||
{editIcon}
|
||||
</Button>
|
||||
{hasChildren ? (
|
||||
<Button
|
||||
className="c-explorer__item__action"
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon name="arrow-right" title={STRINGS.SEE_CHILDREN} />
|
||||
{nextIcon}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
padding: 1.45em 1.75em;
|
||||
padding: 1.45em 1em;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
|
|
@ -25,6 +25,10 @@
|
|||
@include hover {
|
||||
background: $c-explorer-bg-active;
|
||||
}
|
||||
|
||||
@include medium {
|
||||
padding: 1.45em 1.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.c-explorer__item__link .icon {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,16 @@ import { STRINGS, MAX_EXPLORER_PAGES } from '../../config/wagtailConfig';
|
|||
|
||||
import Button from '../Button/Button';
|
||||
import LoadingSpinner from '../LoadingSpinner/LoadingSpinner';
|
||||
import Transition, { PUSH, POP, FADE } from '../Transition/Transition';
|
||||
import Transition, { PUSH, POP } from '../Transition/Transition';
|
||||
import ExplorerHeader from './ExplorerHeader';
|
||||
import ExplorerItem from './ExplorerItem';
|
||||
import PageCount from './PageCount';
|
||||
|
||||
export default class ExplorerPanel extends React.Component {
|
||||
/**
|
||||
* The main panel of the page explorer menu, with heading,
|
||||
* menu items, and special states.
|
||||
*/
|
||||
class ExplorerPanel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
|
@ -38,14 +42,14 @@ export default class ExplorerPanel extends React.Component {
|
|||
document.querySelector('[data-explorer-menu-item]').classList.add('submenu-active');
|
||||
document.body.classList.add('explorer-open');
|
||||
document.addEventListener('mousedown', this.clickOutside);
|
||||
document.addEventListener('touchstart', this.clickOutside);
|
||||
document.addEventListener('touchend', this.clickOutside);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.querySelector('[data-explorer-menu-item]').classList.remove('submenu-active');
|
||||
document.body.classList.remove('explorer-open');
|
||||
document.removeEventListener('mousedown', this.clickOutside);
|
||||
document.removeEventListener('touchstart', this.clickOutside);
|
||||
document.removeEventListener('touchend', this.clickOutside);
|
||||
}
|
||||
|
||||
clickOutside(e) {
|
||||
|
|
@ -136,7 +140,10 @@ export default class ExplorerPanel extends React.Component {
|
|||
tag="nav"
|
||||
className="explorer"
|
||||
paused={paused || !page || page.isFetching}
|
||||
focusTrapOptions={{ onDeactivate: onClose }}
|
||||
focusTrapOptions={{
|
||||
initialFocus: '.c-explorer__close',
|
||||
onDeactivate: onClose,
|
||||
}}
|
||||
>
|
||||
<Button className="c-explorer__close u-hidden" onClick={onClose}>
|
||||
{STRINGS.CLOSE_EXPLORER}
|
||||
|
|
@ -163,14 +170,17 @@ export default class ExplorerPanel extends React.Component {
|
|||
|
||||
ExplorerPanel.propTypes = {
|
||||
nodes: PropTypes.object.isRequired,
|
||||
path: PropTypes.array,
|
||||
path: PropTypes.array.isRequired,
|
||||
page: PropTypes.shape({
|
||||
isFetching: PropTypes.bool,
|
||||
children: PropTypes.shape({
|
||||
count: PropTypes.number,
|
||||
items: PropTypes.array,
|
||||
}),
|
||||
}),
|
||||
}).isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
popPage: PropTypes.func.isRequired,
|
||||
pushPage: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ExplorerPanel;
|
||||
|
|
|
|||
|
|
@ -46,12 +46,10 @@ exports[`Explorer visible 1`] = `
|
|||
"1": Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"isFetching": true,
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": false,
|
||||
"isFetching": true,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
@ -101,12 +99,10 @@ exports[`Explorer visible 2`] = `
|
|||
"1": Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"isFetching": true,
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": false,
|
||||
"isFetching": true,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
@ -118,12 +114,10 @@ exports[`Explorer visible 2`] = `
|
|||
Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"isFetching": true,
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": false,
|
||||
"isFetching": true,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ exports[`ExplorerHeader #depth at root 1`] = `
|
|||
className="c-explorer__header__inner"
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="home"
|
||||
title={null}
|
||||
/>
|
||||
|
|
@ -41,7 +41,7 @@ exports[`ExplorerHeader #page 1`] = `
|
|||
className="c-explorer__header__inner"
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="arrow-left"
|
||||
title={null}
|
||||
/>
|
||||
|
|
@ -67,7 +67,7 @@ exports[`ExplorerHeader basic 1`] = `
|
|||
className="c-explorer__header__inner"
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="arrow-left"
|
||||
title={null}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ exports[`ExplorerItem children 1`] = `
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className="c-explorer__children"
|
||||
className={null}
|
||||
name="folder-inverse"
|
||||
title={null}
|
||||
/>
|
||||
|
|
@ -36,9 +36,9 @@ exports[`ExplorerItem children 1`] = `
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="edit"
|
||||
title="Edit 'test'"
|
||||
title="Edit"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
|
|
@ -52,7 +52,7 @@ exports[`ExplorerItem children 1`] = `
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="arrow-right"
|
||||
title="See children"
|
||||
/>
|
||||
|
|
@ -91,9 +91,9 @@ exports[`ExplorerItem renders 1`] = `
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="edit"
|
||||
title="Edit 'test'"
|
||||
title="Edit"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -114,7 +114,7 @@ exports[`ExplorerItem should show a publication status if not live 1`] = `
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className="c-explorer__children"
|
||||
className={null}
|
||||
name="folder-inverse"
|
||||
title={null}
|
||||
/>
|
||||
|
|
@ -148,9 +148,9 @@ exports[`ExplorerItem should show a publication status if not live 1`] = `
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="edit"
|
||||
title="Edit 'test'"
|
||||
title="Edit"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
|
|
@ -164,7 +164,7 @@ exports[`ExplorerItem should show a publication status if not live 1`] = `
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="arrow-right"
|
||||
title="See children"
|
||||
/>
|
||||
|
|
@ -187,7 +187,7 @@ exports[`ExplorerItem should show a publication status with unpublished changes
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className="c-explorer__children"
|
||||
className={null}
|
||||
name="folder-inverse"
|
||||
title={null}
|
||||
/>
|
||||
|
|
@ -221,9 +221,9 @@ exports[`ExplorerItem should show a publication status with unpublished changes
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="edit"
|
||||
title="Edit 'test'"
|
||||
title="Edit"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
|
|
@ -237,7 +237,7 @@ exports[`ExplorerItem should show a publication status with unpublished changes
|
|||
target={null}
|
||||
>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="arrow-right"
|
||||
title="See children"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ exports[`ExplorerPanel #isError 1`] = `
|
|||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__close",
|
||||
"onDeactivate": [Function],
|
||||
}
|
||||
}
|
||||
|
|
@ -78,6 +79,7 @@ exports[`ExplorerPanel #isFetching 1`] = `
|
|||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__close",
|
||||
"onDeactivate": [Function],
|
||||
}
|
||||
}
|
||||
|
|
@ -139,6 +141,7 @@ exports[`ExplorerPanel #items 1`] = `
|
|||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__close",
|
||||
"onDeactivate": [Function],
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +227,7 @@ exports[`ExplorerPanel no children 1`] = `
|
|||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__close",
|
||||
"onDeactivate": [Function],
|
||||
}
|
||||
}
|
||||
|
|
@ -281,6 +285,7 @@ exports[`ExplorerPanel renders 1`] = `
|
|||
className="explorer"
|
||||
focusTrapOptions={
|
||||
Object {
|
||||
"initialFocus": ".c-explorer__close",
|
||||
"onDeactivate": [Function],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ exports[`PageCount #title 1`] = `
|
|||
5 pages
|
||||
</span>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="arrow-right"
|
||||
title={null}
|
||||
/>
|
||||
|
|
@ -29,7 +29,7 @@ exports[`PageCount plural 1`] = `
|
|||
5 pages
|
||||
</span>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="arrow-right"
|
||||
title={null}
|
||||
/>
|
||||
|
|
@ -47,7 +47,7 @@ exports[`PageCount works 1`] = `
|
|||
1 page
|
||||
</span>
|
||||
<Icon
|
||||
className=""
|
||||
className={null}
|
||||
name="arrow-right"
|
||||
title={null}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export function pushPage(id) {
|
|||
|
||||
dispatch(pushPagePrivate(id));
|
||||
|
||||
if (page && !page.children.isFetching && !page.children.isLoaded) {
|
||||
if (page && !page.isFetching && !page.children.count > 0) {
|
||||
dispatch(getChildren(id));
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,10 +12,8 @@ const stubState = {
|
|||
},
|
||||
nodes: {
|
||||
5: {
|
||||
children: {
|
||||
isFetching: false,
|
||||
isLoaded: true,
|
||||
},
|
||||
isFetching: true,
|
||||
children: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -90,7 +88,7 @@ describe('actions', () => {
|
|||
|
||||
it('triggers getChildren', () => {
|
||||
const stub = Object.assign({}, stubState);
|
||||
stub.nodes[5].children.isLoaded = false;
|
||||
stub.nodes[5].isFetching = false;
|
||||
const store = mockStore(stub);
|
||||
store.dispatch(actions.pushPage(5));
|
||||
expect(store.getActions()).toMatchSnapshot();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Provider } from 'react-redux';
|
|||
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
|
||||
// import { perfMiddleware } from '../../utils/performance';
|
||||
import Explorer from './Explorer';
|
||||
import ExplorerToggle from './ExplorerToggle';
|
||||
import explorer from './reducers/explorer';
|
||||
|
|
@ -22,6 +23,11 @@ const initExplorer = (explorerNode, toggleNode) => {
|
|||
thunkMiddleware,
|
||||
];
|
||||
|
||||
// Uncomment this to use performance measurements.
|
||||
// if (process.env.NODE_ENV !== 'production') {
|
||||
// middleware.push(perfMiddleware);
|
||||
// }
|
||||
|
||||
const store = createStore(rootReducer, {}, compose(
|
||||
applyMiddleware(...middleware),
|
||||
// Expose store to Redux DevTools extension.
|
||||
|
|
|
|||
|
|
@ -5,12 +5,10 @@ Object {
|
|||
"1": Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"isFetching": false,
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": true,
|
||||
"isFetching": false,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
@ -22,9 +20,14 @@ exports[`nodes GET_CHILDREN_START 1`] = `
|
|||
Object {
|
||||
"1": Object {
|
||||
"children": Object {
|
||||
"isFetching": true,
|
||||
"count": 0,
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": false,
|
||||
"isFetching": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
@ -34,9 +37,6 @@ Object {
|
|||
"1": Object {
|
||||
"children": Object {
|
||||
"count": 3,
|
||||
"isError": false,
|
||||
"isFetching": false,
|
||||
"isLoaded": true,
|
||||
"items": Array [
|
||||
3,
|
||||
4,
|
||||
|
|
@ -45,7 +45,6 @@ Object {
|
|||
},
|
||||
"isError": false,
|
||||
"isFetching": false,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
@ -53,13 +52,11 @@ Object {
|
|||
"3": Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"isFetching": false,
|
||||
"items": Array [],
|
||||
},
|
||||
"id": 3,
|
||||
"isError": false,
|
||||
"isFetching": false,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
@ -67,13 +64,11 @@ Object {
|
|||
"4": Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"isFetching": false,
|
||||
"items": Array [],
|
||||
},
|
||||
"id": 4,
|
||||
"isError": false,
|
||||
"isFetching": false,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
@ -81,13 +76,11 @@ Object {
|
|||
"5": Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"isFetching": false,
|
||||
"items": Array [],
|
||||
},
|
||||
"id": 5,
|
||||
"isError": false,
|
||||
"isFetching": false,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
@ -100,12 +93,10 @@ Object {
|
|||
"1": Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"isFetching": false,
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": true,
|
||||
"isFetching": false,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
@ -116,7 +107,15 @@ Object {
|
|||
exports[`nodes GET_PAGE_SUCCESS 1`] = `
|
||||
Object {
|
||||
"1": Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": false,
|
||||
"isFetching": false,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
@ -126,12 +125,10 @@ Object {
|
|||
"1": Object {
|
||||
"children": Object {
|
||||
"count": 0,
|
||||
"isFetching": false,
|
||||
"items": Array [],
|
||||
},
|
||||
"isError": false,
|
||||
"isFetching": false,
|
||||
"isLoaded": true,
|
||||
"meta": Object {
|
||||
"children": Object {},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,31 +9,30 @@ const defaultState = {
|
|||
* - Whether the explorer is open or not.
|
||||
*/
|
||||
export default function explorer(prevState = defaultState, { type, payload }) {
|
||||
const state = Object.assign({}, prevState);
|
||||
|
||||
switch (type) {
|
||||
case 'OPEN_EXPLORER':
|
||||
// Provide a starting page when opening the explorer.
|
||||
state.path = [payload.id];
|
||||
state.isVisible = true;
|
||||
break;
|
||||
return {
|
||||
isVisible: true,
|
||||
path: [payload.id],
|
||||
};
|
||||
|
||||
case 'CLOSE_EXPLORER':
|
||||
state.path = [];
|
||||
state.isVisible = false;
|
||||
break;
|
||||
return defaultState;
|
||||
|
||||
case 'PUSH_PAGE':
|
||||
state.path = state.path.concat([payload.id]);
|
||||
break;
|
||||
return {
|
||||
isVisible: prevState.isVisible,
|
||||
path: prevState.path.concat([payload.id]),
|
||||
};
|
||||
|
||||
case 'POP_PAGE':
|
||||
state.path = state.path.slice(0, -1);
|
||||
break;
|
||||
return {
|
||||
isVisible: prevState.isVisible,
|
||||
path: prevState.path.slice(0, -1),
|
||||
};
|
||||
|
||||
default:
|
||||
break;
|
||||
return prevState;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,69 +1,86 @@
|
|||
const defaultState = {};
|
||||
|
||||
const defaultPageState = {
|
||||
isFetching: false,
|
||||
isLoaded: true,
|
||||
isError: false,
|
||||
children: {
|
||||
items: [],
|
||||
count: 0,
|
||||
isFetching: false,
|
||||
},
|
||||
meta: {
|
||||
children: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default function nodes(prevState = defaultState, { type, payload }) {
|
||||
const state = Object.assign({}, prevState);
|
||||
|
||||
/**
|
||||
* A single page node in the explorer.
|
||||
*/
|
||||
const node = (state = defaultPageState, { type, payload }) => {
|
||||
switch (type) {
|
||||
case 'OPEN_EXPLORER':
|
||||
state[payload.id] = Object.assign({}, defaultPageState, state[payload.id]);
|
||||
break;
|
||||
return state || defaultPageState;
|
||||
|
||||
case 'GET_PAGE_SUCCESS':
|
||||
state[payload.id] = Object.assign({}, state[payload.id], payload.data);
|
||||
state[payload.id].isError = false;
|
||||
break;
|
||||
|
||||
case 'GET_CHILDREN_START':
|
||||
state[payload.id] = Object.assign({}, state[payload.id]);
|
||||
state[payload.id].isFetching = true;
|
||||
state[payload.id].children = Object.assign({}, state[payload.id].children);
|
||||
state[payload.id].children.isFetching = true;
|
||||
break;
|
||||
|
||||
case 'GET_CHILDREN_SUCCESS':
|
||||
state[payload.id] = Object.assign({}, state[payload.id]);
|
||||
state[payload.id].isFetching = false;
|
||||
state[payload.id].isError = false;
|
||||
state[payload.id].children = Object.assign({}, state[payload.id].children, {
|
||||
items: state[payload.id].children.items.slice(),
|
||||
count: payload.meta.total_count,
|
||||
isFetching: false,
|
||||
isLoaded: true,
|
||||
return Object.assign({}, state, payload.data, {
|
||||
isError: false,
|
||||
});
|
||||
|
||||
payload.items.forEach((item) => {
|
||||
state[item.id] = Object.assign({}, defaultPageState, state[item.id], item);
|
||||
|
||||
state[payload.id].children.items.push(item.id);
|
||||
case 'GET_CHILDREN_START':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: true,
|
||||
});
|
||||
|
||||
case 'GET_CHILDREN_SUCCESS':
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
isError: false,
|
||||
children: {
|
||||
items: state.children.items.slice().concat(payload.items.map(item => item.id)),
|
||||
count: payload.meta.total_count,
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case 'GET_PAGE_FAILURE':
|
||||
case 'GET_CHILDREN_FAILURE':
|
||||
state[payload.id] = Object.assign({}, state[payload.id]);
|
||||
state[payload.id].isFetching = false;
|
||||
state[payload.id].isError = true;
|
||||
state[payload.id].children.isFetching = false;
|
||||
break;
|
||||
return Object.assign({}, state, {
|
||||
isFetching: false,
|
||||
isError: true,
|
||||
});
|
||||
|
||||
default:
|
||||
break;
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
return state;
|
||||
const defaultState = {};
|
||||
|
||||
/**
|
||||
* Contains all of the page nodes in one object.
|
||||
*/
|
||||
export default function nodes(state = defaultState, { type, payload }) {
|
||||
switch (type) {
|
||||
|
||||
case 'OPEN_EXPLORER':
|
||||
case 'GET_PAGE_SUCCESS':
|
||||
case 'GET_CHILDREN_START':
|
||||
case 'GET_PAGE_FAILURE':
|
||||
case 'GET_CHILDREN_FAILURE':
|
||||
return Object.assign({}, state, {
|
||||
// Delegate logic to single-node reducer.
|
||||
[payload.id]: node(state[payload.id], { type, payload }),
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
case 'GET_CHILDREN_SUCCESS':
|
||||
const newState = Object.assign({}, state, {
|
||||
[payload.id]: node(state[payload.id], { type, payload }),
|
||||
});
|
||||
|
||||
payload.items.forEach((item) => {
|
||||
newState[item.id] = Object.assign({}, defaultPageState, item);
|
||||
});
|
||||
|
||||
return newState;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import React from 'react';
|
|||
*/
|
||||
const Icon = ({ name, className, title }) => (
|
||||
<span>
|
||||
<span className={`icon icon-${name} ${className}`} aria-hidden></span>
|
||||
<span className={`icon icon-${name} ${className || ''}`} aria-hidden></span>
|
||||
{title ? (
|
||||
<span className="visuallyhidden">
|
||||
{title}
|
||||
|
|
@ -23,7 +23,7 @@ Icon.propTypes = {
|
|||
};
|
||||
|
||||
Icon.defaultProps = {
|
||||
className: '',
|
||||
className: null,
|
||||
title: null,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ const TRANSITION_DURATION = 210;
|
|||
// The available transitions. Must match the class names in CSS.
|
||||
export const PUSH = 'push';
|
||||
export const POP = 'pop';
|
||||
export const FADE = 'fade';
|
||||
|
||||
/**
|
||||
* Wrapper arround react-transition-group with default values.
|
||||
|
|
@ -32,7 +31,7 @@ const Transition = ({
|
|||
);
|
||||
|
||||
Transition.propTypes = {
|
||||
name: PropTypes.oneOf([PUSH, POP, FADE]).isRequired,
|
||||
name: PropTypes.oneOf([PUSH, POP]).isRequired,
|
||||
component: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
duration: PropTypes.number,
|
||||
|
|
|
|||
|
|
@ -57,29 +57,3 @@ $c-transition-duration: 200ms;
|
|||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Fade transition
|
||||
// =============================================================================
|
||||
|
||||
.c-transition-fade-enter {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity $c-transition-duration ease $c-transition-duration;
|
||||
}
|
||||
|
||||
.c-transition-fade-enter-active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c-transition-fade-leave {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
transition: opacity $c-transition-duration ease;
|
||||
}
|
||||
|
||||
.c-transition-fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
|
|
|||
86
client/src/utils/performance.js
Normal file
86
client/src/utils/performance.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
/* eslint-disable import/no-mutable-exports */
|
||||
let perfMiddleware;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
/**
|
||||
* Performance middleware for use with a Redux store.
|
||||
* Will log the time taken by every action across all
|
||||
* of the reducers of the store.
|
||||
*/
|
||||
perfMiddleware = () => {
|
||||
/* eslint-disable no-console */
|
||||
// `next` is a function that takes an 'action' and sends it through to the 'reducers'.
|
||||
const middleware = (next) => (action) => {
|
||||
let result;
|
||||
|
||||
if (!!console.time) {
|
||||
console.time(action.type);
|
||||
result = next(action);
|
||||
console.timeEnd(action.type);
|
||||
} else {
|
||||
result = next(action);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return middleware;
|
||||
};
|
||||
}
|
||||
|
||||
let perfComponent;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
/**
|
||||
* Wraps the passed in `Component` in a higher-order component. It can then
|
||||
* measure the performance of every render of the `Component`.
|
||||
*
|
||||
* Can also be used as an ES2016 decorator.
|
||||
* @param {ReactComponent} Component the component to wrap
|
||||
* @return {ReactComponent} the wrapped component
|
||||
* See https://github.com/sheepsteak/react-perf-component
|
||||
*/
|
||||
perfComponent = (Target) => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return Target;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line global-require
|
||||
const ReactPerf = require('react-addons-perf');
|
||||
|
||||
class Perf extends Component {
|
||||
componentDidMount() {
|
||||
ReactPerf.start();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
ReactPerf.stop();
|
||||
|
||||
const measurements = ReactPerf.getLastMeasurements();
|
||||
|
||||
ReactPerf.printWasted(measurements);
|
||||
ReactPerf.start();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ReactPerf.stop();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Target {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
Perf.displayName = `perf(${Target.displayName || Target.name || 'Component'})`;
|
||||
Perf.WrappedComponent = Target;
|
||||
|
||||
return Perf;
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
perfMiddleware,
|
||||
perfComponent,
|
||||
};
|
||||
|
|
@ -33,7 +33,7 @@ gulp.task('styles:sass', function () {
|
|||
outputStyle: 'expanded'
|
||||
}).on('error', sass.logError))
|
||||
.pipe(autoprefixer({
|
||||
browsers: ['last 3 versions', 'not ie <= 8'],
|
||||
browsers: ['last 3 versions', 'ie 11'],
|
||||
cascade: false
|
||||
}))
|
||||
.pipe(gulp.dest(function(file) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
"imports-loader": "^0.7.1",
|
||||
"jest": "^20.0.4",
|
||||
"mustache": "^2.2.1",
|
||||
"react-addons-perf": "^15.4.2",
|
||||
"react-addons-test-utils": "^15.4.2",
|
||||
"react-test-renderer": "^15.5.4",
|
||||
"redux-mock-store": "^1.2.2",
|
||||
|
|
|
|||
|
|
@ -213,7 +213,6 @@ $footer-submenu: $submenu-color;
|
|||
body.nav-open {
|
||||
.wrapper {
|
||||
transform: translate3d($menu-width, 0, 0);
|
||||
-webkit-transform: translate3d($menu-width, 0, 0);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
|
|
@ -242,13 +241,8 @@ body.explorer-open {
|
|||
}
|
||||
|
||||
@media screen and (min-width: $breakpoint-mobile) {
|
||||
body.explorer-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wrapper,
|
||||
body.nav-open .wrapper {
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
padding-left: $menu-width;
|
||||
}
|
||||
|
|
@ -416,13 +410,14 @@ body.explorer-open {
|
|||
}
|
||||
|
||||
body.explorer-open {
|
||||
overflow: hidden;
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,11 +85,6 @@ body {
|
|||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
// See components/main-nav.scss
|
||||
}
|
||||
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
text-align: left;
|
||||
|
|
@ -112,6 +107,7 @@ body {
|
|||
}
|
||||
|
||||
.content-wrapper {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%; // this has no effect on desktop, but on mobile it helps aesthetics of menu popout action
|
||||
float: left;
|
||||
|
|
@ -198,30 +194,14 @@ footer {
|
|||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 90em) {
|
||||
width: 90em;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's not, for now...
|
||||
|
||||
// ::-webkit-scrollbar {
|
||||
// height: 10px;
|
||||
// width: 10px;
|
||||
// background: $color-grey-1;
|
||||
// }
|
||||
|
||||
// ::-webkit-scrollbar-thumb {
|
||||
// background: $color-grey-2;
|
||||
// -webkit-border-radius: 1ex;
|
||||
// }
|
||||
|
||||
// ::-webkit-scrollbar-corner {
|
||||
// background: $color-grey-1;
|
||||
// }
|
||||
|
||||
.breadcrumb {
|
||||
@include unlist();
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
@include clearfix();
|
||||
overflow: hidden;
|
||||
padding-top: 1.4em;
|
||||
|
|
@ -509,27 +489,3 @@ footer,
|
|||
z-index: 200;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 90em) {
|
||||
.wrapper {
|
||||
// width: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 90em;
|
||||
}
|
||||
}
|
||||
|
||||
// Transitions (resolution agnostic)
|
||||
.content-wrapper,
|
||||
.nav-main,
|
||||
.nav-toggle,
|
||||
footer,
|
||||
.logo {
|
||||
// @include transition(all 0.2s ease);
|
||||
}
|
||||
|
||||
// .nav-main a,
|
||||
// a {
|
||||
// @include transition(color 0.2s ease, background-color 0.2s ease);
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
IMAGES: '{% url "wagtailadmin_api_v1:images:listing" %}',
|
||||
{# // Use this to add an extra query string on all API requests. #}
|
||||
{# // Example value: '&order=-id' #}
|
||||
{# // TODO Hook it up to Django settings #}
|
||||
EXTRA_CHILDREN_PARAMETERS: '',
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue