Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement] extract layers list to a separate component #1665

Merged
merged 11 commits into from
Jan 12, 2022
143 changes: 12 additions & 131 deletions src/components/side-panel/layer-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,13 @@
// THE SOFTWARE.

import React, {Component, useCallback} from 'react';
import classnames from 'classnames';

import PropTypes from 'prop-types';
import {SortableContainer, SortableElement} from 'react-sortable-hoc';
import styled from 'styled-components';
import {createSelector} from 'reselect';
import {injectIntl} from 'react-intl';
import {FormattedMessage} from 'localization';
import {arrayMove} from 'utils/data-utils';

import LayerPanelFactory from './layer-panel/layer-panel';
import SourceDataCatalogFactory from './common/source-data-catalog';
import LayerListFactory from './layer-panel/layer-list';
import {Add} from 'components/common/icons';
import ItemSelector from 'components/common/item-selector/item-selector';
import {
Expand Down Expand Up @@ -72,33 +67,6 @@ const LayerBlendingSelector = ({layerBlending, updateLayerBlending, intl}) => {
);
};

// make sure the element is always visible while is being dragged
// item being dragged is appended in body, here to reset its global style
const SortableStyledItem = styled.div`
z-index: ${props => props.theme.dropdownWrapperZ + 1};

&.sorting {
pointer-events: none;
}

&.sorting-layers .layer-panel__header {
background-color: ${props => props.theme.panelBackgroundHover};
font-family: ${props => props.theme.fontFamily};
font-weight: ${props => props.theme.fontWeight};
font-size: ${props => props.theme.fontSize};
line-height: ${props => props.theme.lineHeight};
*,
*:before,
*:after {
box-sizing: border-box;
}
.layer__drag-handle {
opacity: 1;
color: ${props => props.theme.textColorHl};
}
}
`;

export function AddDataButtonFactory() {
const AddDataButton = ({onClick, isInactive}) => (
<Button
Expand All @@ -116,23 +84,9 @@ export function AddDataButtonFactory() {
return AddDataButton;
}

LayerManagerFactory.deps = [AddDataButtonFactory, LayerPanelFactory, SourceDataCatalogFactory];

function LayerManagerFactory(AddDataButton, LayerPanel, SourceDataCatalog) {
// By wrapping layer panel using a sortable element we don't have to implement the drag and drop logic into the panel itself;
// Developers can provide any layer panel implementation and it will still be sortable
const SortableItem = SortableElement(({children, isSorting}) => {
return (
<SortableStyledItem className={classnames('sortable-layer-items', {sorting: isSorting})}>
{children}
</SortableStyledItem>
);
});

const WrappedSortableContainer = SortableContainer(({children}) => {
return <div>{children}</div>;
});
LayerManagerFactory.deps = [AddDataButtonFactory, LayerListFactory, SourceDataCatalogFactory];

function LayerManagerFactory(AddDataButton, LayerList, SourceDataCatalog) {
class LayerManager extends Component {
static propTypes = {
datasets: PropTypes.object.isRequired,
Expand All @@ -145,47 +99,12 @@ function LayerManagerFactory(AddDataButton, LayerPanel, SourceDataCatalog) {
showDatasetTable: PropTypes.func.isRequired,
updateTableColor: PropTypes.func.isRequired
};
state = {
isSorting: false
};

layerClassSelector = props => props.layerClasses;
layerTypeOptionsSelector = createSelector(this.layerClassSelector, layerClasses =>
Object.keys(layerClasses).map(key => {
const layer = new layerClasses[key]();
return {
id: key,
label: layer.name,
icon: layer.layerIcon,
requireData: layer.requireData
};
})
);

_addEmptyNewLayer = () => {
const {visStateActions} = this.props;
visStateActions.addLayer();
};

_handleSort = ({oldIndex, newIndex}) => {
const {visStateActions} = this.props;
visStateActions.reorderLayer(arrayMove(this.props.layerOrder, oldIndex, newIndex));
this.setState({isSorting: false});
};

_onSortStart = () => {
this.setState({isSorting: true});
};

_updateBeforeSortStart = ({index}) => {
// if layer config is active, close it
const {layerOrder, layers, visStateActions} = this.props;
const layerIdx = layerOrder[index];
if (layers[layerIdx].config.isConfigActive) {
visStateActions.layerConfigChange(layers[layerIdx], {isConfigActive: false});
}
};

render() {
const {
layers,
Expand All @@ -199,26 +118,8 @@ function LayerManagerFactory(AddDataButton, LayerPanel, SourceDataCatalog) {
uiStateActions,
visStateActions
} = this.props;
const {toggleModal: openModal} = uiStateActions;
const defaultDataset = Object.keys(datasets)[0];
const layerTypeOptions = this.layerTypeOptionsSelector(this.props);

const layerActions = {
layerColorUIChange: visStateActions.layerColorUIChange,
layerConfigChange: visStateActions.layerConfigChange,
layerVisualChannelConfigChange: visStateActions.layerVisualChannelConfigChange,
layerTypeChange: visStateActions.layerTypeChange,
layerVisConfigChange: visStateActions.layerVisConfigChange,
layerTextLabelChange: visStateActions.layerTextLabelChange,
removeLayer: visStateActions.removeLayer,
duplicateLayer: visStateActions.duplicateLayer
};

const panelProps = {
datasets,
openModal,
layerTypeOptions
};
const defaultDataset = Object.keys(datasets)[0];

return (
<div className="layer-manager">
Expand All @@ -232,34 +133,14 @@ function LayerManagerFactory(AddDataButton, LayerPanel, SourceDataCatalog) {
<AddDataButton onClick={showAddDataModal} isInactive={!defaultDataset} />
<SidePanelDivider />
<SidePanelSection>
<WrappedSortableContainer
onSortEnd={this._handleSort}
onSortStart={this._onSortStart}
updateBeforeSortStart={this._updateBeforeSortStart}
lockAxis="y"
helperClass="sorting-layers"
useDragHandle
>
{layerOrder.map(
(layerIdx, index) =>
!layers[layerIdx].config.hidden && (
<SortableItem
key={`layer-${layerIdx}`}
index={index}
isSorting={this.state.isSorting}
>
<LayerPanel
{...panelProps}
{...layerActions}
sortData={layerIdx}
key={layers[layerIdx].id}
idx={layerIdx}
layer={layers[layerIdx]}
/>
</SortableItem>
)
)}
</WrappedSortableContainer>
<LayerList
layers={layers}
datasets={datasets}
layerOrder={layerOrder}
uiStateActions={uiStateActions}
visStateActions={visStateActions}
layerClasses={this.props.layerClasses}
/>
</SidePanelSection>
<SidePanelSection>
{defaultDataset ? (
Expand Down
19 changes: 19 additions & 0 deletions src/components/side-panel/layer-panel/layer-list.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import {Layer, LayerClassesType} from 'layers';
import {Datasets} from 'reducers/vis-state-updaters';
import * as VisStateActions from 'actions/vis-state-actions';
import * as UIStateActions from 'actions/ui-state-actions';

export type LayerListProps = {
datasets: Datasets;
layerClasses: LayerClassesType;
layers: Layer[];
layerOrder: number[];
uiStateActions: typeof UIStateActions;
visStateActions: typeof VisStateActions;
isSortable?: Boolean;
};

export default function LayerListFactory(
LayerPanel: React.Component
): React.Component<LayerListProps>;
179 changes: 179 additions & 0 deletions src/components/side-panel/layer-panel/layer-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import React, {useCallback, useMemo, useState} from 'react';

import {arrayMove} from 'utils/data-utils';
import styled from 'styled-components';
import classnames from 'classnames';
import {SortableContainer, SortableElement} from 'react-sortable-hoc';

import LayerPanelFactory from './layer-panel';

// make sure the element is always visible while is being dragged
// item being dragged is appended in body, here to reset its global style
const SortableStyledItem = styled.div`
z-index: ${props => props.theme.dropdownWrapperZ + 1};

&.sorting {
pointer-events: none;
}

&.sorting-layers .layer-panel__header {
background-color: ${props => props.theme.panelBackgroundHover};
font-family: ${props => props.theme.fontFamily};
font-weight: ${props => props.theme.fontWeight};
font-size: ${props => props.theme.fontSize};
line-height: ${props => props.theme.lineHeight};
*,
*:before,
*:after {
box-sizing: border-box;
}
.layer__drag-handle {
opacity: 1;
color: ${props => props.theme.textColorHl};
}
}
`;

LayerListFactory.deps = [LayerPanelFactory];

function LayerListFactory(LayerPanel) {
// By wrapping layer panel using a sortable element we don't have to implement the drag and drop logic into the panel itself;
// Developers can provide any layer panel implementation and it will still be sortable
const SortableItem = SortableElement(({children, isSorting}) => {
return (
<SortableStyledItem className={classnames('sortable-layer-items', {sorting: isSorting})}>
{children}
</SortableStyledItem>
);
});

const WrappedSortableContainer = SortableContainer(({children}) => {
return <div>{children}</div>;
});

const LayerList = props => {
const {
layers,
datasets,
layerOrder,
uiStateActions,
visStateActions,
layerClasses,
isSortable = true
} = props;
const {toggleModal: openModal} = uiStateActions;
const [isSorting, setIsSorting] = useState(false);

const layerTypeOptions = useMemo(
() =>
Object.keys(layerClasses).map(key => {
const layer = new layerClasses[key]();
return {
id: key,
label: layer.name,
icon: layer.layerIcon,
requireData: layer.requireData
};
}),
[layerClasses]
);

const layerActions = useMemo(() => {
hodoje marked this conversation as resolved.
Show resolved Hide resolved
return {
layerColorUIChange: visStateActions.layerColorUIChange,
layerConfigChange: visStateActions.layerConfigChange,
layerVisualChannelConfigChange: visStateActions.layerVisualChannelConfigChange,
layerTypeChange: visStateActions.layerTypeChange,
layerVisConfigChange: visStateActions.layerVisConfigChange,
layerTextLabelChange: visStateActions.layerTextLabelChange,
removeLayer: visStateActions.removeLayer,
duplicateLayer: visStateActions.duplicateLayer
};
}, [
visStateActions.layerColorUIChange,
visStateActions.layerConfigChange,
visStateActions.layerVisualChannelConfigChange,
visStateActions.layerTypeChange,
visStateActions.layerVisConfigChange,
visStateActions.layerTextLabelChange,
visStateActions.removeLayer,
visStateActions.duplicateLayer
]);

const panelProps = useMemo(() => {
hodoje marked this conversation as resolved.
Show resolved Hide resolved
return {
datasets,
openModal,
layerTypeOptions
};
}, [datasets, openModal, layerTypeOptions]);

const _handleSort = useCallback(
({oldIndex, newIndex}) => {
visStateActions.reorderLayer(arrayMove(props.layerOrder, oldIndex, newIndex));
setIsSorting(false);
},
[props.layerOrder, visStateActions]
);

const _onSortStart = useCallback(() => {
setIsSorting(true);
}, []);

const _updateBeforeSortStart = useCallback(() => {
({index}) => {
const layerIdx = layerOrder[index];
if (layers[layerIdx].config.isConfigActive) {
visStateActions.layerConfigChange(layers[layerIdx], {isConfigActive: false});
}
};
}, [layers, layerOrder, visStateActions]);

return isSortable ? (
<WrappedSortableContainer
onSortEnd={_handleSort}
onSortStart={_onSortStart}
updateBeforeSortStart={_updateBeforeSortStart}
lockAxis="y"
helperClass="sorting-layers"
useDragHandle
>
{layerOrder.map(
(layerIdx, index) =>
!layers[layerIdx].config.hidden && (
<SortableItem key={`layer-${layerIdx}`} index={index} isSorting={isSorting}>
<LayerPanel
{...panelProps}
{...layerActions}
sortData={layerIdx}
key={layers[layerIdx].id}
idx={layerIdx}
layer={layers[layerIdx]}
/>
</SortableItem>
)
)}
</WrappedSortableContainer>
) : (
<>
{layerOrder.map(
(layerIdx, index) =>
!layers[layerIdx].config.hidden && (
<LayerPanel
{...panelProps}
{...layerActions}
sortData={layerIdx}
key={layers[layerIdx].id}
idx={layerIdx}
layer={layers[layerIdx]}
/>
)
)}
</>
);
};

return LayerList;
}

export default LayerListFactory;
Loading