Commit 9f360335 authored by Robert Knight's avatar Robert Knight

Add types to filters store module

parent 6684d136
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { actionTypes } from '../util'; import { createStoreModule, makeAction } from '../create-store';
import { createStoreModule } from '../create-store';
/** /**
* Manage state pertaining to the filtering of annotations in the UI. * Manage state pertaining to the filtering of annotations in the UI.
...@@ -22,16 +21,11 @@ import { createStoreModule } from '../create-store'; ...@@ -22,16 +21,11 @@ import { createStoreModule } from '../create-store';
*/ */
/** /**
* * @typedef {import('../../../types/config').FocusConfig} FocusConfig
* @typedef {import('../../../types/config').SidebarSettings} SidebarSettings
* @typedef {import('../../../types/rpc').FocusUserInfo} FocusUserInfo * @typedef {import('../../../types/rpc').FocusUserInfo} FocusUserInfo
*/ */
/**
* Structure of focus-mode config, provided in settings (app config)
* @typedef FocusConfig
* @prop {FocusUserInfo} user
*/
/** /**
* @typedef FilterOption * @typedef FilterOption
* @prop {string} value - The machine-readable value of the option * @prop {string} value - The machine-readable value of the option
...@@ -44,7 +38,7 @@ import { createStoreModule } from '../create-store'; ...@@ -44,7 +38,7 @@ import { createStoreModule } from '../create-store';
*/ */
/** /**
* @typedef {Record<FilterKey, FilterOption>} Filters * @typedef {Record<FilterKey, FilterOption|undefined>} Filters
*/ */
/** /**
...@@ -54,6 +48,7 @@ import { createStoreModule } from '../create-store'; ...@@ -54,6 +48,7 @@ import { createStoreModule } from '../create-store';
* @prop {string} displayName * @prop {string} displayName
*/ */
/** @param {SidebarSettings} settings */
function initialState(settings) { function initialState(settings) {
const focusConfig = settings.focus || {}; const focusConfig = settings.focus || {};
return { return {
...@@ -89,28 +84,38 @@ function isValidFocusConfig(focusConfig) { ...@@ -89,28 +84,38 @@ function isValidFocusConfig(focusConfig) {
* @return {Filters} * @return {Filters}
*/ */
function focusFiltersFromConfig(focusConfig) { function focusFiltersFromConfig(focusConfig) {
if (!isValidFocusConfig(focusConfig)) { const user = focusConfig.user;
if (!user || !isValidFocusConfig(focusConfig)) {
return /** @type {Filters} */ ({}); return /** @type {Filters} */ ({});
} }
const userFilterValue =
focusConfig.user.username || focusConfig.user.userid || ''; const userFilterValue = user.username || user.userid || '';
return { return {
user: { user: {
value: userFilterValue, value: userFilterValue,
display: focusConfig.user.displayName || userFilterValue, display: user.displayName || userFilterValue,
}, },
}; };
} }
const reducers = { const reducers = {
CHANGE_FOCUS_MODE_USER: function (state, action) { /**
* @param {State} state
* @param {{ user: FocusUserInfo }} action
*/
CHANGE_FOCUS_MODE_USER(state, action) {
return { return {
focusActive: isValidFocusConfig({ user: action.user }), focusActive: isValidFocusConfig({ user: action.user }),
focusFilters: focusFiltersFromConfig({ user: action.user }), focusFilters: focusFiltersFromConfig({ user: action.user }),
}; };
}, },
SET_FILTER: function (state, action) { /**
* @param {State} state
* @param {{ filterName: FilterKey, filterOption: FilterOption }} action
*/
SET_FILTER(state, action) {
/** @type {Filters} */
const updatedFilters = { const updatedFilters = {
...state.filters, ...state.filters,
[action.filterName]: action.filterOption, [action.filterName]: action.filterOption,
...@@ -122,11 +127,19 @@ const reducers = { ...@@ -122,11 +127,19 @@ const reducers = {
return { filters: updatedFilters }; return { filters: updatedFilters };
}, },
SET_FILTER_QUERY: function (state, action) { /**
* @param {State} state
* @param {{ query: string }} action
*/
SET_FILTER_QUERY(state, action) {
return { query: action.query }; return { query: action.query };
}, },
SET_FOCUS_MODE: function (state, action) { /**
* @param {State} state
* @param {{ active?: boolean }} action
*/
SET_FOCUS_MODE(state, action) {
const active = action.active ?? !state.focusActive; const active = action.active ?? !state.focusActive;
return { return {
focusActive: active, focusActive: active,
...@@ -135,7 +148,7 @@ const reducers = { ...@@ -135,7 +148,7 @@ const reducers = {
// Actions defined in other modules // Actions defined in other modules
CLEAR_SELECTION: function () { CLEAR_SELECTION() {
return { return {
filters: /** @type {Filters} */ ({}), filters: /** @type {Filters} */ ({}),
focusActive: false, focusActive: false,
...@@ -144,8 +157,6 @@ const reducers = { ...@@ -144,8 +157,6 @@ const reducers = {
}, },
}; };
const actions = actionTypes(reducers);
// Action creators // Action creators
/** /**
...@@ -154,7 +165,7 @@ const actions = actionTypes(reducers); ...@@ -154,7 +165,7 @@ const actions = actionTypes(reducers);
* @param {FocusUserInfo} user - The user to focus on * @param {FocusUserInfo} user - The user to focus on
*/ */
function changeFocusModeUser(user) { function changeFocusModeUser(user) {
return { type: actions.CHANGE_FOCUS_MODE_USER, user }; return makeAction(reducers, 'CHANGE_FOCUS_MODE_USER', { user });
} }
/** /**
...@@ -162,30 +173,28 @@ function changeFocusModeUser(user) { ...@@ -162,30 +173,28 @@ function changeFocusModeUser(user) {
* @param {FilterOption} filterOption * @param {FilterOption} filterOption
*/ */
function setFilter(filterName, filterOption) { function setFilter(filterName, filterOption) {
/**
* @param {import('redux').Dispatch} dispatch
* @param {() => { filters: State }} getState
*/
return (dispatch, getState) => { return (dispatch, getState) => {
// If there is a filter conflict with focusFilters, deactivate focus // If there is a filter conflict with focusFilters, deactivate focus
// mode to prevent unintended collisions and let the new filter value // mode to prevent unintended collisions and let the new filter value
// take precedence. // take precedence.
if (getState().filters.focusFilters?.[filterName]) { if (getState().filters.focusFilters?.[filterName]) {
dispatch({ dispatch(makeAction(reducers, 'SET_FOCUS_MODE', { active: false }));
type: actions.SET_FOCUS_MODE,
active: false,
});
} }
dispatch({ dispatch(makeAction(reducers, 'SET_FILTER', { filterName, filterOption }));
type: actions.SET_FILTER,
filterName,
filterOption,
});
}; };
} }
/** Set the query used to filter displayed annotations. */ /**
* Set the query used to filter displayed annotations.
*
* @param {string} query
*/
function setFilterQuery(query) { function setFilterQuery(query) {
return { return makeAction(reducers, 'SET_FILTER_QUERY', { query });
type: actions.SET_FILTER_QUERY,
query,
};
} }
/** /**
...@@ -195,22 +204,18 @@ function setFilterQuery(query) { ...@@ -195,22 +204,18 @@ function setFilterQuery(query) {
* @param {boolean} [active] - Optional `active` state for focus mode * @param {boolean} [active] - Optional `active` state for focus mode
*/ */
function toggleFocusMode(active) { function toggleFocusMode(active) {
return { return makeAction(reducers, 'SET_FOCUS_MODE', { active });
type: actions.SET_FOCUS_MODE,
active,
};
} }
// Selectors // Selectors
/** @param {State} state */
function filterQuery(state) { function filterQuery(state) {
return state.query; return state.query;
} }
/** /**
* Summary of focus state * Summary of focus state
*
* @type {(state: any) => FocusState}
*/ */
const focusState = createSelector( const focusState = createSelector(
/** @param {State} state */ /** @param {State} state */
...@@ -230,12 +235,13 @@ const focusState = createSelector( ...@@ -230,12 +235,13 @@ const focusState = createSelector(
* Get all currently-applied filters. If focus is active, will also return * Get all currently-applied filters. If focus is active, will also return
* `focusFilters`, though `filters` will supersede in the case of key collisions. * `focusFilters`, though `filters` will supersede in the case of key collisions.
* `query` is not considered a "filter" in this context. * `query` is not considered a "filter" in this context.
*
* @return {Filters}
*/ */
const getFilters = createSelector( const getFilters = createSelector(
/** @param {State} state */
state => state.filters, state => state.filters,
/** @param {State} state */
state => state.focusActive, state => state.focusActive,
/** @param {State} state */
state => state.focusFilters, state => state.focusFilters,
(filters, focusActive, focusFilters) => { (filters, focusActive, focusFilters) => {
if (focusActive) { if (focusActive) {
...@@ -247,6 +253,9 @@ const getFilters = createSelector( ...@@ -247,6 +253,9 @@ const getFilters = createSelector(
/** /**
* Retrieve an applied filter by name/key * Retrieve an applied filter by name/key
*
* @param {State} state
* @param {FilterKey} filterName
*/ */
function getFilter(state, filterName) { function getFilter(state, filterName) {
const filters = getFilters(state); const filters = getFilters(state);
...@@ -257,23 +266,29 @@ function getFilter(state, filterName) { ...@@ -257,23 +266,29 @@ function getFilter(state, filterName) {
* Retrieve the (string) values of all currently-applied filters. * Retrieve the (string) values of all currently-applied filters.
*/ */
const getFilterValues = createSelector( const getFilterValues = createSelector(
/** @param {State} state */
state => getFilters(state), state => getFilters(state),
allFilters => { allFilters => {
/** @type {Record<string,string>} */ /** @type {Record<string,string>} */
const filterValues = {}; const filterValues = {};
Object.keys(allFilters).forEach( for (let [key, options] of Object.entries(allFilters)) {
filterKey => (filterValues[filterKey] = allFilters[filterKey].value) if (options) {
); filterValues[key] = options.value;
}
}
return filterValues; return filterValues;
} }
); );
/** @param {State} state */
function getFocusFilters(state) { function getFocusFilters(state) {
return state.focusFilters; return state.focusFilters;
} }
/** /**
* Are there currently any active (applied) filters? * Are there currently any active (applied) filters?
*
* @param {State} state
*/ */
function hasAppliedFilter(state) { function hasAppliedFilter(state) {
return !!(state.query || Object.keys(getFilters(state)).length); return !!(state.query || Object.keys(getFilters(state)).length);
......
...@@ -77,6 +77,13 @@ ...@@ -77,6 +77,13 @@
* *
*/ */
/**
* Structure of focus-mode config, provided in settings (app config)
*
* @typedef FocusConfig
* @prop {import('./rpc').FocusUserInfo} [user]
*/
/** /**
* Configuration provided by the annotator ("host frame") as * Configuration provided by the annotator ("host frame") as
* `ConfigFromAnnotator` OR by an ancestor ("embedder frame") as * `ConfigFromAnnotator` OR by an ancestor ("embedder frame") as
...@@ -88,6 +95,7 @@ ...@@ -88,6 +95,7 @@
* *
* @typedef ConfigFromHost * @typedef ConfigFromHost
* @prop {string} [annotations] - Direct-linked annotation ID * @prop {string} [annotations] - Direct-linked annotation ID
* @prop {FocusConfig} [focus]
* @prop {string} [group] - Direct-linked group ID * @prop {string} [group] - Direct-linked group ID
* @prop {string} [query] - Initial filter query * @prop {string} [query] - Initial filter query
* @prop {string} [appType] - Method used to load the client * @prop {string} [appType] - Method used to load the client
......
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