Commit a9213ef3 authored by Alejandro Celaya's avatar Alejandro Celaya Committed by Alejandro Celaya

Migrate filters store module to TypeScript

parent 960eb172
import type { Dispatch } from 'redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import type { FocusConfig, SidebarSettings } from '../../../types/config';
import type { FocusUserInfo } from '../../../types/rpc';
import { createStoreModule, makeAction } from '../create-store'; import { createStoreModule, makeAction } from '../create-store';
/** /**
...@@ -7,7 +10,7 @@ import { createStoreModule, makeAction } from '../create-store'; ...@@ -7,7 +10,7 @@ import { createStoreModule, makeAction } from '../create-store';
* *
* There are a few sources of filtering that gets applied to annotations: * There are a few sources of filtering that gets applied to annotations:
* *
* - focusFilters: Filters defined by config/settings. Currently supports a * - focusFilters: Filters defined by config/settings. Currently, supports a
* user filter. Application of these filters may be toggled on/off by user * user filter. Application of these filters may be toggled on/off by user
* interaction (`focusActive`), but the values of these filters are set by * interaction (`focusActive`), but the values of these filters are set by
* config/settings or RPC (not by user directly). The value(s) of * config/settings or RPC (not by user directly). The value(s) of
...@@ -20,73 +23,62 @@ import { createStoreModule, makeAction } from '../create-store'; ...@@ -20,73 +23,62 @@ import { createStoreModule, makeAction } from '../create-store';
* (see `util/search-filter`) * (see `util/search-filter`)
*/ */
/** export type FilterOption = {
* @typedef {import('../../../types/config').FocusConfig} FocusConfig /** The machine-readable value of the option */
* @typedef {import('../../../types/config').SidebarSettings} SidebarSettings value: string;
* @typedef {import('../../../types/rpc').FocusUserInfo} FocusUserInfo /** The human-facing "pretty" value of the option */
*/ display: string;
};
/**
* @typedef FilterOption
* @prop {string} value - The machine-readable value of the option
* @prop {string} display - The human-facing "pretty" value of the option
*/
/** /**
* Valid/recognized filters * Valid/recognized filters
* @typedef {'user'} FilterKey
*/ */
type FilterKey = 'user';
/** type Filters = Partial<Record<FilterKey, FilterOption | undefined>>;
* @typedef {Record<FilterKey, FilterOption|undefined>} Filters
*/
/** type FocusState = {
* @typedef FocusState active: boolean;
* @prop {boolean} active configured: boolean;
* @prop {boolean} configured displayName: string;
* @prop {string} displayName };
*/
/** @param {SidebarSettings} settings */ export type State = {
function initialState(settings) { filters: Filters;
focusActive: boolean;
focusFilters: Filters;
query: string | null;
};
function initialState(settings: SidebarSettings): State {
const focusConfig = settings.focus || {}; const focusConfig = settings.focus || {};
return { return {
filters: /** @type {Filters} */ ({}), filters: {},
// immediately activate focus mode if there is a valid config // immediately activate focus mode if there is a valid config
focusActive: isValidFocusConfig(focusConfig), focusActive: isValidFocusConfig(focusConfig),
focusFilters: focusFiltersFromConfig(focusConfig), focusFilters: focusFiltersFromConfig(focusConfig),
/** @type {string|null} */
query: settings.query || null, query: settings.query || null,
}; };
} }
/** @typedef {ReturnType<typeof initialState>} State */
/** /**
* Given the provided focusConfig: is it a valid configuration for focus? * Given the provided focusConfig: is it a valid configuration for focus?
* At this time, a `user` filter is required. * At this time, a `user` filter is required.
*
* @param {FocusConfig} focusConfig
* @return {boolean}
*/ */
function isValidFocusConfig(focusConfig) { function isValidFocusConfig(focusConfig: FocusConfig): boolean {
return !!(focusConfig.user?.username || focusConfig.user?.userid); return !!(focusConfig.user?.username || focusConfig.user?.userid);
} }
/** /**
* Compose an object of keyed `FilterOption`s from the given `focusConfig`. * Compose an object of keyed `FilterOption`s from the given `focusConfig`.
* At present, this will create a `user` `FilterOption` if the config is valid. * At present, this will create a `user` `FilterOption` if the config is valid.
*
* @param {FocusConfig} focusConfig
* @return {Filters}
*/ */
function focusFiltersFromConfig(focusConfig) { function focusFiltersFromConfig(focusConfig: FocusConfig): Filters {
const user = focusConfig.user; const user = focusConfig.user;
if (!user || !isValidFocusConfig(focusConfig)) { if (!user || !isValidFocusConfig(focusConfig)) {
return /** @type {Filters} */ ({}); return {};
} }
const userFilterValue = user.username || user.userid || ''; const userFilterValue = user.username || user.userid || '';
...@@ -99,24 +91,18 @@ function focusFiltersFromConfig(focusConfig) { ...@@ -99,24 +91,18 @@ function focusFiltersFromConfig(focusConfig) {
} }
const reducers = { const reducers = {
/** CHANGE_FOCUS_MODE_USER(state: State, action: { user: FocusUserInfo }) {
* @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(
* @param {State} state state: State,
* @param {{ filterName: FilterKey, filterOption: FilterOption }} action action: { filterName: FilterKey; filterOption: FilterOption }
*/ ) {
SET_FILTER(state, action) { const updatedFilters: Filters = {
/** @type {Filters} */
const updatedFilters = {
...state.filters, ...state.filters,
[action.filterName]: action.filterOption, [action.filterName]: action.filterOption,
}; };
...@@ -127,19 +113,11 @@ const reducers = { ...@@ -127,19 +113,11 @@ const reducers = {
return { filters: updatedFilters }; return { filters: updatedFilters };
}, },
/** SET_FILTER_QUERY(state: State, action: { query: string }) {
* @param {State} state
* @param {{ query: string }} action
*/
SET_FILTER_QUERY(state, action) {
return { query: action.query }; return { query: action.query };
}, },
/** SET_FOCUS_MODE(state: State, action: { active?: boolean }) {
* @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,
...@@ -150,7 +128,7 @@ const reducers = { ...@@ -150,7 +128,7 @@ const reducers = {
CLEAR_SELECTION() { CLEAR_SELECTION() {
return { return {
filters: /** @type {Filters} */ ({}), filters: {},
focusActive: false, focusActive: false,
query: null, query: null,
}; };
...@@ -162,22 +140,14 @@ const reducers = { ...@@ -162,22 +140,14 @@ const reducers = {
/** /**
* Change the focused user filter and activate focus * Change the focused user filter and activate focus
* *
* @param {FocusUserInfo} user - The user to focus on * @param user - The user to focus on
*/ */
function changeFocusModeUser(user) { function changeFocusModeUser(user: FocusUserInfo) {
return makeAction(reducers, 'CHANGE_FOCUS_MODE_USER', { user }); return makeAction(reducers, 'CHANGE_FOCUS_MODE_USER', { user });
} }
/** function setFilter(filterName: FilterKey, filterOption: FilterOption) {
* @param {FilterKey} filterName return (dispatch: Dispatch, getState: () => { filters: State }) => {
* @param {FilterOption} filterOption
*/
function setFilter(filterName, filterOption) {
/**
* @param {import('redux').Dispatch} dispatch
* @param {() => { filters: State }} 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.
...@@ -190,27 +160,24 @@ function setFilter(filterName, filterOption) { ...@@ -190,27 +160,24 @@ function setFilter(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: string) {
return makeAction(reducers, 'SET_FILTER_QUERY', { query }); return makeAction(reducers, 'SET_FILTER_QUERY', { query });
} }
/** /**
* Toggle whether or not a (user-)focus mode is applied, either inverting the * Toggle whether a (user-)focus mode is applied, either inverting the
* current active state or setting it to a target `active` state, if provided. * current active state or setting it to a target `active` state, if provided.
* *
* @param {boolean} [active] - Optional `active` state for focus mode * @param active - Optional `active` state for focus mode
*/ */
function toggleFocusMode(active) { function toggleFocusMode(active?: boolean) {
return makeAction(reducers, 'SET_FOCUS_MODE', { active }); return makeAction(reducers, 'SET_FOCUS_MODE', { active });
} }
// Selectors // Selectors
/** @param {State} state */ function filterQuery(state: State) {
function filterQuery(state) {
return state.query; return state.query;
} }
...@@ -218,11 +185,9 @@ function filterQuery(state) { ...@@ -218,11 +185,9 @@ function filterQuery(state) {
* Summary of focus state * Summary of focus state
*/ */
const focusState = createSelector( const focusState = createSelector(
/** @param {State} state */ (state: State) => state.focusActive,
state => state.focusActive, (state: State) => state.focusFilters,
/** @param {State} state */ (focusActive, focusFilters): FocusState => {
state => state.focusFilters,
(focusActive, focusFilters) => {
return { return {
active: focusActive, active: focusActive,
configured: !!focusFilters?.user, configured: !!focusFilters?.user,
...@@ -237,12 +202,9 @@ const focusState = createSelector( ...@@ -237,12 +202,9 @@ const focusState = createSelector(
* `query` is not considered a "filter" in this context. * `query` is not considered a "filter" in this context.
*/ */
const getFilters = createSelector( const getFilters = createSelector(
/** @param {State} state */ (state: State) => state.filters,
state => state.filters, (state: State) => state.focusActive,
/** @param {State} state */ (state: State) => state.focusFilters,
state => state.focusActive,
/** @param {State} state */
state => state.focusFilters,
(filters, focusActive, focusFilters) => { (filters, focusActive, focusFilters) => {
if (focusActive) { if (focusActive) {
return { ...focusFilters, ...filters }; return { ...focusFilters, ...filters };
...@@ -253,11 +215,8 @@ const getFilters = createSelector( ...@@ -253,11 +215,8 @@ 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: State, filterName: FilterKey) {
const filters = getFilters(state); const filters = getFilters(state);
return filters[filterName]; return filters[filterName];
} }
...@@ -266,12 +225,10 @@ function getFilter(state, filterName) { ...@@ -266,12 +225,10 @@ 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: State) => getFilters(state),
state => getFilters(state),
allFilters => { allFilters => {
/** @type {Record<string,string>} */ const filterValues: Record<string, string> = {};
const filterValues = {}; for (const [key, options] of Object.entries(allFilters)) {
for (let [key, options] of Object.entries(allFilters)) {
if (options) { if (options) {
filterValues[key] = options.value; filterValues[key] = options.value;
} }
...@@ -280,17 +237,14 @@ const getFilterValues = createSelector( ...@@ -280,17 +237,14 @@ const getFilterValues = createSelector(
} }
); );
/** @param {State} state */ function getFocusFilters(state: 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: State) {
return !!(state.query || Object.keys(getFilters(state)).length); return !!(state.query || Object.keys(getFilters(state)).length);
} }
......
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