import { Subject } from "rxjs" ; import React , { useContext , useState } from "react" ; import { Provider } from "polar-shared/src/util/Providers" ; import { useComponentWillUnmount } from "../../hooks/ReactLifecycleHooks" ; import deepEquals from "react-fast-compare" ; function pick < T , K extends keyof T > ( value , keys ) { const result = { } ; for ( const key of keys ) { result [ key ] = value [ key ] ; } return result ; } /**
 * Hook that allows us to just pick specific keys from the store.
 */ function usePick < T , K extends keyof T > ( useStoreHook , keys ) { const store = useStoreHook ( ) ; const [ value , ] = React . useState < Pick < T , K > > ( pick ( store , keys ) ) ; return value ; } interface InternalObservableStore < V > { /**
     * The underlying rxjs observable for sending off updates to components.
     */ readonly subject : Subject < V > ; /**
     * The current value, used for the the initial render of each component
     * and to update it each time so that on useObservableStore we can
     * return the current value.
     */ current : V ; } export interface ObservableStore < V > { /**
     * The current value, used for the the initial render of each component.
     */ readonly current : V ; } export type SetStore < V > = ( value : V ) => void ; export type Store < V > = [ V , SetStore < V > ] ; interface IUseObservableStoreOpts < V , K extends keyof V , N > extends IUseStoreHookOpts < V , K , N > { readonly enableShallowEquals : boolean ; } type Dict = { [ key ] : any ; } ; namespace Equals { export function shallow ( a , b ) { if ( a === b ) { return true ; } const aKeys = Object . keys ( a ) ; const bKeys = Object . keys ( b ) ; if ( aKeys . length !== bKeys . length ) { return false ; } for ( let idx = 0 ; idx < aKeys . length ; ++ idx ) { if ( aKeys [ idx ] !== bKeys [ idx ] ) { return false ; } } for ( const key of aKeys ) { if ( a [ key ] !== b [ key ] ) { return false ; } } return true ; } export function deep ( a , b ) { return deepEquals ( a , b ) ; } } export function useObservableStore < V , K extends keyof V , N > ( context , keys , opts ) { const internalObservableStore = useContext ( context ) as InternalObservableStore < V > ; const [ value , setValue ] = useState < N > ( opts . mapper ( internalObservableStore . current ) ) ; const valueRef = React . useRef ( value ) ; React . useEffect ( ( ) => { const subscription = internalObservableStore . subject . subscribe ( ( nextValue ) => { function doUpdateValue ( newValue ) { setValue ( newValue ) ; valueRef . current = newValue ; } const currValue = valueRef . current ; function isEqual ( a , b ) { if ( opts . enableShallowEquals ) { return Equals . shallow ( a , b ) ; } return Equals . deep ( a , b ) ; } if ( keys ) { const nextValueMapped = opts . mapper ( pick ( nextValue , keys ) ) ; if ( ! isEqual ( currValue , nextValueMapped ) ) { if ( opts . filter && ! opts . filter ( currValue , nextValueMapped ) ) { return ; } return doUpdateValue ( nextValueMapped ) ; } else { } } else { const currValue = valueRef . current ; const mapped = opts . mapper ( nextValue ) ; if ( ! isEqual ( currValue , nextValue ) ) { return doUpdateValue ( mapped ) ; } } } ) ; return ( ) => { subscription . unsubscribe ( ) ; } ; } , [ internalObservableStore . subject , keys , opts ] ) ; return value ; } export type InternalStoreContext < V > = [ React . Context < ObservableStore < V > > , InternalObservableStore < V > ] ; export type StoreContext < V > = [ React . Context < ObservableStore < V > > , ObservableStore < V > ] ; function createInternalObservableStore < V > ( initialValue ) { const subject = new Subject < V > ( ) ; subject . next ( initialValue ) ; const store = { subject , current : initialValue } ; return store ; } function createObservableStoreContext < V > ( store ) { const context = React . createContext ( store as ObservableStore < V > ) ; return [ context , store ] ; } interface ObservableStoreProps < V > { readonly children : JSX . Element | Provider < JSX . Element > ; readonly store ? : V ; } interface ObservableStorePropsWithoutStore < V > { readonly children : JSX . Element | Provider < JSX . Element > ; } export type ObservableStoreProviderComponent < V > = React . FunctionComponent < ObservableStoreProps < V > > ; /**
 * Hook to listen to store changes. Use undefined to not filter for properties
 * but we don't recommend it.
 */ export type UseStoreHook < V , K extends keyof V , N > = ( keys : ReadonlyArray < K > | undefined , opts ? : IUseStoreHookOpts < V , K , N > ) => N ; export type UseContextHook < V > = ( ) => V ; /**
 * Tag interface just for documentation right now.
 */ export interface StoreMutator { } const DEFAULT_USE_STORE_MAPPER = < V , K extends keyof V , N > ( store ) => store ; export type UseStoreMapper < V , K extends keyof V , N > = ( store : Pick < V , K > ) => N ; const DEFAULT_USE_STORE_FILTER = < V , K extends keyof V > ( ) => true ; /**
 * Given a store, only render the hook it the value passes the filter.  This is
 * an option for when we want to avoid triggering too many sub-component
 * re-renders by filtering before RXJS is pushed.
 */ export type UseStoreFilter < V , K extends keyof V , N > = ( curr : N , next : N ) => boolean ; export interface IUseStoreHookOpts < V , K extends keyof V , N > { readonly debug ? : boolean ; readonly filter : UseStoreFilter < V , K , N > ; readonly mapper : UseStoreMapper < V , K , N > ; } export type ObservableStoreTuple < V , M extends StoreMutator , C > = [ ObservableStoreProviderComponent < V > , < K extends keyof V , N > ( keys : ReadonlyArray < K > | undefined , opts ? : IUseStoreHookOpts < V , K , N > ) => N , UseContextHook < C > , UseContextHook < M > ] ; /**
 * Create the callbacks. Called for every useCallbacks function so that we can
 * call hooks when the callbacks are created. This allows us to use other hooks
 * in our created callbacks.
 */ export type CallbacksFactory < V , M , C > = ( storeProvider : Provider < V > , setStore : SetStore < V > , mutator : M ) => C ; export type MutatorFactory < V , M > = ( storeProvider : Provider < V > , setStore : SetStore < V > ) => M ; export interface ObservableStoreOpts < V , M , C > { /**
     * The initial value for the store including defaults for every value.
     */ readonly initialValue : V ; readonly mutatorFactory : MutatorFactory < V , M > ; /**
     * Used to create high level callbacks that can be injected into your
     * components with a useXCallbacks hook to mutate your store.  The callback
     * is a singleton and never updated so component memo works.
     */ readonly callbacksFactory : CallbacksFactory < V , M , C > ; /**
     * Same functionality as callbacksFactory but used with mocks so that you
     * can work with components using the mock without complex initialization.
     *
     * Used with more production apps so other developers don't need to worry
     * about configuration of our component system.
     *
     * Usually these mocks should just print to the console or perform some
     * action to note that they were called and with what arguments.
     */ readonly mockCallbacksFactory ? : CallbacksFactory < V , M , C > ; /**
     * Do a shallow equals by default..
     */ readonly enableShallowEquals ? : boolean ; } type ComponentCallbacksFactory < C > = ( ) => C ; type InitialContextValues < V , M , C > = [ InternalObservableStore < V > , M , ComponentCallbacksFactory < C > , SetStore < V > ] ; /**
 * Create the initial values of the components we're working with (store
 * and callbacks)
 */ function createInitialContextValues < V , M , C > ( opts ) { const { initialValue , mutatorFactory , callbacksFactory } = opts ; const store = createInternalObservableStore ( initialValue ) ; const setStore = ( value ) => { store . current = value ; store . subject . next ( value ) ; } ; const storeProvider = ( ) => store . current ; const mutator = mutatorFactory ( storeProvider , setStore ) ; const componentCallbacksFactory = ( ) => callbacksFactory ( storeProvider , setStore , mutator ) ; return [ store , mutator , componentCallbacksFactory , setStore ] ; } export function createObservableStore < V , M , C > ( opts ) { const [ store , mutator , componentCallbacksFactory , setStore ] = createInitialContextValues ( opts ) ; const [ storeContext , ] = createObservableStoreContext < V > ( store ) ; const useStoreHook = < K extends keyof V , N > ( keys , storeHookOpts ) => { return useObservableStore ( storeContext , keys , { enableShallowEquals : opts . enableShallowEquals || false , filter : storeHookOpts . filter , mapper : storeHookOpts . mapper } ) ; } ; const callbacksContext = React . createContext < ComponentCallbacksFactory < C > > ( componentCallbacksFactory ) ; const useCallbacksHook = componentCallbacksFactory ; const mutatorContext = React . createContext < M > ( mutator ) ; const useMutatorHook = ( ) => { return React . useContext ( mutatorContext ) ; } ; const ObservableProviderComponent = ( props ) => { React . useMemo ( ( ) => { if ( props . store !== undefined ) { setStore ( props . store ) ; } } , [ props . store ] ) ; return ( < storeContext . Provider value = { store } > < callbacksContext . Provider value = { componentCallbacksFactory } > < mutatorContext . Provider value = { mutator } > { props . children } < /mutatorContext.Provider> < /callbacksContext.Provider> < /storeContext.Provider>); } ; return null ! ; }