diff --git a/ui/src/components/ErrorAlert.tsx b/ui/src/components/ErrorAlert.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8dbcd41a91686c132bb6be0f1c65cab9162c1fff --- /dev/null +++ b/ui/src/components/ErrorAlert.tsx @@ -0,0 +1,18 @@ +import {Paper} from "@material-ui/core"; + +export interface ErrorAlertProps { + severity: string, + error: unknown, +} + +export function ErrorAlert({severity, error}: ErrorAlertProps) { + if (!error) { + return null; + } + + return ( + <Paper variant="outlined" color={severity}> + <strong>Error</strong>: {"" + error} + </Paper> + ) +} diff --git a/ui/src/components/ErrorContext.tsx b/ui/src/components/ErrorContext.tsx new file mode 100644 index 0000000000000000000000000000000000000000..988ae5f0aa1b880011fd8cb1ba71c76bd51c35c6 --- /dev/null +++ b/ui/src/components/ErrorContext.tsx @@ -0,0 +1,42 @@ +import {createContext, FC, FunctionComponent, PropsWithChildren, useCallback, useContext, useState} from "react"; +import {createPortal} from "react-dom"; + +type ChildrenComponent = FunctionComponent<PropsWithChildren<{}>>; + +const ErrorContext = createContext<ChildrenComponent | null>(null); + +export function useErrorDisplay(): [FC, ChildrenComponent] { + const [ref, setRef] = useState<HTMLDivElement | null>(null); + const errorDisplay = useCallback( + () => ( + <div ref={setRef}/> + ), + [] + ); + const errorRenderer: ChildrenComponent = useCallback( + ({children}: PropsWithChildren<{}>) => ref && createPortal( + children, + ref + ), + [ref] + ); + const errorWrapper = useCallback( + ({children}: PropsWithChildren<{}>) => ( + <ErrorContext.Provider value={errorRenderer}> + {children} + </ErrorContext.Provider> + ), + [errorRenderer] + ); + + return [errorDisplay, errorWrapper]; +} + +export const ErrorPortal: ChildrenComponent = (props: PropsWithChildren<{}>) => { + const errorRenderer = useContext(ErrorContext); + if (!errorRenderer) { + return null; + } + + return errorRenderer(props); +} diff --git a/ui/src/components/UploadView.tsx b/ui/src/components/UploadView.tsx index eae8360c5c2b5cec3c60167c74e26944de17b708..8c998007f848112a61c4c5132e1325614e24a402 100644 --- a/ui/src/components/UploadView.tsx +++ b/ui/src/components/UploadView.tsx @@ -1,14 +1,22 @@ import {useUploadImage} from "../api/useUploadImage"; +import {ErrorPortal} from "./ErrorContext"; +import {ErrorAlert} from "./ErrorAlert"; +import {LinearProgress} from "@material-ui/core"; export default function UploadView() { - const {mutate: upload, error: uploadError, isLoading: uploadLoading} = useUploadImage(); + const {mutate: upload, error, isLoading} = useUploadImage(); return ( <div> - <pre>Error: {JSON.stringify(uploadError, null, 2)}</pre> - <pre>Loading: {JSON.stringify(uploadLoading, null, 2)}</pre> + {isLoading && ( + <LinearProgress/> + )} + <ErrorPortal> + <ErrorAlert severity="error" error={error}/> + </ErrorPortal> <input type="file" + disabled={isLoading} onChange={async ({target}) => { if (target.files) { await upload(target.files)