Update the API of React Hooks using Meteor's reactive system (#18226)
parent
d025656a6c
commit
e4e3c3ad02
@ -1,4 +1,7 @@ |
||||
import { useCallback } from 'react'; |
||||
|
||||
import { useReactiveValue } from '../../../../../client/hooks/useReactiveValue'; |
||||
import { Rooms } from '../../../../models/client'; |
||||
|
||||
export const useUserRoom = (rid, fields) => useReactiveValue(() => Rooms.findOne({ _id: rid }, { fields }), [rid, fields]); |
||||
export const useUserRoom = (rid, fields) => |
||||
useReactiveValue(useCallback(() => Rooms.findOne({ _id: rid }, { fields }), [rid, fields])); |
||||
|
||||
@ -1,4 +1,7 @@ |
||||
import { useCallback } from 'react'; |
||||
|
||||
import { useReactiveValue } from '../../../../../client/hooks/useReactiveValue'; |
||||
import { Subscriptions } from '../../../../models/client'; |
||||
|
||||
export const useUserSubscription = (rid, fields) => useReactiveValue(() => Subscriptions.findOne({ rid }, { fields }), [rid, fields]); |
||||
export const useUserSubscription = (rid, fields) => |
||||
useReactiveValue(useCallback(() => Subscriptions.findOne({ rid }, { fields }), [rid, fields])); |
||||
|
||||
@ -1,34 +0,0 @@ |
||||
import { createContext, useCallback, useContext } from 'react'; |
||||
|
||||
import { useObservableValue } from '../hooks/useObservableValue'; |
||||
|
||||
export const AuthorizationContext = createContext({ |
||||
hasPermission: () => {}, |
||||
hasAtLeastOnePermission: () => {}, |
||||
hasAllPermissions: () => {}, |
||||
hasRole: () => {}, |
||||
}); |
||||
|
||||
export const usePermission = (permission, scope) => { |
||||
const { hasPermission } = useContext(AuthorizationContext); |
||||
return useObservableValue(useCallback((listener) => |
||||
hasPermission(permission, scope, listener), [hasPermission, permission, scope])); |
||||
}; |
||||
|
||||
export const useAtLeastOnePermission = (permissions, scope) => { |
||||
const { hasAtLeastOnePermission } = useContext(AuthorizationContext); |
||||
return useObservableValue(useCallback((listener) => |
||||
hasAtLeastOnePermission(permissions, scope, listener), [hasAtLeastOnePermission, permissions, scope])); |
||||
}; |
||||
|
||||
export const useAllPermissions = (permissions, scope) => { |
||||
const { hasAllPermissions } = useContext(AuthorizationContext); |
||||
return useObservableValue(useCallback((listener) => |
||||
hasAllPermissions(permissions, scope, listener), [hasAllPermissions, permissions, scope])); |
||||
}; |
||||
|
||||
export const useRole = (role) => { |
||||
const { hasRole } = useContext(AuthorizationContext); |
||||
return useObservableValue(useCallback((listener) => |
||||
hasRole(role, listener), [hasRole, role])); |
||||
}; |
||||
@ -0,0 +1,79 @@ |
||||
import { createContext, useContext, useMemo } from 'react'; |
||||
import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; |
||||
|
||||
export type AuthorizationContextValue = { |
||||
queryPermission( |
||||
permission: string | Mongo.ObjectID, |
||||
scope?: string | Mongo.ObjectID |
||||
): Subscription<boolean>; |
||||
queryAtLeastOnePermission( |
||||
permission: (string | Mongo.ObjectID)[], |
||||
scope?: string | Mongo.ObjectID |
||||
): Subscription<boolean>; |
||||
queryAllPermissions( |
||||
permission: (string | Mongo.ObjectID)[], |
||||
scope?: string | Mongo.ObjectID |
||||
): Subscription<boolean>; |
||||
queryRole(role: string | Mongo.ObjectID): Subscription<boolean>; |
||||
}; |
||||
|
||||
export const AuthorizationContext = createContext<AuthorizationContextValue>({ |
||||
queryPermission: () => ({ |
||||
getCurrentValue: (): boolean => false, |
||||
subscribe: (): Unsubscribe => (): void => undefined, |
||||
}), |
||||
queryAtLeastOnePermission: () => ({ |
||||
getCurrentValue: (): boolean => false, |
||||
subscribe: (): Unsubscribe => (): void => undefined, |
||||
}), |
||||
queryAllPermissions: () => ({ |
||||
getCurrentValue: (): boolean => false, |
||||
subscribe: (): Unsubscribe => (): void => undefined, |
||||
}), |
||||
queryRole: () => ({ |
||||
getCurrentValue: (): boolean => false, |
||||
subscribe: (): Unsubscribe => (): void => undefined, |
||||
}), |
||||
}); |
||||
|
||||
export const usePermission = ( |
||||
permission: string | Mongo.ObjectID, |
||||
scope?: string | Mongo.ObjectID, |
||||
): boolean => { |
||||
const { queryPermission } = useContext(AuthorizationContext); |
||||
const subscription = useMemo( |
||||
() => queryPermission(permission, scope), |
||||
[queryPermission, permission, scope], |
||||
); |
||||
return useSubscription(subscription); |
||||
}; |
||||
|
||||
export const useAtLeastOnePermission = ( |
||||
permissions: (string | Mongo.ObjectID)[], |
||||
scope?: string | Mongo.ObjectID, |
||||
): boolean => { |
||||
const { queryAtLeastOnePermission } = useContext(AuthorizationContext); |
||||
const subscription = useMemo( |
||||
() => queryAtLeastOnePermission(permissions, scope), |
||||
[queryAtLeastOnePermission, permissions, scope], |
||||
); |
||||
return useSubscription(subscription); |
||||
}; |
||||
|
||||
export const useAllPermissions = ( |
||||
permissions: (string | Mongo.ObjectID)[], |
||||
scope?: string | Mongo.ObjectID, |
||||
): boolean => { |
||||
const { queryAllPermissions } = useContext(AuthorizationContext); |
||||
const subscription = useMemo( |
||||
() => queryAllPermissions(permissions, scope), |
||||
[queryAllPermissions, permissions, scope], |
||||
); |
||||
return useSubscription(subscription); |
||||
}; |
||||
|
||||
export const useRole = (role: string | Mongo.ObjectID): boolean => { |
||||
const { queryRole } = useContext(AuthorizationContext); |
||||
const subscription = useMemo(() => queryRole(role), [queryRole, role]); |
||||
return useSubscription(subscription); |
||||
}; |
||||
@ -1,18 +0,0 @@ |
||||
import { createContext, useCallback, useContext } from 'react'; |
||||
|
||||
import { useObservableValue } from '../hooks/useObservableValue'; |
||||
|
||||
export const SessionContext = createContext({ |
||||
get: () => {}, |
||||
set: () => {}, |
||||
}); |
||||
|
||||
export const useSession = (name) => { |
||||
const { get } = useContext(SessionContext); |
||||
return useObservableValue((listener) => get(name, listener)); |
||||
}; |
||||
|
||||
export const useSessionDispatch = (name) => { |
||||
const { set } = useContext(SessionContext); |
||||
return useCallback((value) => set(name, value), [set, name]); |
||||
}; |
||||
@ -0,0 +1,26 @@ |
||||
import { createContext, useCallback, useContext, useMemo } from 'react'; |
||||
import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; |
||||
|
||||
type SessionContextValue = { |
||||
query: (name: string) => Subscription<unknown>; |
||||
dispatch: (name: string, value: unknown) => void; |
||||
}; |
||||
|
||||
export const SessionContext = createContext<SessionContextValue>({ |
||||
query: () => ({ |
||||
getCurrentValue: (): undefined => undefined, |
||||
subscribe: (): Unsubscribe => (): void => undefined, |
||||
}), |
||||
dispatch: (): void => undefined, |
||||
}); |
||||
|
||||
export const useSession = (name: string): unknown => { |
||||
const { query } = useContext(SessionContext); |
||||
const subscription = useMemo(() => query(name), [query, name]); |
||||
return useSubscription(subscription); |
||||
}; |
||||
|
||||
export const useSessionDispatch = (name: string): ((name: string, value: unknown) => void) => { |
||||
const { dispatch } = useContext(SessionContext); |
||||
return useCallback((value) => dispatch(name, value), [dispatch, name]); |
||||
}; |
||||
@ -1,19 +0,0 @@ |
||||
import { createContext, useContext, useCallback } from 'react'; |
||||
|
||||
import { useObservableValue } from '../hooks/useObservableValue'; |
||||
|
||||
export const UserContext = createContext({ |
||||
userId: null, |
||||
user: null, |
||||
loginWithPassword: async () => {}, |
||||
getPreference: () => {}, |
||||
}); |
||||
|
||||
export const useUserId = () => useContext(UserContext).userId; |
||||
export const useUser = () => useContext(UserContext).user; |
||||
export const useLoginWithPassword = () => useContext(UserContext).loginWithPassword; |
||||
export const useUserPreference = (key, defaultValue = undefined) => { |
||||
const { getPreference } = useContext(UserContext); |
||||
return useObservableValue(useCallback((listener) => |
||||
getPreference(key, defaultValue, listener), [getPreference, key, defaultValue])); |
||||
}; |
||||
@ -0,0 +1,34 @@ |
||||
import { createContext, useContext, useMemo } from 'react'; |
||||
import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; |
||||
|
||||
type UserContextValue = { |
||||
userId: string | null; |
||||
user: Meteor.User | null; |
||||
loginWithPassword: (user: string | object, password: string) => Promise<void>; |
||||
queryPreference: <T>(key: string | Mongo.ObjectID, defaultValue?: T) => Subscription<T | undefined>; |
||||
}; |
||||
|
||||
export const UserContext = createContext<UserContextValue>({ |
||||
userId: null, |
||||
user: null, |
||||
loginWithPassword: async () => undefined, |
||||
queryPreference: () => ({ |
||||
getCurrentValue: (): undefined => undefined, |
||||
subscribe: (): Unsubscribe => (): void => undefined, |
||||
}), |
||||
}); |
||||
|
||||
export const useUserId = (): string | Mongo.ObjectID | null => |
||||
useContext(UserContext).userId; |
||||
|
||||
export const useUser = (): Meteor.User | null => |
||||
useContext(UserContext).user; |
||||
|
||||
export const useLoginWithPassword = (): ((user: string | object, password: string) => Promise<void>) => |
||||
useContext(UserContext).loginWithPassword; |
||||
|
||||
export const useUserPreference = <T>(key: string | Mongo.ObjectID, defaultValue?: T): T | undefined => { |
||||
const { queryPreference } = useContext(UserContext); |
||||
const subscription = useMemo(() => queryPreference(key, defaultValue), [queryPreference, key, defaultValue]); |
||||
return useSubscription(subscription); |
||||
}; |
||||
@ -1,31 +0,0 @@ |
||||
/* eslint-disable react-hooks/rules-of-hooks */ |
||||
import { useState, useCallback } from 'react'; |
||||
|
||||
import { capitalize } from '../helpers/capitalize'; |
||||
|
||||
const getValue = (e) => (e.currentTarget ? e.currentTarget.value : e); |
||||
|
||||
export const useForm = (obj) => { |
||||
const resetCallbacks = []; |
||||
const hasUnsavedChanges = []; |
||||
// TODO: use useReducer hook as we can't assure that obj will have the same structure on each render
|
||||
const ret = Object.keys(obj).sort().reduce((ret, key) => { |
||||
const value = obj[key]; |
||||
const [data, setData] = useState(value); |
||||
|
||||
ret.values = { ...ret.values, [key]: data }; |
||||
ret.handlers = { ...ret.handlers, [`handle${ capitalize(key) }`]: useCallback(typeof value !== 'boolean' ? (e) => setData(getValue(e)) : () => setData(!data), [data]) }; |
||||
hasUnsavedChanges.push(JSON.stringify(value) !== JSON.stringify(data)); |
||||
resetCallbacks.push(() => setData(value)); |
||||
|
||||
return ret; |
||||
}, {}); |
||||
|
||||
ret.reset = () => { |
||||
resetCallbacks.forEach((reset) => reset()); |
||||
}; |
||||
|
||||
ret.hasUnsavedChanges = hasUnsavedChanges.filter(Boolean).length > 0; |
||||
|
||||
return ret; |
||||
}; |
||||
@ -0,0 +1,179 @@ |
||||
import { useCallback, useReducer, useMemo, ChangeEvent } from 'react'; |
||||
|
||||
import { capitalize } from '../helpers/capitalize'; |
||||
|
||||
type Field = { |
||||
name: string; |
||||
currentValue: unknown; |
||||
initialValue: unknown; |
||||
changed: boolean; |
||||
}; |
||||
|
||||
type FormState = { |
||||
fields: Field[]; |
||||
values: Record<string, unknown>; |
||||
hasUnsavedChanges: boolean; |
||||
}; |
||||
|
||||
type FormAction = { |
||||
(prevState: FormState): FormState; |
||||
}; |
||||
|
||||
type UseFormReturnType = { |
||||
values: Record<string, unknown>; |
||||
handlers: Record<string, (eventOrValue: ChangeEvent | unknown) => void>; |
||||
hasUnsavedChanges: boolean; |
||||
commit: () => void; |
||||
reset: () => void; |
||||
}; |
||||
|
||||
const reduceForm = (state: FormState, action: FormAction): FormState => { |
||||
console.time('reduceForm'); |
||||
const newState = action(state); |
||||
console.timeEnd('reduceForm'); |
||||
return newState; |
||||
}; |
||||
|
||||
const initForm = (initialValues: Record<string, unknown>): FormState => { |
||||
const fields = []; |
||||
|
||||
for (const [fieldName, initialValue] of Object.entries(initialValues)) { |
||||
fields.push({ |
||||
name: fieldName, |
||||
currentValue: initialValue, |
||||
initialValue, |
||||
changed: false, |
||||
}); |
||||
} |
||||
|
||||
return { |
||||
fields, |
||||
values: { ...initialValues }, |
||||
hasUnsavedChanges: false, |
||||
}; |
||||
}; |
||||
|
||||
const valueChanged = (fieldName: string, newValue: unknown): FormAction => |
||||
(state: FormState): FormState => { |
||||
let { fields } = state; |
||||
const field = fields.find(({ name }) => name === fieldName); |
||||
|
||||
if (!field || field.currentValue === newValue) { |
||||
return state; |
||||
} |
||||
|
||||
const newField = { |
||||
...field, |
||||
currentValue: newValue, |
||||
changed: JSON.stringify(newValue) !== JSON.stringify(field.initialValue), |
||||
}; |
||||
|
||||
fields = state.fields.map((field) => { |
||||
if (field.name === fieldName) { |
||||
return newField; |
||||
} |
||||
|
||||
return field; |
||||
}); |
||||
|
||||
return { |
||||
...state, |
||||
fields, |
||||
values: { |
||||
...state.values, |
||||
[newField.name]: newField.currentValue, |
||||
}, |
||||
hasUnsavedChanges: newField.changed || fields.some((field) => field.changed), |
||||
}; |
||||
}; |
||||
|
||||
const formCommitted = (): FormAction => |
||||
(state: FormState): FormState => ({ |
||||
...state, |
||||
fields: state.fields.map((field) => ({ |
||||
...field, |
||||
initialValue: field.currentValue, |
||||
changed: false, |
||||
})), |
||||
hasUnsavedChanges: false, |
||||
}); |
||||
|
||||
const formReset = (): FormAction => |
||||
(state: FormState): FormState => ({ |
||||
...state, |
||||
fields: state.fields.map((field) => ({ |
||||
...field, |
||||
currentValue: field.initialValue, |
||||
changed: false, |
||||
})), |
||||
values: state.fields.reduce((values, field) => ({ |
||||
...values, |
||||
[field.name]: field.initialValue, |
||||
}), {}), |
||||
hasUnsavedChanges: false, |
||||
}); |
||||
|
||||
const isChangeEvent = (x: any): x is ChangeEvent => |
||||
(typeof x === 'object' || typeof x === 'function') && typeof x?.currentTarget !== 'undefined'; |
||||
|
||||
const getValue = (eventOrValue: ChangeEvent | unknown): unknown => { |
||||
if (!isChangeEvent(eventOrValue)) { |
||||
return eventOrValue; |
||||
} |
||||
|
||||
const target = eventOrValue.currentTarget; |
||||
|
||||
if (target instanceof HTMLTextAreaElement) { |
||||
return target.value; |
||||
} |
||||
|
||||
if (target instanceof HTMLSelectElement) { |
||||
return target.value; |
||||
} |
||||
|
||||
if (!(target instanceof HTMLInputElement)) { |
||||
return undefined; |
||||
} |
||||
|
||||
if (target.type === 'checkbox' || target.type === 'radio') { |
||||
return target.checked; |
||||
} |
||||
|
||||
return target.value; |
||||
}; |
||||
|
||||
export const useForm = ( |
||||
initialValues: Record<string, unknown>, |
||||
onChange: ((...args: unknown[]) => void) = (): void => undefined, |
||||
): UseFormReturnType => { |
||||
const [state, dispatch] = useReducer(reduceForm, initialValues, initForm); |
||||
|
||||
const commit = useCallback(() => { |
||||
dispatch(formCommitted()); |
||||
}, []); |
||||
|
||||
const reset = useCallback(() => { |
||||
dispatch(formReset()); |
||||
}, []); |
||||
|
||||
const handlers = useMemo(() => state.fields.reduce((handlers, { name, initialValue }) => ({ |
||||
...handlers, |
||||
[`handle${ capitalize(name) }`]: (eventOrValue: ChangeEvent | unknown): void => { |
||||
const newValue = getValue(eventOrValue); |
||||
dispatch(valueChanged(name, newValue)); |
||||
onChange({ |
||||
initialValue, |
||||
value: newValue, |
||||
key: name, |
||||
}); |
||||
}, |
||||
}), {}), [onChange, state.fields]); |
||||
|
||||
return { |
||||
handlers, |
||||
values: state.values, |
||||
hasUnsavedChanges: state.hasUnsavedChanges, |
||||
commit, |
||||
reset, |
||||
}; |
||||
}; |
||||
@ -1,22 +0,0 @@ |
||||
import { useEffect, useState } from 'react'; |
||||
|
||||
export const useObservableValue = (getValue) => { |
||||
const [value, setValue] = useState(() => getValue()); |
||||
|
||||
useEffect(() => { |
||||
let mounted = true; |
||||
|
||||
const unsubscribe = getValue((newValue) => { |
||||
if (mounted) { |
||||
setValue(newValue); |
||||
} |
||||
}); |
||||
|
||||
return () => { |
||||
mounted = false; |
||||
typeof unsubscribe === 'function' && unsubscribe(); |
||||
}; |
||||
}, [getValue]); |
||||
|
||||
return value; |
||||
}; |
||||
@ -1,30 +0,0 @@ |
||||
import { Tracker } from 'meteor/tracker'; |
||||
import { useCallback, useMemo, useRef } from 'react'; |
||||
import { useSubscription } from 'use-subscription'; |
||||
import { Mongo } from 'meteor/mongo'; |
||||
|
||||
const allQuery = {}; |
||||
|
||||
export const useQuery = <T>(collection: Mongo.Collection<T>, query: object = allQuery, options?: object): T[] => { |
||||
const queryHandle = useMemo(() => collection.find(query, options), [collection, query, options]); |
||||
const resultRef = useRef<T[]>([]); |
||||
resultRef.current = Tracker.nonreactive(() => queryHandle.fetch()) as unknown as T[]; |
||||
|
||||
const subscribe = useCallback((cb) => { |
||||
const computation = Tracker.autorun(() => { |
||||
resultRef.current = queryHandle.fetch(); |
||||
cb(resultRef.current); |
||||
}); |
||||
|
||||
return (): void => { |
||||
computation.stop(); |
||||
}; |
||||
}, [queryHandle]); |
||||
|
||||
const subscription = useMemo(() => ({ |
||||
getCurrentValue: (): T[] => resultRef.current ?? [], |
||||
subscribe, |
||||
}), [subscribe]); |
||||
|
||||
return useSubscription(subscription); |
||||
}; |
||||
@ -1,28 +0,0 @@ |
||||
import { Tracker } from 'meteor/tracker'; |
||||
import { useCallback } from 'react'; |
||||
import { Subscription, Unsubscribe } from 'use-subscription'; |
||||
|
||||
interface ISubscriptionFactory<T> { |
||||
(...args: any[]): Subscription<T>; |
||||
} |
||||
|
||||
export const useReactiveSubscriptionFactory = <T>(fn: (...args: any[]) => T): ISubscriptionFactory<T> => |
||||
useCallback<ISubscriptionFactory<T>>((...args: any[]) => { |
||||
const fnWithArgs = (): T => fn(...args); |
||||
|
||||
return { |
||||
getCurrentValue: (): T => Tracker.nonreactive(fnWithArgs) as unknown as T, |
||||
subscribe: (callback): Unsubscribe => { |
||||
const computation = Tracker.autorun((c) => { |
||||
fnWithArgs(); |
||||
if (!c.firstRun) { |
||||
callback(); |
||||
} |
||||
}); |
||||
|
||||
return (): void => { |
||||
computation.stop(); |
||||
}; |
||||
}, |
||||
}; |
||||
}, [fn]); |
||||
@ -1,19 +0,0 @@ |
||||
import { useState, useEffect } from 'react'; |
||||
import { Tracker } from 'meteor/tracker'; |
||||
|
||||
export const useReactiveValue = (getValue, deps = []) => { |
||||
const [value, setValue] = useState(() => Tracker.nonreactive(getValue)); |
||||
|
||||
useEffect(() => { |
||||
const computation = Tracker.autorun(() => { |
||||
const newValue = getValue(); |
||||
setValue(() => newValue); |
||||
}); |
||||
|
||||
return () => { |
||||
computation.stop(); |
||||
}; |
||||
}, deps); |
||||
|
||||
return value; |
||||
}; |
||||
@ -0,0 +1,35 @@ |
||||
import { Tracker } from 'meteor/tracker'; |
||||
import { useMemo } from 'react'; |
||||
import { Subscription, Unsubscribe, useSubscription } from 'use-subscription'; |
||||
|
||||
export const useReactiveValue = <T>(computeCurrentValue: () => T): T => { |
||||
const subscription: Subscription<T> = useMemo(() => { |
||||
const callbacks = new Set<Unsubscribe>(); |
||||
|
||||
let currentValue: T; |
||||
|
||||
const computation = Tracker.autorun(() => { |
||||
currentValue = computeCurrentValue(); |
||||
callbacks.forEach((callback) => { |
||||
callback(); |
||||
}); |
||||
}); |
||||
|
||||
return { |
||||
getCurrentValue: (): T => currentValue, |
||||
subscribe: (callback): Unsubscribe => { |
||||
callbacks.add(callback); |
||||
|
||||
return (): void => { |
||||
callbacks.delete(callback); |
||||
|
||||
if (callbacks.size === 0) { |
||||
computation.stop(); |
||||
} |
||||
}; |
||||
}, |
||||
}; |
||||
}, [computeCurrentValue]); |
||||
|
||||
return useSubscription(subscription); |
||||
}; |
||||
@ -1,22 +0,0 @@ |
||||
import React from 'react'; |
||||
import { Meteor } from 'meteor/meteor'; |
||||
|
||||
import { |
||||
hasPermission, |
||||
hasAtLeastOnePermission, |
||||
hasAllPermission, |
||||
} from '../../app/authorization/client/hasPermission'; |
||||
import { AuthorizationContext } from '../contexts/AuthorizationContext'; |
||||
import { hasRole } from '../../app/authorization/client'; |
||||
import { createObservableFromReactive } from './createObservableFromReactive'; |
||||
|
||||
const contextValue = { |
||||
hasPermission: createObservableFromReactive(hasPermission), |
||||
hasAtLeastOnePermission: createObservableFromReactive(hasAtLeastOnePermission), |
||||
hasAllPermission: createObservableFromReactive(hasAllPermission), |
||||
hasRole: createObservableFromReactive((role) => hasRole(Meteor.userId(), role)), |
||||
}; |
||||
|
||||
export function AuthorizationProvider({ children }) { |
||||
return <AuthorizationContext.Provider children={children} value={contextValue} />; |
||||
} |
||||
@ -0,0 +1,31 @@ |
||||
import React, { FC } from 'react'; |
||||
import { Meteor } from 'meteor/meteor'; |
||||
|
||||
import { |
||||
hasPermission, |
||||
hasAtLeastOnePermission, |
||||
hasAllPermission, |
||||
hasRole, |
||||
} from '../../app/authorization/client'; |
||||
import { AuthorizationContext } from '../contexts/AuthorizationContext'; |
||||
import { createReactiveSubscriptionFactory } from './createReactiveSubscriptionFactory'; |
||||
|
||||
const contextValue = { |
||||
queryPermission: createReactiveSubscriptionFactory( |
||||
(permission, scope) => hasPermission(permission, scope), |
||||
), |
||||
queryAtLeastOnePermission: createReactiveSubscriptionFactory( |
||||
(permissions, scope) => hasAtLeastOnePermission(permissions, scope), |
||||
), |
||||
queryAllPermissions: createReactiveSubscriptionFactory( |
||||
(permissions, scope) => hasAllPermission(permissions, scope), |
||||
), |
||||
queryRole: createReactiveSubscriptionFactory( |
||||
(role) => hasRole(Meteor.userId(), role), |
||||
), |
||||
}; |
||||
|
||||
const AuthorizationProvider: FC = ({ children }) => |
||||
<AuthorizationContext.Provider children={children} value={contextValue} />; |
||||
|
||||
export default AuthorizationProvider; |
||||
@ -1,16 +0,0 @@ |
||||
import React from 'react'; |
||||
import { Session } from 'meteor/session'; |
||||
|
||||
import { SessionContext } from '../contexts/SessionContext'; |
||||
import { createObservableFromReactive } from './createObservableFromReactive'; |
||||
|
||||
const contextValue = { |
||||
get: createObservableFromReactive((name) => Session.get(name)), |
||||
set: (name, value) => { |
||||
Session.set(name, value); |
||||
}, |
||||
}; |
||||
|
||||
export function SessionProvider({ children }) { |
||||
return <SessionContext.Provider children={children} value={contextValue} />; |
||||
} |
||||
@ -0,0 +1,17 @@ |
||||
import React, { FC } from 'react'; |
||||
import { Session } from 'meteor/session'; |
||||
|
||||
import { SessionContext } from '../contexts/SessionContext'; |
||||
import { createReactiveSubscriptionFactory } from './createReactiveSubscriptionFactory'; |
||||
|
||||
const contextValue = { |
||||
query: createReactiveSubscriptionFactory<unknown>((name) => Session.get(name)), |
||||
dispatch: (name: string, value: unknown): void => { |
||||
Session.set(name, value); |
||||
}, |
||||
}; |
||||
|
||||
const SessionProvider: FC = ({ children }) => |
||||
<SessionContext.Provider children={children} value={contextValue} />; |
||||
|
||||
export default SessionProvider; |
||||
@ -1,34 +0,0 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import React, { useCallback } from 'react'; |
||||
|
||||
import { getUserPreference } from '../../app/utils/client'; |
||||
import { UserContext } from '../contexts/UserContext'; |
||||
import { useReactiveValue } from '../hooks/useReactiveValue'; |
||||
import { createObservableFromReactive } from './createObservableFromReactive'; |
||||
|
||||
const getPreference = createObservableFromReactive((key, defaultValue) => |
||||
getUserPreference(Meteor.userId(), key, defaultValue)); |
||||
|
||||
export function UserProvider({ children }) { |
||||
const userId = useReactiveValue(() => Meteor.userId(), []); |
||||
const user = useReactiveValue(() => Meteor.user(), []); |
||||
const loginWithPassword = useCallback((user, password) => new Promise((resolve, reject) => { |
||||
Meteor.loginWithPassword(user, password, (error, result) => { |
||||
if (error) { |
||||
reject(error); |
||||
return; |
||||
} |
||||
|
||||
resolve(result); |
||||
}); |
||||
}), []); |
||||
|
||||
const contextValue = useReactiveValue(() => ({ |
||||
userId, |
||||
user, |
||||
loginWithPassword, |
||||
getPreference, |
||||
}), [userId, user, loginWithPassword]); |
||||
|
||||
return <UserContext.Provider children={children} value={contextValue} />; |
||||
} |
||||
@ -0,0 +1,41 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import React, { useMemo, FC } from 'react'; |
||||
|
||||
import { getUserPreference } from '../../app/utils/client'; |
||||
import { UserContext } from '../contexts/UserContext'; |
||||
import { useReactiveValue } from '../hooks/useReactiveValue'; |
||||
import { createReactiveSubscriptionFactory } from './createReactiveSubscriptionFactory'; |
||||
|
||||
const getUserId = (): string | null => Meteor.userId(); |
||||
|
||||
const getUser = (): Meteor.User | null => Meteor.user(); |
||||
|
||||
const loginWithPassword = (user: string | object, password: string): Promise<void> => |
||||
new Promise((resolve, reject) => { |
||||
Meteor.loginWithPassword(user, password, (error: Error | Meteor.Error | Meteor.TypedError | undefined) => { |
||||
if (error) { |
||||
reject(error); |
||||
return; |
||||
} |
||||
|
||||
resolve(); |
||||
}); |
||||
}); |
||||
|
||||
const UserProvider: FC = ({ children }) => { |
||||
const userId = useReactiveValue(getUserId); |
||||
const user = useReactiveValue(getUser); |
||||
|
||||
const contextValue = useMemo(() => ({ |
||||
userId, |
||||
user, |
||||
loginWithPassword, |
||||
queryPreference: createReactiveSubscriptionFactory( |
||||
(key, defaultValue) => getUserPreference(userId, key, defaultValue), |
||||
), |
||||
}), [userId, user]); |
||||
|
||||
return <UserContext.Provider children={children} value={contextValue} />; |
||||
}; |
||||
|
||||
export default UserProvider; |
||||
@ -1,19 +0,0 @@ |
||||
import { Tracker } from 'meteor/tracker'; |
||||
|
||||
export const createObservableFromReactive = (fn) => (...fnArgs) => { |
||||
const args = fnArgs.slice(0, -1); |
||||
const listener = fnArgs.pop(); |
||||
|
||||
if (!listener) { |
||||
return Tracker.nonreactive(() => fn(...args)); |
||||
} |
||||
|
||||
const computation = Tracker.autorun(() => { |
||||
const value = fn(...args); |
||||
listener(value); |
||||
}); |
||||
|
||||
return () => { |
||||
computation.stop(); |
||||
}; |
||||
}; |
||||
@ -0,0 +1,39 @@ |
||||
import { Tracker } from 'meteor/tracker'; |
||||
import { Subscription, Unsubscribe } from 'use-subscription'; |
||||
|
||||
interface ISubscriptionFactory<T> { |
||||
(...args: any[]): Subscription<T>; |
||||
} |
||||
|
||||
export const createReactiveSubscriptionFactory = <T>( |
||||
computeCurrentValueWith: (...args: any[]) => T, |
||||
): ISubscriptionFactory<T> => |
||||
(...args: any[]): Subscription<T> => { |
||||
const computeCurrentValue = (): T => computeCurrentValueWith(...args); |
||||
|
||||
const callbacks = new Set<Unsubscribe>(); |
||||
|
||||
let currentValue: T; |
||||
|
||||
const computation = Tracker.autorun(() => { |
||||
currentValue = computeCurrentValue(); |
||||
callbacks.forEach((callback) => { |
||||
callback(); |
||||
}); |
||||
}); |
||||
|
||||
return { |
||||
getCurrentValue: (): T => currentValue, |
||||
subscribe: (callback): Unsubscribe => { |
||||
callbacks.add(callback); |
||||
|
||||
return (): void => { |
||||
callbacks.delete(callback); |
||||
|
||||
if (callbacks.size === 0) { |
||||
computation.stop(); |
||||
} |
||||
}; |
||||
}, |
||||
}; |
||||
}; |
||||
Loading…
Reference in new issue