Skip to content

Commit

Permalink
Allow closing active story and use separate expansion for filtered
Browse files Browse the repository at this point in the history
For #5594 and #5419
  • Loading branch information
Tom Coleman committed Feb 16, 2019
1 parent 247dd6a commit 0854fb6
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 53 deletions.
6 changes: 4 additions & 2 deletions lib/ui/src/components/sidebar/SidebarSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ const FilterForm = styled.form(({ theme, focussed }) => ({
},
}));

export const PureSidebarSearch = ({ focussed, onSetFocussed, className, ...props }) => (
<FilterForm autoComplete="off" focussed={focussed} className={className}>
export const PureSidebarSearch = ({ focussed, onSetFocussed, className, onReset, ...props }) => (
<FilterForm autoComplete="off" focussed={focussed} className={className} onReset={onReset}>
<FilterField
type="text"
autocomplete="off"
Expand All @@ -117,13 +117,15 @@ export const PureSidebarSearch = ({ focussed, onSetFocussed, className, ...props
);

PureSidebarSearch.propTypes = {
onReset: PropTypes.func,
focussed: PropTypes.bool.isRequired,
onSetFocussed: PropTypes.func.isRequired,
className: PropTypes.string,
};

PureSidebarSearch.defaultProps = {
className: null,
onReset: null,
};

export default withState('focussed', 'onSetFocussed', false)(PureSidebarSearch);
147 changes: 96 additions & 51 deletions lib/ui/src/components/sidebar/treeview/treeview.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,21 +138,50 @@ const Tree = props => {
}
};

const calculateTreeProps = memoize(50)((input, filter, selectedId, extraExpanded) => {
const dataset = filter ? toFiltered(input, filter) : input;
const calculateTreeState = memoize(50)(
({ dataset, selectedId }, { lastSelectedId, unfilteredExpanded }) => {
if (selectedId === lastSelectedId) {
return null;
}

const selected = Object.keys(dataset).reduce(
(acc, k) => Object.assign(acc, { [k]: k === selectedId }),
{}
);
// If a new selection is made, we need to ensure it is part of the expanded set
const selectedAncestorIds = selectedId ? getParents(selectedId, dataset).map(i => i.id) : [];

const newExpanded = Object.keys(dataset).reduce(
(acc, key) => ({
...acc,
[key]: selectedAncestorIds.includes(key) || unfilteredExpanded[key],
}),
{}
);

return {
lastSelectedId: selectedId,
unfilteredExpanded: newExpanded,
};
}
);

const selectedAncestorIds = selectedId ? getParents(selectedId, dataset).map(i => i.id) : [];
const getExpanded = ({ unfilteredExpanded, filteredExpanded, filter }) =>
filter ? filteredExpanded : unfilteredExpanded;

const expanded = Object.keys(dataset).reduce(
(acc, key) => ({
...acc,
[key]: selectedAncestorIds.includes(key) || extraExpanded[key],
}),
const getFilteredDataset = memoize(50)(({ dataset, filter }) =>
filter ? toFiltered(dataset, filter) : dataset
);

// Update the set of expansions we are currently working with
const updateExpanded = fn => ({ unfilteredExpanded, filteredExpanded, filter }) => {
if (filter) {
return {
filteredExpanded: fn(filteredExpanded),
};
}
return { unfilteredExpanded: fn(unfilteredExpanded) };
};

const getPropsForTree = memoize(50)(({ dataset, selectedId }) => {
const selected = Object.keys(dataset).reduce(
(acc, k) => Object.assign(acc, { [k]: k === selectedId }),
{}
);

Expand All @@ -166,34 +195,59 @@ const calculateTreeProps = memoize(50)((input, filter, selectedId, extraExpanded
{ roots: [], others: [] }
);

return { input, dataset, selected, expanded, roots, others };
return { selected, roots, others };
});

// eslint-disable-next-line react/no-multi-comp
class TreeState extends PureComponent {
state = {
// We maintain two sets of expanded nodes, so we remember which were expanded if we clear the filter
unfilteredExpanded: [],
filteredExpanded: [],
filter: null,
lastSelectedId: null,
};

static getDerivedStateFromProps(props, state) {
return calculateTreeState(props, state);
}

events = {
onClick: (e, item) => {
const { extraExpanded } = this.state;
this.setState(
updateExpanded(expanded => ({
...expanded,
[item.id]: !expanded[item.id],
}))
);
},
onFilter: e => {
const { dataset } = this.props;
const filter = e.target.value && e.target.value.length >= 2 ? e.target.value : '';
const filteredDataset = getFilteredDataset({ dataset, filter });

// Whenever we change the filter, we reset the "filtered" expanded set back to all matching stories
this.setState({
extraExpanded: {
...extraExpanded,
[item.id]: !extraExpanded[item.id],
},
filter,
filteredExpanded:
!!filter &&
Object.keys(filteredDataset).reduce((acc, k) => Object.assign(acc, { [k]: true }), {}),
});
},
onKeyUp: (e, item) => {
const { dataset: input, selectedId, prefix } = this.props;
const { filter, extraExpanded } = this.state;
const { dataset, expanded } = calculateTreeProps(input, filter, selectedId, extraExpanded);
const { prefix, dataset } = this.props;
const { filter } = this.state;
const filteredDataset = getFilteredDataset({ dataset, filter });
const expanded = getExpanded(this.state);

const action = keyEventToAction(e);
if (action) {
prevent(e);
}

if (action === 'RIGHT') {
const next = getNext({ id: item.id, dataset, expanded });
if (!dataset[item.id].children || expanded[item.id]) {
const next = getNext({ id: item.id, dataset: filteredDataset, expanded });
if (!filteredDataset[item.id].children || expanded[item.id]) {
if (next) {
try {
document.getElementById(createId(next.id, prefix)).focus();
Expand All @@ -203,13 +257,13 @@ class TreeState extends PureComponent {
}
}

this.setState({ extraExpanded: { ...extraExpanded, [item.id]: true } });
this.setState(updateExpanded(currExpanded => ({ ...currExpanded, [item.id]: true })));
}
if (action === 'LEFT') {
const prev = getPrevious({ id: item.id, dataset, expanded });
const prev = getPrevious({ id: item.id, dataset: filteredDataset, expanded });

if (!dataset[item.id].children || !expanded[item.id]) {
const parent = getParent(item.id, dataset);
if (!filteredDataset[item.id].children || !expanded[item.id]) {
const parent = getParent(item.id, filteredDataset);
if (parent && parent.children) {
try {
document.getElementById(createId(parent.id, prefix)).focus();
Expand All @@ -227,10 +281,10 @@ class TreeState extends PureComponent {
}
}

this.setState({ extraExpanded: { ...extraExpanded, [item.id]: false } });
this.setState(updateExpanded(currExpanded => ({ ...currExpanded, [item.id]: false })));
}
if (action === 'DOWN') {
const next = getNext({ id: item.id, dataset, expanded });
const next = getNext({ id: item.id, dataset: filteredDataset, expanded });
if (next) {
try {
document.getElementById(createId(next.id, prefix)).focus();
Expand All @@ -240,7 +294,7 @@ class TreeState extends PureComponent {
}
}
if (action === 'UP') {
const prev = getPrevious({ id: item.id, dataset, expanded });
const prev = getPrevious({ id: item.id, dataset: filteredDataset, expanded });

if (prev) {
try {
Expand All @@ -251,25 +305,15 @@ class TreeState extends PureComponent {
}
}
},

onFilter: e => {
const filter = e.target.value.length >= 2 ? e.target.value : '';
this.setState({ filter });
},
};

state = {
filter: null,
extraExpanded: {},
};

render() {
const {
events,
state: { filter, extraExpanded },
state: { filter, unfilteredExpanded, filteredExpanded },
props,
} = this;
const { prefix, dataset: input, selectedId } = props;
const { prefix, dataset, selectedId } = props;

const Filter = getFilter(props.Filter);
const List = getFilter(props.List);
Expand All @@ -281,16 +325,15 @@ class TreeState extends PureComponent {
const Section = getContainer(props.Section);
const Message = getMessage(props.Message);

const { dataset, selected, expanded, roots, others } = calculateTreeProps(
input,
filter,
selectedId,
extraExpanded
);
const filteredDataset = getFilteredDataset({ dataset, filter });
const expanded = filter ? filteredExpanded : unfilteredExpanded;
const { selected, roots, others } = getPropsForTree({ dataset: filteredDataset, selectedId });

return (
<Fragment>
{Filter ? <Filter key="filter" onChange={this.events.onFilter} /> : null}
{Filter ? (
<Filter key="filter" onChange={this.events.onFilter} onReset={this.events.onFilter} />
) : null}
{roots.length || others.length ? (
<Fragment>
{roots.map(({ id, name, children }) => (
Expand All @@ -302,7 +345,7 @@ class TreeState extends PureComponent {
<Branch
key={key}
depth={0}
dataset={dataset}
dataset={filteredDataset}
selected={selected}
expanded={expanded}
root={key}
Expand All @@ -326,7 +369,7 @@ class TreeState extends PureComponent {
<Branch
key={id}
depth={0}
dataset={dataset}
dataset={filteredDataset}
selected={selected}
expanded={expanded}
root={id}
Expand All @@ -351,6 +394,8 @@ class TreeState extends PureComponent {
TreeState.propTypes = {
prefix: PropTypes.string.isRequired,
dataset: PropTypes.shape({}).isRequired,
// We do use this prop, eslint is confused
// eslint-disable-next-line react/no-unused-prop-types
selectedId: PropTypes.string,
};
TreeState.defaultProps = {
Expand Down

0 comments on commit 0854fb6

Please sign in to comment.