Commit 8999cc4b authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Remove `filterState`; add `focusState`

Refactor the computation of "filter state", that
is, all of the store state that impacts what
constitutes applied filters on annotations.
parent e9b81457
import { createElement } from 'preact'; import { createElement } from 'preact';
import { useMemo } from 'preact/hooks';
import propTypes from 'prop-types'; import propTypes from 'prop-types';
import { countVisible } from '../util/thread'; import { countVisible } from '../util/thread';
...@@ -9,10 +10,19 @@ import useRootThread from './hooks/use-root-thread'; ...@@ -9,10 +10,19 @@ import useRootThread from './hooks/use-root-thread';
import useStore from '../store/use-store'; import useStore from '../store/use-store';
/** /**
* @typedef {import('../store/modules/filters').FilterState} FilterState
* @typedef {import('../util/build-thread').Thread} Thread * @typedef {import('../util/build-thread').Thread} Thread
*/ */
/**
* @typedef FilterState
* @prop {string|null} filterQuery
* @prop {boolean} focusActive
* @prop {boolean} focusConfigured
* @prop {string|null} focusDisplayName
* @prop {number} forcedVisibleCount
* @prop {number} selectedCount
*/
/** /**
* @typedef FilterStatusPanelProps * @typedef FilterStatusPanelProps
* @prop {object} actionButton - * @prop {object} actionButton -
...@@ -267,7 +277,26 @@ FocusFilterStatus.propTypes = { ...@@ -267,7 +277,26 @@ FocusFilterStatus.propTypes = {
*/ */
export default function FilterStatus() { export default function FilterStatus() {
const rootThread = useRootThread(); const rootThread = useRootThread();
const filterState = useStore(store => store.filterState());
const focusState = useStore(store => store.focusState());
const forcedVisibleCount = useStore(
store => store.forcedVisibleAnnotations().length
);
const filterQuery = useStore(store => store.filterQuery());
const selectedCount = useStore(store => store.selectedAnnotations().length);
// Build a memoized state object with filter and selection details
// This will be used by the FilterStatus subcomponents
const filterState = useMemo(() => {
return {
filterQuery,
focusActive: focusState.active,
focusConfigured: focusState.configured,
focusDisplayName: focusState.displayName,
forcedVisibleCount,
selectedCount,
};
}, [focusState, forcedVisibleCount, filterQuery, selectedCount]);
if (filterState.selectedCount > 0) { if (filterState.selectedCount > 0) {
return ( return (
......
...@@ -16,6 +16,14 @@ function getFilterState() { ...@@ -16,6 +16,14 @@ function getFilterState() {
}; };
} }
function getFocusState() {
return {
active: false,
configured: false,
focusDisplayName: '',
};
}
describe('FilterStatus', () => { describe('FilterStatus', () => {
let fakeStore; let fakeStore;
let fakeUseRootThread; let fakeUseRootThread;
...@@ -33,7 +41,11 @@ describe('FilterStatus', () => { ...@@ -33,7 +41,11 @@ describe('FilterStatus', () => {
annotationCount: sinon.stub(), annotationCount: sinon.stub(),
clearSelection: sinon.stub(), clearSelection: sinon.stub(),
directLinkedAnnotationId: sinon.stub(), directLinkedAnnotationId: sinon.stub(),
filterQuery: sinon.stub().returns(null),
filterState: sinon.stub().returns(getFilterState()), filterState: sinon.stub().returns(getFilterState()),
focusState: sinon.stub().returns(getFocusState()),
forcedVisibleAnnotations: sinon.stub().returns([]),
selectedAnnotations: sinon.stub().returns([]),
toggleFocusMode: sinon.stub(), toggleFocusMode: sinon.stub(),
}; };
...@@ -77,11 +89,8 @@ describe('FilterStatus', () => { ...@@ -77,11 +89,8 @@ describe('FilterStatus', () => {
}); });
context('(State 2): filtered by query', () => { context('(State 2): filtered by query', () => {
let filterState;
beforeEach(() => { beforeEach(() => {
filterState = { ...getFilterState(), filterQuery: 'foobar' }; fakeStore.filterQuery.returns('foobar');
fakeStore.filterState.returns(filterState);
fakeThreadUtil.countVisible.returns(1); fakeThreadUtil.countVisible.returns(1);
}); });
...@@ -105,15 +114,9 @@ describe('FilterStatus', () => { ...@@ -105,15 +114,9 @@ describe('FilterStatus', () => {
}); });
context('(State 3): filtered by query with force-expanded threads', () => { context('(State 3): filtered by query with force-expanded threads', () => {
let filterState;
beforeEach(() => { beforeEach(() => {
filterState = { fakeStore.filterQuery.returns('foobar');
...getFilterState(), fakeStore.forcedVisibleAnnotations.returns([1, 2, 3]);
filterQuery: 'foobar',
forcedVisibleCount: 3,
};
fakeStore.filterState.returns(filterState);
fakeThreadUtil.countVisible.returns(5); fakeThreadUtil.countVisible.returns(5);
}); });
...@@ -130,14 +133,8 @@ describe('FilterStatus', () => { ...@@ -130,14 +133,8 @@ describe('FilterStatus', () => {
}); });
context('(State 4): selected annotations', () => { context('(State 4): selected annotations', () => {
let filterState;
beforeEach(() => { beforeEach(() => {
filterState = { fakeStore.selectedAnnotations.returns([1]);
...getFilterState(),
selectedCount: 1,
};
fakeStore.filterState.returns(filterState);
}); });
it('should show the count of annotations', () => { it('should show the count of annotations', () => {
...@@ -145,8 +142,7 @@ describe('FilterStatus', () => { ...@@ -145,8 +142,7 @@ describe('FilterStatus', () => {
}); });
it('should pluralize annotations when necessary', () => { it('should pluralize annotations when necessary', () => {
filterState.selectedCount = 4; fakeStore.selectedAnnotations.returns([1, 2, 3, 4]);
fakeStore.filterState.returns(filterState);
assertFilterText(createComponent(), 'Showing 4 annotations'); assertFilterText(createComponent(), 'Showing 4 annotations');
}); });
...@@ -198,16 +194,12 @@ describe('FilterStatus', () => { ...@@ -198,16 +194,12 @@ describe('FilterStatus', () => {
}); });
context('(State 5): user-focus mode active', () => { context('(State 5): user-focus mode active', () => {
let filterState;
beforeEach(() => { beforeEach(() => {
filterState = { fakeStore.focusState.returns({
...getFilterState(), active: true,
focusActive: true, configured: true,
focusConfigured: true, displayName: 'Ebenezer Studentolog',
focusDisplayName: 'Ebenezer Studentolog', });
};
fakeStore.filterState.returns(filterState);
fakeThreadUtil.countVisible.returns(1); fakeThreadUtil.countVisible.returns(1);
}); });
...@@ -244,17 +236,13 @@ describe('FilterStatus', () => { ...@@ -244,17 +236,13 @@ describe('FilterStatus', () => {
}); });
context('(State 6): user-focus mode active, filtered by query', () => { context('(State 6): user-focus mode active, filtered by query', () => {
let filterState;
beforeEach(() => { beforeEach(() => {
filterState = { fakeStore.focusState.returns({
...getFilterState(), active: true,
focusActive: true, configured: true,
focusConfigured: true, displayName: 'Ebenezer Studentolog',
focusDisplayName: 'Ebenezer Studentolog', });
filterQuery: 'biscuits', fakeStore.filterQuery.returns('biscuits');
};
fakeStore.filterState.returns(filterState);
fakeThreadUtil.countVisible.returns(1); fakeThreadUtil.countVisible.returns(1);
}); });
...@@ -289,18 +277,14 @@ describe('FilterStatus', () => { ...@@ -289,18 +277,14 @@ describe('FilterStatus', () => {
context( context(
'(State 7): user-focus mode active, filtered by query, force-expanded threads', '(State 7): user-focus mode active, filtered by query, force-expanded threads',
() => { () => {
let filterState;
beforeEach(() => { beforeEach(() => {
filterState = { fakeStore.focusState.returns({
...getFilterState(), active: true,
focusActive: true, configured: true,
focusConfigured: true, displayName: 'Ebenezer Studentolog',
focusDisplayName: 'Ebenezer Studentolog', });
filterQuery: 'biscuits', fakeStore.filterQuery.returns('biscuits');
forcedVisibleCount: 2, fakeStore.forcedVisibleAnnotations.returns([1, 2]);
};
fakeStore.filterState.returns(filterState);
fakeThreadUtil.countVisible.returns(3); fakeThreadUtil.countVisible.returns(3);
}); });
...@@ -318,17 +302,13 @@ describe('FilterStatus', () => { ...@@ -318,17 +302,13 @@ describe('FilterStatus', () => {
); );
context('(State 8): user-focus mode active, selected annotations', () => { context('(State 8): user-focus mode active, selected annotations', () => {
let filterState;
beforeEach(() => { beforeEach(() => {
filterState = { fakeStore.focusState.returns({
...getFilterState(), active: true,
focusActive: true, configured: true,
focusConfigured: true, displayName: 'Ebenezer Studentolog',
focusDisplayName: 'Ebenezer Studentolog', });
selectedCount: 2, fakeStore.selectedAnnotations.returns([1, 2]);
};
fakeStore.filterState.returns(filterState);
}); });
it('should ignore user and display selected annotations', () => { it('should ignore user and display selected annotations', () => {
...@@ -345,17 +325,13 @@ describe('FilterStatus', () => { ...@@ -345,17 +325,13 @@ describe('FilterStatus', () => {
}); });
context('(State 9): user-focus mode active, force-expanded threads', () => { context('(State 9): user-focus mode active, force-expanded threads', () => {
let filterState;
beforeEach(() => { beforeEach(() => {
filterState = { fakeStore.focusState.returns({
...getFilterState(), active: true,
focusActive: true, configured: true,
focusConfigured: true, displayName: 'Ebenezer Studentolog',
focusDisplayName: 'Ebenezer Studentolog', });
forcedVisibleCount: 3, fakeStore.forcedVisibleAnnotations.returns([1, 2, 3]);
};
fakeStore.filterState.returns(filterState);
fakeThreadUtil.countVisible.returns(7); fakeThreadUtil.countVisible.returns(7);
}); });
...@@ -367,8 +343,7 @@ describe('FilterStatus', () => { ...@@ -367,8 +343,7 @@ describe('FilterStatus', () => {
}); });
it('should handle cases when there are no focused-user annotations', () => { it('should handle cases when there are no focused-user annotations', () => {
filterState = { ...filterState, forcedVisibleCount: 7 }; fakeStore.forcedVisibleAnnotations.returns([1, 2, 3, 4, 5, 6, 7]);
fakeStore.filterState.returns(filterState);
assertFilterText( assertFilterText(
createComponent(), createComponent(),
'No annotations by Ebenezer Studentolog (and 7 more)' 'No annotations by Ebenezer Studentolog (and 7 more)'
...@@ -385,16 +360,12 @@ describe('FilterStatus', () => { ...@@ -385,16 +360,12 @@ describe('FilterStatus', () => {
}); });
context('(State 10): user-focus mode configured but inactive', () => { context('(State 10): user-focus mode configured but inactive', () => {
let filterState;
beforeEach(() => { beforeEach(() => {
filterState = { fakeStore.focusState.returns({
...getFilterState(), active: false,
focusActive: false, configured: true,
focusConfigured: true, displayName: 'Ebenezer Studentolog',
focusDisplayName: 'Ebenezer Studentolog', });
};
fakeStore.filterState.returns(filterState);
fakeThreadUtil.countVisible.returns(7); fakeThreadUtil.countVisible.returns(7);
}); });
......
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { actionTypes } from '../util'; import { actionTypes } from '../util';
import { trueKeys } from '../../util/collections';
/** /**
* @typedef FocusConfig * @typedef FocusConfig
...@@ -24,7 +23,7 @@ import { trueKeys } from '../../util/collections'; ...@@ -24,7 +23,7 @@ import { trueKeys } from '../../util/collections';
*/ */
/** /**
* @typedef FocusState * @typedef Focus
* @prop {boolean} configured - Focus config contains valid `user` and * @prop {boolean} configured - Focus config contains valid `user` and
* is good to go * is good to go
* @prop {boolean} active - Focus mode is currently applied * @prop {boolean} active - Focus mode is currently applied
...@@ -32,14 +31,10 @@ import { trueKeys } from '../../util/collections'; ...@@ -32,14 +31,10 @@ import { trueKeys } from '../../util/collections';
*/ */
/** /**
* TODO potentially split into `FilterState` and `FocusState`? * @typedef FocusState
* @typedef FilterState * @prop {boolean} active
* @prop {string|null} filterQuery * @prop {boolean} configured
* @prop {boolean} focusActive * @prop {string} displayName
* @prop {boolean} focusConfigured
* @prop {string|null} focusDisplayName
* @prop {number} forcedVisibleCount
* @prop {number} selectedCount
*/ */
/** /**
...@@ -54,7 +49,7 @@ import { trueKeys } from '../../util/collections'; ...@@ -54,7 +49,7 @@ import { trueKeys } from '../../util/collections';
* and may be toggled via `toggleFocusMode`. * and may be toggled via `toggleFocusMode`.
* *
* @param {FocusConfig} focusConfig * @param {FocusConfig} focusConfig
* @return {FocusState} * @return {Focus}
*/ */
function setFocus(focusConfig) { function setFocus(focusConfig) {
const focusDefaultState = { const focusDefaultState = {
...@@ -172,39 +167,17 @@ function filterQuery(state) { ...@@ -172,39 +167,17 @@ function filterQuery(state) {
} }
/** /**
* Returns the display name for a user or the userid * Summary of focus state
* if display name is not present. If both are missing
* then this returns an empty string.
* *
* @return {string} * @type {(state: any) => FocusState}
*/ */
function focusModeUserPrettyName(state) { const focusState = createSelector(
if (!state.focus.configured) { state => state.focus,
return ''; focus => {
}
return state.focus.user.displayName;
}
/**
* Summary of applied filters
*
* @type {(state: any) => FilterState}
*/
const filterState = createSelector(
rootState => rootState.selection,
rootState => rootState.filters,
(selection, filters) => {
// TODO FIXME
const forcedVisibleCount = trueKeys(selection.forcedVisible).length;
// TODO FIXME
const selectedCount = trueKeys(selection.selected).length;
return { return {
filterQuery: filters.query, active: focus.active,
focusActive: filters.focus.active, configured: focus.configured,
focusConfigured: filters.focus.configured, displayName: focus.configured ? focus.user.displayName : '',
focusDisplayName: focusModeUserPrettyName(filters),
forcedVisibleCount,
selectedCount,
}; };
} }
); );
...@@ -219,9 +192,7 @@ const filterState = createSelector( ...@@ -219,9 +192,7 @@ const filterState = createSelector(
* *
* // Selectors * // Selectors
* @prop {() => string|null} filterQuery * @prop {() => string|null} filterQuery
* * @prop {() => FocusState} focusState
* // Root Selectors
* @prop {() => FilterState} filterState
* *
*/ */
...@@ -236,8 +207,6 @@ export default { ...@@ -236,8 +207,6 @@ export default {
}, },
selectors: { selectors: {
filterQuery, filterQuery,
}, focusState,
rootSelectors: {
filterState,
}, },
}; };
...@@ -94,44 +94,24 @@ describe('sidebar/store/modules/filters', () => { ...@@ -94,44 +94,24 @@ describe('sidebar/store/modules/filters', () => {
}); });
describe('selectors', () => { describe('selectors', () => {
describe('filterState', () => { describe('focusState', () => {
it('returns the current filter query', () => {
store.setFilterQuery('doodah, doodah');
assert.equal(store.filterState().filterQuery, 'doodah, doodah');
});
it('returns user focus information', () => { it('returns user focus information', () => {
store.changeFocusModeUser({ store.changeFocusModeUser({
username: 'filbert', username: 'filbert',
displayName: 'Pantomime Nutball', displayName: 'Pantomime Nutball',
}); });
const filterState = store.filterState(); const focusState = store.focusState();
assert.isTrue(filterState.focusActive); assert.isTrue(focusState.active);
assert.isTrue(filterState.focusConfigured); assert.isTrue(focusState.configured);
assert.equal(filterState.focusDisplayName, 'Pantomime Nutball'); assert.equal(focusState.displayName, 'Pantomime Nutball');
});
it('returns a count of forced-visible annotations', () => {
store.setForcedVisible('kaboodle', true);
store.setForcedVisible('stampy', false);
assert.equal(store.filterState().forcedVisibleCount, 1);
});
it('returns a count of selected annotations', () => {
store.selectAnnotations(['tabulature', 'felonious']);
assert.equal(store.filterState().selectedCount, 2);
}); });
it('returns empty filter states when no filters active', () => { it('returns empty focus values when no focus is configured or set', () => {
const filterState = store.filterState(); const focusState = store.focusState();
assert.isFalse(filterState.focusActive); assert.isFalse(focusState.active);
assert.isFalse(filterState.focusConfigured); assert.isFalse(focusState.configured);
assert.isEmpty(filterState.focusDisplayName); assert.isEmpty(focusState.displayName);
assert.isNull(filterState.filterQuery);
assert.equal(filterState.forcedVisibleCount, 0);
assert.equal(filterState.selectedCount, 0);
}); });
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment