|
| 1 | +import { |
| 2 | + ReactNode, |
| 3 | + RefObject, |
| 4 | + forwardRef, |
| 5 | + useContext, |
| 6 | + createContext, |
| 7 | + memo, |
| 8 | + Context as ReactContext, |
| 9 | + useEffect, |
| 10 | + useRef, |
| 11 | + createPortal, |
| 12 | + ComponentProps, |
| 13 | + ForwardedRef, |
| 14 | + useImperativeHandle, |
| 15 | + useMemo, |
| 16 | +} from "preact/compat"; |
| 17 | +import { App, Modal as ObsidianModal } from "obsidian"; |
| 18 | +import { APP_CONTEXT } from "ui/markdown"; |
| 19 | +import { Literal } from "expression/literal"; |
| 20 | +import { VNode } from "preact"; |
| 21 | + |
| 22 | +class DatacoreModal extends ObsidianModal { |
| 23 | + constructor(app: App, public openCallback?: ObsidianModal["onOpen"], public onCancel?: ObsidianModal["onClose"]) { |
| 24 | + super(app); |
| 25 | + } |
| 26 | + public onOpen() { |
| 27 | + super.onOpen(); |
| 28 | + this.openCallback?.(); |
| 29 | + } |
| 30 | + public onClose() { |
| 31 | + super.onClose(); |
| 32 | + this.onCancel?.(); |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +class SubmittableDatacoreModal<T> extends DatacoreModal { |
| 37 | + constructor( |
| 38 | + app: App, |
| 39 | + public submitCallback?: (result: T) => void | Promise<void>, |
| 40 | + public openCallback?: ObsidianModal["onOpen"], |
| 41 | + public onCancel?: ObsidianModal["onClose"] |
| 42 | + ) { |
| 43 | + super(app, openCallback, onCancel); |
| 44 | + } |
| 45 | + public onSubmit(result: T) { |
| 46 | + this.close(); |
| 47 | + this.submitCallback?.(result); |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +export class Modals { |
| 52 | + get submittableModal() { |
| 53 | + return SubmittableDatacoreModal; |
| 54 | + } |
| 55 | + get modal() { |
| 56 | + return DatacoreModal; |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +interface ModalContextType<M extends ObsidianModal> { |
| 61 | + modal: M; |
| 62 | +} |
| 63 | + |
| 64 | +interface BaseModalProps { |
| 65 | + title?: Literal | VNode | ReactNode; |
| 66 | + children: ReactNode; |
| 67 | + onCancel?: ObsidianModal["onClose"]; |
| 68 | + onOpen?: ObsidianModal["onOpen"]; |
| 69 | +} |
| 70 | + |
| 71 | +const MODAL_CONTEXT = createContext<ModalContextType<any> | null>(null); |
| 72 | + |
| 73 | +function ModalContext<M extends ObsidianModal>({ modal, children }: { modal: M; children: ReactNode }) { |
| 74 | + const Ctx = MODAL_CONTEXT as ReactContext<ModalContextType<M>>; |
| 75 | + return <Ctx.Provider value={{ modal }}>{children}</Ctx.Provider>; |
| 76 | +} |
| 77 | + |
| 78 | +function useReusableImperativeHandle<M extends ObsidianModal>(modal: M, ref: ForwardedRef<M>) { |
| 79 | + useImperativeHandle(ref, () => modal, [modal]); |
| 80 | +} |
| 81 | + |
| 82 | +function InnerSubmittableModal<T>( |
| 83 | + { |
| 84 | + children, |
| 85 | + onSubmit, |
| 86 | + onCancel, |
| 87 | + onOpen, |
| 88 | + title, |
| 89 | + }: BaseModalProps & { |
| 90 | + onSubmit?: (result: T) => void | Promise<void>; |
| 91 | + }, |
| 92 | + ref: ForwardedRef<SubmittableDatacoreModal<T>> |
| 93 | +) { |
| 94 | + const app = useContext(APP_CONTEXT)!; |
| 95 | + const modal = useMemo( |
| 96 | + () => new SubmittableDatacoreModal<T>(app, onSubmit, onOpen, onCancel), |
| 97 | + [app, onSubmit, onOpen, onCancel] |
| 98 | + ); |
| 99 | + useReusableImperativeHandle(modal, ref); |
| 100 | + return ( |
| 101 | + <ModalContext modal={modal}> |
| 102 | + {createPortal(<>{title}</>, modal.titleEl)} |
| 103 | + {createPortal(<>{children}</>, modal.contentEl)} |
| 104 | + </ModalContext> |
| 105 | + ); |
| 106 | +} |
| 107 | + |
| 108 | +function InnerModal({ children, onCancel, onOpen, title }: BaseModalProps, ref: ForwardedRef<DatacoreModal>) { |
| 109 | + const app = useContext(APP_CONTEXT)!; |
| 110 | + const modal = useMemo(() => new DatacoreModal(app, onOpen, onCancel), [app, onOpen, onCancel]); |
| 111 | + useReusableImperativeHandle(modal, ref); |
| 112 | + return ( |
| 113 | + <ModalContext modal={modal}> |
| 114 | + {createPortal(<>{title}</>, modal.titleEl)} |
| 115 | + {createPortal(<>{children}</>, modal.contentEl)} |
| 116 | + </ModalContext> |
| 117 | + ); |
| 118 | +} |
| 119 | + |
| 120 | +export function useModalContext<M extends ObsidianModal>() { |
| 121 | + return useContext(MODAL_CONTEXT) as ModalContextType<M>; |
| 122 | +} |
| 123 | + |
| 124 | +export const SubmittableModal = forwardRef(InnerSubmittableModal) as typeof InnerSubmittableModal; |
| 125 | + |
| 126 | +export const Modal = forwardRef(InnerModal) as typeof InnerModal; |
0 commit comments