Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion dev/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,26 @@ class App extends Component {
// fontOptions={[{label:'Arial', value: 'arial'}]}
// chartHelp={chartHelp}
>
<DefaultEditor>
<DefaultEditor
// menuPanelOrder={[
// {group: 'Dev', name: 'JSON'},
// {group: 'Dev', name: 'Inspector'},
// {group: 'Structure', name: 'Create'},
// {group: 'Structure', name: 'Subplots'},
// {group: 'Structure', name: 'Transforms'},
// {group: 'Test', name: 'Testing'},
// {group: 'Style', name: 'General'},
// {group: 'Style', name: 'Traces'},
// {group: 'Style', name: 'Axes'},
// {group: 'Style', name: 'Legend'},
// {group: 'Style', name: 'Color Bars'},
// {group: 'Style', name: 'Annotation'},
// {group: 'Style', name: 'Shapes'},
// {group: 'Style', name: 'Images'},
// {group: 'Style', name: 'Sliders'},
// {group: 'Style', name: 'Menus'},
// ]}
>
<Panel group="Dev" name="JSON">
<div className="mocks">
<Select
Expand Down
3 changes: 2 additions & 1 deletion src/DefaultEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class DefaultEditor extends Component {
const logo = this.props.logoSrc && <Logo src={this.props.logoSrc} />;

return (
<PanelMenuWrapper>
<PanelMenuWrapper menuPanelOrder={this.props.menuPanelOrder}>
{logo ? logo : null}
<GraphCreatePanel group={_('Structure')} name={_('Traces')} />
<GraphSubplotsPanel group={_('Structure')} name={_('Subplots')} />
Expand All @@ -95,6 +95,7 @@ class DefaultEditor extends Component {
DefaultEditor.propTypes = {
children: PropTypes.node,
logoSrc: PropTypes.string,
menuPanelOrder: PropTypes.array,
};

DefaultEditor.contextTypes = {
Expand Down
11 changes: 9 additions & 2 deletions src/components/PanelMenuWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, {cloneElement, Component} from 'react';
import SidebarGroup from './sidebar/SidebarGroup';
import {bem} from 'lib';
import sortMenu from 'lib/sortMenu';

class PanelsWithSidebar extends Component {
constructor(props) {
Expand Down Expand Up @@ -46,12 +47,17 @@ class PanelsWithSidebar extends Component {
}

computeMenuOptions(props) {
const {children} = props;
const {children, menuPanelOrder} = props;
const sections = [];
const groupLookup = {};
let groupIndex;
const panels = React.Children.toArray(children);

React.Children.forEach(children, child => {
if (menuPanelOrder) {
sortMenu(panels, menuPanelOrder);
}

panels.forEach(child => {
if (!child) {
return;
}
Expand Down Expand Up @@ -101,6 +107,7 @@ class PanelsWithSidebar extends Component {

PanelsWithSidebar.propTypes = {
children: PropTypes.node,
menuPanelOrder: PropTypes.array,
};

PanelsWithSidebar.childContextTypes = {
Expand Down
180 changes: 180 additions & 0 deletions src/lib/__tests__/sortMenu-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import sortMenu from '../sortMenu';

function stringify(array) {
let string = '';
array.forEach(obj => {
string += JSON.stringify(obj);
});
return string;
}

describe('sortMenu', () => {
it('modifies original array to follow the group, then name order provided', () => {
const initialArray = [
{props: {group: 'DEV', name: 'Inspector'}},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider making a helper here that takes in threes arrays of strings of form GROUP/NAME and does a split and does the comparison, just to have some very compact test cases below

{props: {group: 'DEV', name: 'JSON'}},
];
const orderProp = [{group: 'DEV', name: 'JSON'}, {group: 'DEV', name: 'Inspector'}];

sortMenu(initialArray, orderProp);
expect(stringify(initialArray)).toBe(
stringify([{props: {group: 'DEV', name: 'JSON'}}, {props: {group: 'DEV', name: 'Inspector'}}])
);
});

it('sorts the array by group, then by name', () => {
const initialArray = [
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Structure', name: 'Subplots'}},
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
{props: {group: 'DEV', name: 'Inspector'}},
{props: {group: 'DEV', name: 'JSON'}},
];
const orderProp = [
{group: 'DEV', name: 'JSON'},
{group: 'DEV', name: 'Inspector'},
{group: 'Structure', name: 'Subplots'},
{group: 'Structure', name: 'Create'},
{group: 'Style', name: 'Color Bars'},
{group: 'Style', name: 'Annotation'},
];

sortMenu(initialArray, orderProp);
expect(stringify(initialArray)).toBe(
stringify([
{props: {group: 'DEV', name: 'JSON'}},
{props: {group: 'DEV', name: 'Inspector'}},
{props: {group: 'Structure', name: 'Subplots'}},
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
])
);
});

it('puts not mentionned panels to the bottom of list and sorts alphabetically', () => {
const initialArray = [
{props: {group: 'DEV', name: 'JSON'}},
{props: {group: 'DEV', name: 'Inspector'}},
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Structure', name: 'Subplots'}},
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
];
const orderProp = [
{group: 'Structure', name: 'Subplots'},
{group: 'Structure', name: 'Create'},
{group: 'Style', name: 'Color Bars'},
{group: 'Style', name: 'Annotation'},
];

sortMenu(initialArray, orderProp);
expect(stringify(initialArray)).toBe(
stringify([
{props: {group: 'Structure', name: 'Subplots'}},
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
{props: {group: 'DEV', name: 'Inspector'}},
{props: {group: 'DEV', name: 'JSON'}},
])
);
});

it('orders not mentionned subpanels at the end, alphabetically', () => {
const initialArray = [
{props: {group: 'Style', name: 'General'}},
{props: {group: 'Style', name: 'Traces'}},
{props: {group: 'Style', name: 'Axes'}},
{props: {group: 'Structure', name: 'Create'}},
];
const orderProp = [{group: 'Style', name: 'Traces'}];

sortMenu(initialArray, orderProp);
expect(stringify(initialArray)).toBe(
stringify([
{props: {group: 'Style', name: 'Traces'}},
{props: {group: 'Style', name: 'Axes'}},
{props: {group: 'Style', name: 'General'}},
{props: {group: 'Structure', name: 'Create'}},
])
);
});

it('ignores non existent panel groups', () => {
const initialArray = [
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Structure', name: 'Subplots'}},
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
];

const orderProp = [
{group: 'Non Existent', name: 'Subplots'},
{group: 'Structure', name: 'Create'},
{group: 'Style', name: 'Color Bars'},
{group: 'Style', name: 'Annotation'},
];

sortMenu(initialArray, orderProp);
expect(stringify(initialArray)).toBe(
stringify([
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Structure', name: 'Subplots'}},
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
])
);
});

it('ignores non existent panel names', () => {
const initialArray = [
{props: {group: 'Structure', name: 'Subplots'}},
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
];

const orderProp = [
{group: 'Structure', name: 'Non Existent'},
{group: 'Style', name: 'Color Bars'},
{group: 'Style', name: 'Annotation'},
];

sortMenu(initialArray, orderProp);
expect(stringify(initialArray)).toBe(
stringify([
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Structure', name: 'Subplots'}},
])
);
});

it('ignores invalid combinations', () => {
const initialArray = [
{props: {group: 'Structure', name: 'Subplots'}},
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
];

const orderProp = [
{group: 'Structure', name: 'Annotation'},
{group: 'Style', name: 'Color Bars'},
{group: 'Style', name: 'Annotation'},
];

sortMenu(initialArray, orderProp);
expect(stringify(initialArray)).toBe(
stringify([
{props: {group: 'Style', name: 'Color Bars'}},
{props: {group: 'Style', name: 'Annotation'}},
{props: {group: 'Structure', name: 'Create'}},
{props: {group: 'Structure', name: 'Subplots'}},
])
);
});
});
82 changes: 82 additions & 0 deletions src/lib/sortMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
function getUniqueValues(value, index, self) {
return self.indexOf(value) === index;
}

function sortAlphabetically(a, b) {
const sortByGroup = a.props.group === b.props.group ? 0 : a.props.group < b.props.group ? -1 : 1;
const sortByName = a.props.name === b.props.name ? 0 : a.props.name < b.props.name ? -1 : 1;
return sortByGroup || sortByName;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clever! took me a sec to think about how that works :)

}

export default function sortMenu(panels, order) {
// validates order, if a desired panel matches no panel in the panels array,
// it is excluded from ordering considerations

// eslint-disable-next-line
order = order.filter(desiredPanel =>
panels.some(
actualPanel =>
actualPanel.props.name === desiredPanel.name &&
actualPanel.props.group === desiredPanel.group
)
);

const desiredGroupOrder = order.map(panel => panel.group).filter(getUniqueValues);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arguably this is unnecessary right? It won't change the order :)

const desiredNameOrder = order.map(panel => panel.name).filter(getUniqueValues);

panels.sort((a, b) => {
const panelAHasGroupCustomOrder = desiredGroupOrder.includes(a.props.group);
const panelBHasGroupCustomOrder = desiredGroupOrder.includes(b.props.group);

// if one of the elements is not in the desiredGroupOrder array, then it goes to the end of the list
if (panelAHasGroupCustomOrder && !panelBHasGroupCustomOrder) {
return -1;
}
if (!panelAHasGroupCustomOrder && panelBHasGroupCustomOrder) {
return 1;
}

// if both elements are not in the desiredGroupOrder array, they get sorted alphabetically,
// by group, then by name
if (!panelAHasGroupCustomOrder && !panelBHasGroupCustomOrder) {
return sortAlphabetically(a, b);
}

// if both elements are in the desiredGroupOrder array, they get sorted according to their order in
// the desiredGroupOrder, then desiredNameOrder arrays.
if (panelAHasGroupCustomOrder && panelBHasGroupCustomOrder) {
const indexOfGroupA = desiredGroupOrder.indexOf(a.props.group);
const indexOfGroupB = desiredGroupOrder.indexOf(b.props.group);

if (indexOfGroupA < indexOfGroupB) {
return -1;
}

if (indexOfGroupA > indexOfGroupB) {
return 1;
}

if (indexOfGroupA === indexOfGroupB) {
const panelAHasNameCustomOrder = desiredNameOrder.includes(a.props.name);
const panelBHasNameCustomOrder = desiredNameOrder.includes(b.props.name);

if (!panelAHasNameCustomOrder || !panelBHasNameCustomOrder) {
if (panelAHasNameCustomOrder && !panelBHasNameCustomOrder) {
return -1;
}
if (!panelAHasNameCustomOrder && panelBHasNameCustomOrder) {
return 1;
}
if (!panelAHasNameCustomOrder && !panelBHasNameCustomOrder) {
return sortAlphabetically(a, b);
}
}

if (panelAHasNameCustomOrder && panelBHasNameCustomOrder) {
return desiredNameOrder.indexOf(a.props.name) - desiredNameOrder.indexOf(b.props.name);
}
}
}
return 0;
});
}