Skip to content

Commit 9780ab1

Browse files
authored
Merge pull request #784 from plotly/customize-default-editor
allow custom ordering of default panels
2 parents 9260bb8 + bf7420c commit 9780ab1

File tree

5 files changed

+293
-4
lines changed

5 files changed

+293
-4
lines changed

dev/App.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,26 @@ class App extends Component {
185185
// fontOptions={[{label:'Arial', value: 'arial'}]}
186186
// chartHelp={chartHelp}
187187
>
188-
<DefaultEditor>
188+
<DefaultEditor
189+
// menuPanelOrder={[
190+
// {group: 'Dev', name: 'JSON'},
191+
// {group: 'Dev', name: 'Inspector'},
192+
// {group: 'Structure', name: 'Create'},
193+
// {group: 'Structure', name: 'Subplots'},
194+
// {group: 'Structure', name: 'Transforms'},
195+
// {group: 'Test', name: 'Testing'},
196+
// {group: 'Style', name: 'General'},
197+
// {group: 'Style', name: 'Traces'},
198+
// {group: 'Style', name: 'Axes'},
199+
// {group: 'Style', name: 'Legend'},
200+
// {group: 'Style', name: 'Color Bars'},
201+
// {group: 'Style', name: 'Annotation'},
202+
// {group: 'Style', name: 'Shapes'},
203+
// {group: 'Style', name: 'Images'},
204+
// {group: 'Style', name: 'Sliders'},
205+
// {group: 'Style', name: 'Menus'},
206+
// ]}
207+
>
189208
<Panel group="Dev" name="JSON">
190209
<div className="mocks">
191210
<Select

src/DefaultEditor.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class DefaultEditor extends Component {
6969
const logo = this.props.logoSrc && <Logo src={this.props.logoSrc} />;
7070

7171
return (
72-
<PanelMenuWrapper>
72+
<PanelMenuWrapper menuPanelOrder={this.props.menuPanelOrder}>
7373
{logo ? logo : null}
7474
<GraphCreatePanel group={_('Structure')} name={_('Traces')} />
7575
<GraphSubplotsPanel group={_('Structure')} name={_('Subplots')} />
@@ -95,6 +95,7 @@ class DefaultEditor extends Component {
9595
DefaultEditor.propTypes = {
9696
children: PropTypes.node,
9797
logoSrc: PropTypes.string,
98+
menuPanelOrder: PropTypes.array,
9899
};
99100

100101
DefaultEditor.contextTypes = {

src/components/PanelMenuWrapper.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
22
import React, {cloneElement, Component} from 'react';
33
import SidebarGroup from './sidebar/SidebarGroup';
44
import {bem} from 'lib';
5+
import sortMenu from 'lib/sortMenu';
56

67
class PanelsWithSidebar extends Component {
78
constructor(props) {
@@ -46,12 +47,17 @@ class PanelsWithSidebar extends Component {
4647
}
4748

4849
computeMenuOptions(props) {
49-
const {children} = props;
50+
const {children, menuPanelOrder} = props;
5051
const sections = [];
5152
const groupLookup = {};
5253
let groupIndex;
54+
const panels = React.Children.toArray(children);
5355

54-
React.Children.forEach(children, child => {
56+
if (menuPanelOrder) {
57+
sortMenu(panels, menuPanelOrder);
58+
}
59+
60+
panels.forEach(child => {
5561
if (!child) {
5662
return;
5763
}
@@ -101,6 +107,7 @@ class PanelsWithSidebar extends Component {
101107

102108
PanelsWithSidebar.propTypes = {
103109
children: PropTypes.node,
110+
menuPanelOrder: PropTypes.array,
104111
};
105112

106113
PanelsWithSidebar.childContextTypes = {

src/lib/__tests__/sortMenu-test.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import sortMenu from '../sortMenu';
2+
3+
function stringify(array) {
4+
let string = '';
5+
array.forEach(obj => {
6+
string += JSON.stringify(obj);
7+
});
8+
return string;
9+
}
10+
11+
describe('sortMenu', () => {
12+
it('modifies original array to follow the group, then name order provided', () => {
13+
const initialArray = [
14+
{props: {group: 'DEV', name: 'Inspector'}},
15+
{props: {group: 'DEV', name: 'JSON'}},
16+
];
17+
const orderProp = [{group: 'DEV', name: 'JSON'}, {group: 'DEV', name: 'Inspector'}];
18+
19+
sortMenu(initialArray, orderProp);
20+
expect(stringify(initialArray)).toBe(
21+
stringify([{props: {group: 'DEV', name: 'JSON'}}, {props: {group: 'DEV', name: 'Inspector'}}])
22+
);
23+
});
24+
25+
it('sorts the array by group, then by name', () => {
26+
const initialArray = [
27+
{props: {group: 'Structure', name: 'Create'}},
28+
{props: {group: 'Structure', name: 'Subplots'}},
29+
{props: {group: 'Style', name: 'Color Bars'}},
30+
{props: {group: 'Style', name: 'Annotation'}},
31+
{props: {group: 'DEV', name: 'Inspector'}},
32+
{props: {group: 'DEV', name: 'JSON'}},
33+
];
34+
const orderProp = [
35+
{group: 'DEV', name: 'JSON'},
36+
{group: 'DEV', name: 'Inspector'},
37+
{group: 'Structure', name: 'Subplots'},
38+
{group: 'Structure', name: 'Create'},
39+
{group: 'Style', name: 'Color Bars'},
40+
{group: 'Style', name: 'Annotation'},
41+
];
42+
43+
sortMenu(initialArray, orderProp);
44+
expect(stringify(initialArray)).toBe(
45+
stringify([
46+
{props: {group: 'DEV', name: 'JSON'}},
47+
{props: {group: 'DEV', name: 'Inspector'}},
48+
{props: {group: 'Structure', name: 'Subplots'}},
49+
{props: {group: 'Structure', name: 'Create'}},
50+
{props: {group: 'Style', name: 'Color Bars'}},
51+
{props: {group: 'Style', name: 'Annotation'}},
52+
])
53+
);
54+
});
55+
56+
it('puts not mentionned panels to the bottom of list and sorts alphabetically', () => {
57+
const initialArray = [
58+
{props: {group: 'DEV', name: 'JSON'}},
59+
{props: {group: 'DEV', name: 'Inspector'}},
60+
{props: {group: 'Structure', name: 'Create'}},
61+
{props: {group: 'Structure', name: 'Subplots'}},
62+
{props: {group: 'Style', name: 'Color Bars'}},
63+
{props: {group: 'Style', name: 'Annotation'}},
64+
];
65+
const orderProp = [
66+
{group: 'Structure', name: 'Subplots'},
67+
{group: 'Structure', name: 'Create'},
68+
{group: 'Style', name: 'Color Bars'},
69+
{group: 'Style', name: 'Annotation'},
70+
];
71+
72+
sortMenu(initialArray, orderProp);
73+
expect(stringify(initialArray)).toBe(
74+
stringify([
75+
{props: {group: 'Structure', name: 'Subplots'}},
76+
{props: {group: 'Structure', name: 'Create'}},
77+
{props: {group: 'Style', name: 'Color Bars'}},
78+
{props: {group: 'Style', name: 'Annotation'}},
79+
{props: {group: 'DEV', name: 'Inspector'}},
80+
{props: {group: 'DEV', name: 'JSON'}},
81+
])
82+
);
83+
});
84+
85+
it('orders not mentionned subpanels at the end, alphabetically', () => {
86+
const initialArray = [
87+
{props: {group: 'Style', name: 'General'}},
88+
{props: {group: 'Style', name: 'Traces'}},
89+
{props: {group: 'Style', name: 'Axes'}},
90+
{props: {group: 'Structure', name: 'Create'}},
91+
];
92+
const orderProp = [{group: 'Style', name: 'Traces'}];
93+
94+
sortMenu(initialArray, orderProp);
95+
expect(stringify(initialArray)).toBe(
96+
stringify([
97+
{props: {group: 'Style', name: 'Traces'}},
98+
{props: {group: 'Style', name: 'Axes'}},
99+
{props: {group: 'Style', name: 'General'}},
100+
{props: {group: 'Structure', name: 'Create'}},
101+
])
102+
);
103+
});
104+
105+
it('ignores non existent panel groups', () => {
106+
const initialArray = [
107+
{props: {group: 'Structure', name: 'Create'}},
108+
{props: {group: 'Structure', name: 'Subplots'}},
109+
{props: {group: 'Style', name: 'Color Bars'}},
110+
{props: {group: 'Style', name: 'Annotation'}},
111+
];
112+
113+
const orderProp = [
114+
{group: 'Non Existent', name: 'Subplots'},
115+
{group: 'Structure', name: 'Create'},
116+
{group: 'Style', name: 'Color Bars'},
117+
{group: 'Style', name: 'Annotation'},
118+
];
119+
120+
sortMenu(initialArray, orderProp);
121+
expect(stringify(initialArray)).toBe(
122+
stringify([
123+
{props: {group: 'Structure', name: 'Create'}},
124+
{props: {group: 'Structure', name: 'Subplots'}},
125+
{props: {group: 'Style', name: 'Color Bars'}},
126+
{props: {group: 'Style', name: 'Annotation'}},
127+
])
128+
);
129+
});
130+
131+
it('ignores non existent panel names', () => {
132+
const initialArray = [
133+
{props: {group: 'Structure', name: 'Subplots'}},
134+
{props: {group: 'Structure', name: 'Create'}},
135+
{props: {group: 'Style', name: 'Color Bars'}},
136+
{props: {group: 'Style', name: 'Annotation'}},
137+
];
138+
139+
const orderProp = [
140+
{group: 'Structure', name: 'Non Existent'},
141+
{group: 'Style', name: 'Color Bars'},
142+
{group: 'Style', name: 'Annotation'},
143+
];
144+
145+
sortMenu(initialArray, orderProp);
146+
expect(stringify(initialArray)).toBe(
147+
stringify([
148+
{props: {group: 'Style', name: 'Color Bars'}},
149+
{props: {group: 'Style', name: 'Annotation'}},
150+
{props: {group: 'Structure', name: 'Create'}},
151+
{props: {group: 'Structure', name: 'Subplots'}},
152+
])
153+
);
154+
});
155+
156+
it('ignores invalid combinations', () => {
157+
const initialArray = [
158+
{props: {group: 'Structure', name: 'Subplots'}},
159+
{props: {group: 'Structure', name: 'Create'}},
160+
{props: {group: 'Style', name: 'Color Bars'}},
161+
{props: {group: 'Style', name: 'Annotation'}},
162+
];
163+
164+
const orderProp = [
165+
{group: 'Structure', name: 'Annotation'},
166+
{group: 'Style', name: 'Color Bars'},
167+
{group: 'Style', name: 'Annotation'},
168+
];
169+
170+
sortMenu(initialArray, orderProp);
171+
expect(stringify(initialArray)).toBe(
172+
stringify([
173+
{props: {group: 'Style', name: 'Color Bars'}},
174+
{props: {group: 'Style', name: 'Annotation'}},
175+
{props: {group: 'Structure', name: 'Create'}},
176+
{props: {group: 'Structure', name: 'Subplots'}},
177+
])
178+
);
179+
});
180+
});

src/lib/sortMenu.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
function getUniqueValues(value, index, self) {
2+
return self.indexOf(value) === index;
3+
}
4+
5+
function sortAlphabetically(a, b) {
6+
const sortByGroup = a.props.group === b.props.group ? 0 : a.props.group < b.props.group ? -1 : 1;
7+
const sortByName = a.props.name === b.props.name ? 0 : a.props.name < b.props.name ? -1 : 1;
8+
return sortByGroup || sortByName;
9+
}
10+
11+
export default function sortMenu(panels, order) {
12+
// validates order, if a desired panel matches no panel in the panels array,
13+
// it is excluded from ordering considerations
14+
15+
// eslint-disable-next-line
16+
order = order.filter(desiredPanel =>
17+
panels.some(
18+
actualPanel =>
19+
actualPanel.props.name === desiredPanel.name &&
20+
actualPanel.props.group === desiredPanel.group
21+
)
22+
);
23+
24+
const desiredGroupOrder = order.map(panel => panel.group).filter(getUniqueValues);
25+
const desiredNameOrder = order.map(panel => panel.name).filter(getUniqueValues);
26+
27+
panels.sort((a, b) => {
28+
const panelAHasGroupCustomOrder = desiredGroupOrder.includes(a.props.group);
29+
const panelBHasGroupCustomOrder = desiredGroupOrder.includes(b.props.group);
30+
31+
// if one of the elements is not in the desiredGroupOrder array, then it goes to the end of the list
32+
if (panelAHasGroupCustomOrder && !panelBHasGroupCustomOrder) {
33+
return -1;
34+
}
35+
if (!panelAHasGroupCustomOrder && panelBHasGroupCustomOrder) {
36+
return 1;
37+
}
38+
39+
// if both elements are not in the desiredGroupOrder array, they get sorted alphabetically,
40+
// by group, then by name
41+
if (!panelAHasGroupCustomOrder && !panelBHasGroupCustomOrder) {
42+
return sortAlphabetically(a, b);
43+
}
44+
45+
// if both elements are in the desiredGroupOrder array, they get sorted according to their order in
46+
// the desiredGroupOrder, then desiredNameOrder arrays.
47+
if (panelAHasGroupCustomOrder && panelBHasGroupCustomOrder) {
48+
const indexOfGroupA = desiredGroupOrder.indexOf(a.props.group);
49+
const indexOfGroupB = desiredGroupOrder.indexOf(b.props.group);
50+
51+
if (indexOfGroupA < indexOfGroupB) {
52+
return -1;
53+
}
54+
55+
if (indexOfGroupA > indexOfGroupB) {
56+
return 1;
57+
}
58+
59+
if (indexOfGroupA === indexOfGroupB) {
60+
const panelAHasNameCustomOrder = desiredNameOrder.includes(a.props.name);
61+
const panelBHasNameCustomOrder = desiredNameOrder.includes(b.props.name);
62+
63+
if (!panelAHasNameCustomOrder || !panelBHasNameCustomOrder) {
64+
if (panelAHasNameCustomOrder && !panelBHasNameCustomOrder) {
65+
return -1;
66+
}
67+
if (!panelAHasNameCustomOrder && panelBHasNameCustomOrder) {
68+
return 1;
69+
}
70+
if (!panelAHasNameCustomOrder && !panelBHasNameCustomOrder) {
71+
return sortAlphabetically(a, b);
72+
}
73+
}
74+
75+
if (panelAHasNameCustomOrder && panelBHasNameCustomOrder) {
76+
return desiredNameOrder.indexOf(a.props.name) - desiredNameOrder.indexOf(b.props.name);
77+
}
78+
}
79+
}
80+
return 0;
81+
});
82+
}

0 commit comments

Comments
 (0)