Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Determine type of sidebar store automatically #4348

Merged
merged 1 commit into from
Mar 31, 2022
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
25 changes: 22 additions & 3 deletions src/sidebar/store/create-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,24 @@ function assignOnce(target, source) {
return Object.assign(target, source);
}

/**
* @template T
* @typedef {{[K in keyof T]: (x: T[K]) => void }} MapContravariant
Copy link
Member Author

@robertknight robertknight Mar 29, 2022

Choose a reason for hiding this comment

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

This is part of a trick taken from the linked GitHub issue below. I can attempt to explain this, but ah, I don't expect understanding of it. Grokking what the TupleToIntersection utility is used for is enough.

*/

/**
* Utility that turns a tuple type `[A, B, C]` into an intersection `A & B & C`.
*
* The implementation is magic adapted from
* https://github.com/microsoft/TypeScript/issues/28323. Roughly speaking it
* works by computing a type that could be assigned to any position in the
* tuple, which must be the intersection of all the tuple element types.
*
* @template T
* @template {Record<number, unknown>} [Temp=MapContravariant<T>]
* @typedef {Temp[number] extends (x: infer U) => unknown ? U : never} TupleToIntersection
*/

/**
* Create a Redux store from a set of _modules_.
*
Expand All @@ -173,10 +191,11 @@ function assignOnce(target, source) {
* `use-store.js`. This returns a proxy which enables UI components to observe
* what store state a component depends upon and re-render when it changes.
*
* @param {Module<any,any,any,any>[]} modules
* @template {readonly Module<any,any,any,any>[]} Modules
* @param {Modules} modules
* @param {any[]} [initArgs] - Arguments to pass to each state module's `initialState` function
* @param {any[]} [middleware] - List of additional Redux middlewares to use
* @return Store<any,any,any>
* @return {StoreFromModule<TupleToIntersection<Modules>>}
*/
export function createStore(modules, initArgs = [], middleware = []) {
/** @type {Record<string, unknown>} */
Expand Down Expand Up @@ -241,7 +260,7 @@ export function createStore(modules, initArgs = [], middleware = []) {
}
Object.assign(store, selectorMethods);

return store;
return /** @type {any} */ (store);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you help me understand this any?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is basically saying "trust me" to TS, since it can't figure out for itself that the object we've put together has the shape we promise.

}

/**
Expand Down
36 changes: 6 additions & 30 deletions src/sidebar/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,7 @@ import { sidebarPanelsModule } from './modules/sidebar-panels';
import { toastMessagesModule } from './modules/toast-messages';
import { viewerModule } from './modules/viewer';

/**
* @template M
* @typedef {import('./create-store').StoreFromModule<M>} StoreFromModule
*/

/**
* @typedef {StoreFromModule<activityModule> &
* StoreFromModule<annotationsModule> &
* StoreFromModule<defaultsModule> &
* StoreFromModule<directLinkedModule> &
* StoreFromModule<draftsModule> &
* StoreFromModule<filtersModule> &
* StoreFromModule<framesModule> &
* StoreFromModule<groupsModule> &
* StoreFromModule<linksModule> &
* StoreFromModule<realTimeUpdatesModule> &
* StoreFromModule<routeModule> &
* StoreFromModule<selectionModule> &
* StoreFromModule<sessionModule> &
* StoreFromModule<sidebarPanelsModule> &
* StoreFromModule<toastMessagesModule> &
* StoreFromModule<viewerModule>
* } SidebarStore
*/
/** @typedef {ReturnType<createSidebarStore>} SidebarStore */

/**
* Create the central state store for the sidebar application.
Expand All @@ -52,13 +29,14 @@ import { viewerModule } from './modules/viewer';
* [1] https://redux.js.org
*
* @param {import('../../types/config').SidebarSettings} settings
* @return {SidebarStore}
* @inject
*/
export function createSidebarStore(settings) {
const middleware = [debugMiddleware];

const modules = [
// `const` type gives `modules` a tuple type, which allows `createStore`
// to infer properties (eg. action and selector methods) of returned store.
const modules = /** @type {const} */ ([
activityModule,
annotationsModule,
defaultsModule,
Expand All @@ -75,8 +53,6 @@ export function createSidebarStore(settings) {
sidebarPanelsModule,
toastMessagesModule,
viewerModule,
];
return /** @type {SidebarStore} */ (
createStore(modules, [settings], middleware)
);
]);
return createStore(modules, [settings], middleware);
}