Skip to content

Commit

Permalink
Implement reorderable layers by mouse (#281)
Browse files Browse the repository at this point in the history
  • Loading branch information
Morten Barklund committed Feb 17, 2020
1 parent bc44083 commit df1acb8
Show file tree
Hide file tree
Showing 11 changed files with 454 additions and 112 deletions.
22 changes: 22 additions & 0 deletions assets/src/edit-story/components/panels/layer/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* WordPress dependencies
*/
import { createContext } from '@wordpress/element';

export default createContext({ state: {}, actions: {} });
18 changes: 12 additions & 6 deletions assets/src/edit-story/components/panels/layer/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { rgba } from 'polished';
import StoryPropTypes from '../../../types';
import { getDefinitionForType } from '../../../elements';
import useLayerSelection from './useLayerSelection';
import useLayerReordering from './useLayerReordering';
import { LAYER_HEIGHT } from './constants';

const LayerButton = styled.button.attrs({
Expand Down Expand Up @@ -77,24 +78,29 @@ const LayerDescription = styled.div`
text-align: left;
`;

function Layer({ element }) {
const { LayerIcon, LayerContent } = getDefinitionForType(element.type);
const { isSelected, handleClick } = useLayerSelection(element);
function Layer({ layer }) {
const { LayerIcon, LayerContent } = getDefinitionForType(layer.type);
const { isSelected, handleClick } = useLayerSelection(layer);
const { handleStartReordering } = useLayerReordering(layer);

return (
<LayerButton isSelected={isSelected} onClick={handleClick}>
<LayerButton
isSelected={isSelected}
onClick={handleClick}
onPointerDown={handleStartReordering}
>
<LayerIconWrapper>
<LayerIcon />
</LayerIconWrapper>
<LayerDescription>
<LayerContent element={element} />
<LayerContent element={layer} />
</LayerDescription>
</LayerButton>
);
}

Layer.propTypes = {
element: StoryPropTypes.layer.isRequired,
layer: StoryPropTypes.layer.isRequired,
};

export default Layer;
80 changes: 80 additions & 0 deletions assets/src/edit-story/components/panels/layer/layerList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import styled from 'styled-components';

/**
* WordPress dependencies
*/
import { Fragment, useContext, useEffect } from '@wordpress/element';
import { speak } from '@wordpress/a11y';
import { sprintf, __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import Layer from './layer';
import LayerContext from './context';
import LayerSeparator from './separator';

const REORDER_MESSAGE = __(
/* translators: d: new layer position. */
'Reordering layers. Press Escape to abort. Release mouse to drop in position %d.',
'web-stories'
);

const LayerList = styled.div.attrs({ role: 'listbox' })`
display: flex;
flex-direction: column;
width: 100%;
align-items: stretch;
`;

function LayerPanel() {
const {
state: { layers, isReordering, currentSeparator },
} = useContext(LayerContext);

const numLayers = layers && layers.length;

useEffect(() => {
if (isReordering && currentSeparator) {
const position = numLayers - currentSeparator;
const message = sprintf(REORDER_MESSAGE, position);
speak(message, 'assertive');
}
}, [isReordering, currentSeparator, numLayers]);

if (!numLayers) {
return null;
}

return (
<LayerList>
{layers.map((layer) => (
<Fragment key={layer.id}>
{isReordering && <LayerSeparator position={layer.position + 1} />}
<Layer layer={layer} />
</Fragment>
))}
</LayerList>
);
}

export default LayerPanel;
25 changes: 5 additions & 20 deletions assets/src/edit-story/components/panels/layer/panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@
* limitations under the License.
*/

/**
* External dependencies
*/
import styled from 'styled-components';

/**
* WordPress dependencies
*/
Expand All @@ -29,30 +24,20 @@ import { __ } from '@wordpress/i18n';
*/
import { Panel, PanelTitle, PanelContent } from '../panel';
import { DEFAULT_LAYERS_VISIBLE, LAYER_HEIGHT } from './constants';
import Layer from './layer';
import useLayers from './useLayers';

const LayerList = styled.div.attrs({ role: 'listbox' })`
display: flex;
flex-direction: column;
width: 100%;
`;
import LayerList from './layerList';
import LayerProvider from './provider';

function LayerPanel() {
const layers = useLayers();

return (
<Panel name="layers" initialHeight={DEFAULT_LAYERS_VISIBLE * LAYER_HEIGHT}>
<PanelTitle isPrimary isResizable>
{__('Layers', 'web-stories')}
</PanelTitle>

<PanelContent isScrollable padding={'0'}>
<LayerList>
{layers.map((element) => (
<Layer key={element.id} element={element} />
))}
</LayerList>
<LayerProvider>
<LayerList />
</LayerProvider>
</PanelContent>
</Panel>
);
Expand Down
53 changes: 53 additions & 0 deletions assets/src/edit-story/components/panels/layer/provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import StoryPropTypes from '../../../types';
import Context from './context';
import useLayers from './useLayers';

function LayerProvider({ children }) {
const layers = useLayers();
const [isReordering, setIsReordering] = useState(false);
const [currentSeparator, setCurrentSeparator] = useState(null);

const state = {
state: {
layers,
isReordering,
currentSeparator,
},
actions: {
setIsReordering,
setCurrentSeparator,
},
};

return <Context.Provider value={state}>{children}</Context.Provider>;
}

LayerProvider.propTypes = {
children: StoryPropTypes.children,
};

export default LayerProvider;
72 changes: 72 additions & 0 deletions assets/src/edit-story/components/panels/layer/separator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import styled from 'styled-components';
import PropTypes from 'prop-types';

/**
* WordPress dependencies
*/
import { useContext, useCallback } from '@wordpress/element';

/**
* Internal dependencies
*/
import { LAYER_HEIGHT } from './constants';
import LayerContext from './context';

const Wrapper = styled.div`
height: ${LAYER_HEIGHT}px;
margin: -${LAYER_HEIGHT / 2}px 0;
padding: ${LAYER_HEIGHT / 2}px 0;
opacity: 0;
position: relative;
z-index: 1;
&:hover {
opacity: 1;
}
`;

const Line = styled.div`
height: 4px;
margin: 0 0 -4px;
background: ${({ theme }) => theme.colors.action};
width: 100%;
`;

function LayerSeparator({ position }) {
const {
actions: { setCurrentSeparator },
} = useContext(LayerContext);
const handlePointerEnter = useCallback(() => {
setCurrentSeparator(position);
}, [setCurrentSeparator, position]);
return (
<Wrapper onPointerEnter={handlePointerEnter}>
<Line />
</Wrapper>
);
}

LayerSeparator.propTypes = {
position: PropTypes.number.isRequired,
};

export default LayerSeparator;

0 comments on commit df1acb8

Please sign in to comment.