import { useEffect , useState , useRef , useCallback , createContext , useContext } from 'react' ; import { Handle , RepoFrontend , HyperfileUrl , Doc } from 'hypermerge' ; import * as Hyperfile from './hyperfile' ; import { HypermergeUrl } from './ShareLink' ; import SelfContext from './components/SelfContext' ; import { ContactDoc } from './components/content-types/contact' ; export type ChangeFn < T > = ( cb : ( doc : T ) => void ) => void ; type Cleanup = void | ( ( ) => void ) ; export const RepoContext = createContext < RepoFrontend | null > ( null ) ; export function useRepo ( ) { const repo = useContext ( RepoContext ) ; if ( ! repo ) { throw new Error ( 'Repo not available on RepoContext.' ) ; } return repo ; } export function useSelfId ( ) { return useContext ( SelfContext ) ; } export function useSelf ( ) { const selfId = useSelfId ( ) ; return useDocument < ContactDoc > ( selfId ) ; } remarks export function useHandle < D > ( url , cb ) { const repo = useRepo ( ) ; useEffect ( ( ) => { if ( ! url ) { return ( ) => { } ; } const handle = repo . open < D > ( url ) ; const cleanup = cb ( handle ) ; return ( ) => { handle . close ( ) ; cleanup && cleanup ( ) ; } ; } , [ url ] ) ; return repo ; } export function useDocument < D > ( url ) { const [ doc , setDoc ] = useState < Doc < D > | null > ( null ) ; const repo = useHandle < D > ( url , ( handle ) => { handle . subscribe ( ( doc ) => setDoc ( doc ) ) ; return ( ) => { setDoc ( null ) ; } ; } ) ; const change = useCallback ( ( cb ) => { if ( url ) { repo . change ( url , cb ) ; } } , [ url ] ) ; return [ doc , change ] ; } export function useDocumentReducer < D , A > ( url , reducer , deps ? ) { const [ doc , changeDoc ] = useDocument < D > ( url ) ; const dispatch = useCallback ( ( action ) => { changeDoc ( ( doc ) => { reducer ( doc , action ) ; } ) ; } , [ deps ] ) ; return [ doc , dispatch ] ; } export function useMessaging < M > ( url , onMsg ) { const [ sendObj , setSend ] = useState < { send : ( msg : M ) => void ; } > ( { send ( ) { } } ) ; const onMsgRef = useRef ( onMsg ) ; onMsgRef . current = onMsg ; useHandle ( url , ( handle ) => { handle . subscribeMessage ( ( msg ) => onMsgRef . current ( msg ) ) ; setSend ( { send : handle . message } ) ; return ( ) => { onMsgRef . current = ( ) => { } ; setSend ( { send ( ) { } } ) ; } ; } ) ; return sendObj . send ; } export function useHyperfile ( url ) { const [ data , setData ] = useState < Hyperfile . HyperfileResult | null > ( null ) ; useEffect ( ( ) => { data && setData ( null ) ; url && Hyperfile . fetch ( url ) . then ( ( [ data , mimeType , size ] ) => setData ( { data , mimeType , size } ) ) ; } , [ url ] ) ; return data ; } export function useHyperfileBuffer ( url ) { const [ buffered , setBuffered ] = useState < Hyperfile . BufferedHyperfileResult | null > ( null ) ; const data = useHyperfile ( url ) ; useEffect ( ( ) => { buffered && setBuffered ( null ) ; data && Hyperfile . streamToBuffer ( data . data ) . then ( ( buffer ) => setBuffered ( { data : buffer , mimeType : data . mimeType } ) ) ; } , [ data ] ) ; return buffered ; } export function useInterval ( ms , cb , deps ) { useEffect ( ( ) => { const id = setInterval ( cb , ms ) ; return ( ) => { clearInterval ( id ) ; } ; } , deps ) ; } remarks export function useTimeoutWhen ( cond , ms , cb ) { const reset = useRef ( ( ) => { } ) ; useEffect ( ( ) => { if ( ! cond ) { reset . current = ( ) => { } ; return ( ) => { } ; } let id ; reset . current = ( ) => { id != null && clearTimeout ( id ) ; id = setTimeout ( cb , ms ) ; } ; reset . current ( ) ; return ( ) => { clearTimeout ( id ) ; } ; } , [ cond ] ) ; return ( ) => reset . current ( ) ; } remarks export function useTimeouts < K > ( ms , onTimeout ) { const timeoutIds = useRef < Map < K , NodeJS . Timeout > > ( new Map ( ) ) ; const timedOut = useStaticCallback ( ( key ) => { timeoutIds . current . delete ( key ) ; onTimeout ( key ) ; } ) ; const bump = useCallback ( ( key ) => { const timeoutId = timeoutIds . current . get ( key ) ; if ( timeoutId ) clearTimeout ( timeoutId ) ; timeoutIds . current . set ( key , setTimeout ( ( ) => timedOut ( key ) , ms ) ) ; } , [ onTimeout ] ) ; return [ bump , timedOut ] ; } type InputEvent = React . ChangeEvent < HTMLInputElement > | React . KeyboardEvent < HTMLInputElement > ; remarks export function useConfirmableInput ( value , onConfirm ) { const [ str , setStr ] = useState < string | null > ( null ) ; function onChange ( e ) { setStr ( e . target . value ) ; } function onKeyDown ( e ) { switch ( e . key ) { case 'Enter' : if ( str !== null ) { onConfirm ( str ) ; setStr ( null ) ; } e . currentTarget . blur ( ) ; break ; case 'Backspace' : e . stopPropagation ( ) ; break ; case 'Escape' : e . currentTarget . blur ( ) ; setStr ( null ) ; break ; } } function onEvent ( e ) { switch ( e . type ) { case 'change' : onChange ( e as React . ChangeEvent < HTMLInputElement > ) ; break ; case 'keydown' : onKeyDown ( e as React . KeyboardEvent < HTMLInputElement > ) ; break ; } } return [ str != null ? str : value , onEvent ] ; } export function useEvent < K extends keyof HTMLElementEventMap > ( target , type , cb ) ; export function useEvent < K extends keyof DocumentEventMap > ( target , type , cb ) ; export function useEvent < K extends string > ( target , type , cb ) { useEffect ( ( ) => { target . addEventListener ( type , cb ) ; return ( ) => { target . removeEventListener ( type , cb ) ; } ; } , [ target , type , cb ] ) ; } remarks export function useStaticCallback < T extends ( ... args : any [ ] ) => any > ( callback ) { const cb = useRef < T > ( callback ) ; cb . current = callback ; return useCallback ( ( ... args ) => cb . current ( ... args ) , [ ] ) as T ; }