Skip to content

Commit e2fb6a1

Browse files
authored
Merge pull request #902 from plotly/moveable-folds
Add ability to reorder folds
2 parents 0eaef99 + f7505a6 commit e2fb6a1

17 files changed

+234
-16
lines changed

src/EditorControls.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,46 @@ class EditorControls extends Component {
289289
}
290290
break;
291291

292+
case EDITOR_ACTIONS.MOVE_TO:
293+
// checking if fromIndex and toIndex is a number because
294+
// gives errors if index is 0 (falsy value)
295+
if (payload.path && !isNaN(payload.fromIndex) && !isNaN(payload.toIndex)) {
296+
function move(container) {
297+
const movedEl = container[payload.fromIndex];
298+
const replacedEl = container[payload.toIndex];
299+
container[payload.toIndex] = movedEl;
300+
container[payload.fromIndex] = replacedEl;
301+
}
302+
303+
if (payload.path === 'data') {
304+
move(graphDiv.data);
305+
}
306+
307+
if (payload.path === 'layout.images') {
308+
move(graphDiv.layout.images);
309+
}
310+
311+
if (payload.path === 'layout.shapes') {
312+
move(graphDiv.layout.shapes);
313+
}
314+
315+
if (payload.path === 'layout.annotations') {
316+
move(graphDiv.layout.annotations);
317+
}
318+
319+
const updatedData = payload.path.startsWith('data')
320+
? graphDiv.data.slice()
321+
: graphDiv.data;
322+
const updatedLayout = payload.path.startsWith('layout')
323+
? Object.assign({}, graphDiv.layout)
324+
: graphDiv.layout;
325+
326+
if (this.props.onUpdate) {
327+
this.props.onUpdate(updatedData, updatedLayout, graphDiv._transitionData._frames);
328+
}
329+
}
330+
break;
331+
292332
default:
293333
throw new Error(this.localize('must specify an action type to handleEditorUpdate'));
294334
}

src/components/containers/AnnotationAccordion.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class AnnotationAccordion extends Component {
1313
layout: {annotations = [], meta = []},
1414
localize: _,
1515
} = this.context;
16-
const {canAdd, children} = this.props;
16+
const {canAdd, children, canReorder} = this.props;
1717

1818
const content =
1919
annotations.length &&
@@ -50,7 +50,7 @@ class AnnotationAccordion extends Component {
5050
};
5151

5252
return (
53-
<LayoutPanel addAction={canAdd ? addAction : null}>
53+
<LayoutPanel addAction={canAdd ? addAction : null} canReorder={canReorder}>
5454
{content ? (
5555
content
5656
) : (
@@ -76,6 +76,7 @@ AnnotationAccordion.contextTypes = {
7676
AnnotationAccordion.propTypes = {
7777
children: PropTypes.node,
7878
canAdd: PropTypes.bool,
79+
canReorder: PropTypes.bool,
7980
};
8081

8182
export default AnnotationAccordion;

src/components/containers/ImageAccordion.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class ImageAccordion extends Component {
1313
layout: {images = []},
1414
localize: _,
1515
} = this.context;
16-
const {canAdd, children} = this.props;
16+
const {canAdd, children, canReorder} = this.props;
1717

1818
const content =
1919
images.length &&
@@ -48,7 +48,7 @@ class ImageAccordion extends Component {
4848
};
4949

5050
return (
51-
<LayoutPanel addAction={canAdd ? addAction : null}>
51+
<LayoutPanel addAction={canAdd ? addAction : null} canReorder={canReorder}>
5252
{content ? (
5353
content
5454
) : (
@@ -74,6 +74,7 @@ ImageAccordion.contextTypes = {
7474
ImageAccordion.propTypes = {
7575
children: PropTypes.node,
7676
canAdd: PropTypes.bool,
77+
canReorder: PropTypes.bool,
7778
};
7879

7980
export default ImageAccordion;

src/components/containers/PlotlyFold.js

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class Fold extends Component {
2121
if (!this.foldVisible && !this.props.messageIfEmpty) {
2222
return null;
2323
}
24-
const {deleteContainer} = this.context;
24+
const {deleteContainer, moveContainer} = this.context;
2525
const {
2626
canDelete,
2727
children,
@@ -33,6 +33,8 @@ export class Fold extends Component {
3333
icon: Icon,
3434
messageIfEmpty,
3535
name,
36+
canMoveUp,
37+
canMoveDown,
3638
} = this.props;
3739

3840
const contentClass = classnames('fold__content', {
@@ -47,7 +49,7 @@ export class Fold extends Component {
4749
'fold__top__arrow--open': !folded,
4850
});
4951

50-
const arrowIcon = (
52+
const arrowDownIcon = (
5153
<div className={arrowClass}>
5254
<div className="fold__top__arrow__wrapper">
5355
<AngleDownIcon />
@@ -70,13 +72,50 @@ export class Fold extends Component {
7072
</div>
7173
) : null;
7274

75+
const movingControls = (canMoveDown || canMoveUp) && (
76+
<div className="fold__top__moving-controls">
77+
<span
78+
className={`fold__top__moving-controls--up${canMoveUp ? '' : '--disabled'}`}
79+
onClick={e => {
80+
// prevents fold toggle to happen when clicking on moving arrow controls
81+
e.stopPropagation();
82+
83+
if (canMoveUp) {
84+
if (!moveContainer || typeof moveContainer !== 'function') {
85+
throw new Error('moveContainer must be a function');
86+
}
87+
moveContainer('up');
88+
}
89+
}}
90+
>
91+
<AngleDownIcon />
92+
</span>
93+
<span
94+
className={`fold__top__moving-controls--down${canMoveDown ? '' : '--disabled'}`}
95+
onClick={e => {
96+
// prevents fold toggle to happen when clicking on moving arrow controls
97+
e.stopPropagation();
98+
if (canMoveDown) {
99+
if (!moveContainer || typeof moveContainer !== 'function') {
100+
throw new Error('moveContainer must be a function');
101+
}
102+
moveContainer('down');
103+
}
104+
}}
105+
>
106+
<AngleDownIcon />
107+
</span>
108+
</div>
109+
);
110+
73111
const foldHeader = !hideHeader && (
74112
<div className={headerClass} onClick={toggleFold}>
75113
<div className="fold__top__arrow-title">
76-
{arrowIcon}
114+
{arrowDownIcon}
77115
{icon}
78116
<div className="fold__top__title">{striptags(name)}</div>
79117
</div>
118+
{movingControls}
80119
{deleteButton}
81120
</div>
82121
);
@@ -118,6 +157,8 @@ Fold.propTypes = {
118157
icon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
119158
messageIfEmpty: PropTypes.string,
120159
name: PropTypes.string,
160+
canMoveUp: PropTypes.bool,
161+
canMoveDown: PropTypes.bool,
121162
};
122163

123164
Fold.contextTypes = {
@@ -175,6 +216,7 @@ PlotlyFold.plotly_editor_traits = {
175216
PlotlyFold.contextTypes = Object.assign(
176217
{
177218
deleteContainer: PropTypes.func,
219+
moveContainer: PropTypes.func,
178220
},
179221
containerConnectedContextTypes
180222
);

src/components/containers/PlotlyPanel.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export class Panel extends Component {
8686

8787
render() {
8888
const {individualFoldStates, hasError} = this.state;
89+
const {canReorder} = this.props;
8990

9091
if (hasError) {
9192
return <PanelError />;
@@ -97,6 +98,11 @@ export class Panel extends Component {
9798
key: index,
9899
folded: individualFoldStates[index] || false,
99100
toggleFold: () => this.toggleFold(index),
101+
canMoveUp: canReorder && individualFoldStates.length > 1 && index > 0,
102+
canMoveDown:
103+
canReorder &&
104+
individualFoldStates.length > 1 &&
105+
index !== individualFoldStates.length - 1,
100106
});
101107
}
102108
return child;
@@ -122,6 +128,7 @@ Panel.propTypes = {
122128
deleteAction: PropTypes.func,
123129
noPadding: PropTypes.bool,
124130
showExpandCollapse: PropTypes.bool,
131+
canReorder: PropTypes.bool,
125132
};
126133

127134
Panel.defaultProps = {

src/components/containers/ShapeAccordion.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class ShapeAccordion extends Component {
1414
layout: {shapes = []},
1515
localize: _,
1616
} = this.context;
17-
const {canAdd, children} = this.props;
17+
const {canAdd, children, canReorder} = this.props;
1818

1919
const content =
2020
shapes.length &&
@@ -48,7 +48,7 @@ class ShapeAccordion extends Component {
4848
};
4949

5050
return (
51-
<LayoutPanel addAction={canAdd ? addAction : null}>
51+
<LayoutPanel addAction={canAdd ? addAction : null} canReorder={canReorder}>
5252
{content ? (
5353
content
5454
) : (
@@ -74,6 +74,7 @@ ShapeAccordion.contextTypes = {
7474
ShapeAccordion.propTypes = {
7575
children: PropTypes.node,
7676
canAdd: PropTypes.bool,
77+
canReorder: PropTypes.bool,
7778
};
7879

7980
export default ShapeAccordion;

src/components/containers/TraceAccordion.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class TraceAccordion extends Component {
130130
}
131131

132132
render() {
133-
const {canAdd, canGroup} = this.props;
133+
const {canAdd, canGroup, canReorder} = this.props;
134134
const _ = this.context.localize;
135135

136136
if (canAdd) {
@@ -146,7 +146,7 @@ class TraceAccordion extends Component {
146146
};
147147
const traceFolds = this.renderTraceFolds();
148148
return (
149-
<PlotlyPanel addAction={addAction}>
149+
<PlotlyPanel addAction={addAction} canReorder={canReorder}>
150150
{traceFolds ? traceFolds : this.renderTracePanelHelp()}
151151
</PlotlyPanel>
152152
);
@@ -190,6 +190,7 @@ TraceAccordion.contextTypes = {
190190
TraceAccordion.propTypes = {
191191
canAdd: PropTypes.bool,
192192
canGroup: PropTypes.bool,
193+
canReorder: PropTypes.bool,
193194
children: PropTypes.node,
194195
traceFilterCondition: PropTypes.func,
195196
};

src/default_panels/GraphCreatePanel.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const GraphCreatePanel = (props, {localize: _, setPanel}) => {
2525
traceFilterCondition={t =>
2626
!(t.transforms && t.transforms.some(tr => ['fit', 'moving-average'].includes(tr.type)))
2727
}
28+
canReorder
2829
>
2930
<TraceSelector label={_('Type')} attr="type" show />
3031

src/default_panels/StyleImagesPanel.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from '../components';
1212

1313
const StyleImagesPanel = (props, {localize: _}) => (
14-
<ImageAccordion canAdd>
14+
<ImageAccordion canAdd canReorder>
1515
<Radio
1616
attr="visible"
1717
options={[{label: _('Show'), value: true}, {label: _('Hide'), value: false}]}

src/default_panels/StyleNotesPanel.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from '../components';
1818

1919
const StyleNotesPanel = (props, {localize: _}) => (
20-
<AnnotationAccordion canAdd>
20+
<AnnotationAccordion canAdd canReorder>
2121
<PlotlySection name={_('Note Text')} attr="text">
2222
<TextEditor attr="text" />
2323
<FontSelector label={_('Typeface')} attr="font.family" />

0 commit comments

Comments
 (0)