From 8e3f4f413525d5d39f17217d7293485fd634fb53 Mon Sep 17 00:00:00 2001 From: Carson Full Date: Tue, 26 Sep 2023 17:33:07 -0500 Subject: [PATCH] More work on media --- src/components/form/Dropzone.tsx | 31 +++-- .../EditForm/Steps/Media/MediaInfoForm.tsx | 106 +++++++++++++-- .../EditForm/Steps/Media/MediaStep.graphql | 3 +- .../EditForm/Steps/Media/MediaStep.tsx | 126 +++++++++--------- .../Steps/Media/VariantMediaAccordion.tsx | 83 ++---------- .../VariantResponsesAccordion.tsx | 116 ++++++---------- .../EditForm/Steps/VariantAccordion.tsx | 39 ++++++ 7 files changed, 267 insertions(+), 237 deletions(-) create mode 100644 src/scenes/ProgressReports/EditForm/Steps/VariantAccordion.tsx diff --git a/src/components/form/Dropzone.tsx b/src/components/form/Dropzone.tsx index 8f85c6977e..d15c0239ed 100644 --- a/src/components/form/Dropzone.tsx +++ b/src/components/form/Dropzone.tsx @@ -1,5 +1,6 @@ import { Clear as ClearIcon } from '@mui/icons-material'; import { + Box, IconButton, List, ListItem, @@ -12,20 +13,19 @@ import { ReactNode } from 'react'; import { useDropzone } from 'react-dropzone'; import { makeStyles } from 'tss-react/mui'; import { Except } from 'type-fest'; +import { extendSx, StyleProps } from '../../common'; import { fileIcon } from '../files/fileTypes'; import { FieldConfig, useField } from './useField'; -const useStyles = makeStyles()(({ palette, spacing }) => { +const useStyles = makeStyles()(({ palette, spacing, shape }) => { const dropzoneHoverStyle = { - backgroundColor: palette.grey[200], + // backgroundColor: palette.grey[200], borderColor: palette.primary.main, }; return { - root: { - marginBottom: spacing(2), - }, dropzone: { - backgroundColor: palette.grey[300], + borderRadius: shape.borderRadius, + // backgroundColor: palette.grey[300], border: `2px dashed ${palette.divider}`, cursor: 'pointer', padding: spacing(3), @@ -42,17 +42,20 @@ const useStyles = makeStyles()(({ palette, spacing }) => { }; }); -export type DropzoneFieldProps = Except, 'multiple'> & { - label?: ReactNode; - multiple?: boolean; - className?: string; -}; +export type DropzoneFieldProps = Except, 'multiple'> & + StyleProps & { + label?: ReactNode; + multiple?: boolean; + disableFileList?: boolean; + }; export function DropzoneField({ multiple = false, label = 'Click or drag files here', name: nameProp, className, + sx, + disableFileList, }: DropzoneFieldProps) { const { classes, cx } = useStyles(); @@ -91,7 +94,7 @@ export function DropzoneField({ }); return ( -
+
- {currentFiles.length > 0 && ( + {!disableFileList && currentFiles.length > 0 && ( {currentFiles.map((file, index) => { const { name, type } = file; @@ -129,6 +132,6 @@ export function DropzoneField({ })} )} -
+ ); } diff --git a/src/scenes/ProgressReports/EditForm/Steps/Media/MediaInfoForm.tsx b/src/scenes/ProgressReports/EditForm/Steps/Media/MediaInfoForm.tsx index ae8e5e2f2e..154f3cdd3b 100644 --- a/src/scenes/ProgressReports/EditForm/Steps/Media/MediaInfoForm.tsx +++ b/src/scenes/ProgressReports/EditForm/Steps/Media/MediaInfoForm.tsx @@ -1,26 +1,114 @@ +import { AddPhotoAlternate } from '@mui/icons-material'; import { Box, TextField } from '@mui/material'; -import { ProgressReportMediaCategory } from '~/api/schema.graphql'; +import { useMemo } from 'react'; +import { + ProgressReportMediaCategory as Category, + Sensitivity, +} from '~/api/schema.graphql'; import { ProgressReportMediaCategoryLabels, ProgressReportMediaCategoryList, } from '~/api/schema/enumLists'; import { labelFrom } from '~/common'; -import { Form, FormProps, SelectField } from '~/components/form'; +import { VariantFragment } from '~/common/fragments'; +import { + DropzoneField, + Form, + FormProps, + SelectField, + SubmitAction, +} from '~/components/form'; +import { SensitivityIcon } from '../../../../../components/Sensitivity'; +import { ProgressReportMediaFragment } from './progressReportMedia.graphql'; -export interface MediaFormState { - category: ProgressReportMediaCategory | null; - caption: string | null; +interface MediaFormState extends SubmitAction<'delete'> { + variant: VariantFragment; id?: string; + newFile?: File[]; + category?: Category | null; + caption?: string | null; } -export const MediaInfoForm = ({ ...formValues }: FormProps) => { + +export type MediaInfoFormProps = Omit< + FormProps, + 'initialValues' +> & { + sensitivity: Sensitivity; + variant: VariantFragment; + existingMedia?: ProgressReportMediaFragment; +}; + +export const MediaInfoForm = ({ + existingMedia, + sensitivity, + variant, + ...props +}: MediaInfoFormProps) => { + const initialValues = useMemo( + () => ({ + variant, + id: existingMedia?.id, + category: existingMedia?.category, + caption: existingMedia?.media.caption, + }), + [variant, existingMedia] + ); + return ( - {...formValues}> + autoSubmit {...props} initialValues={initialValues}> {({ handleSubmit, values }) => ( + {/* TODO Maybe only use for first upload */} + *': { + flex: 1, + + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + position: 'relative', + }} + label={ + <> + {existingMedia ? ( + + ) : ( + <> + +
Click or drop to add photo
+ + )} + + + } + /> { const [createMedia] = useMutation(CreateMediaDocument); const [deleteMedia] = useMutation(DeleteMediaDocument); const uploadFile = useUploadFileAsync(); - const handleSubmit: FormProps["onSubmit"] = async (values) => { - // use values.id to determine if we want to create, update, delete - // delete = values.id + submitAction - const [uploadedImageInfo, finalizeUpload] = await uploadFile(values); - if (!uploadedImageInfo) { - finalizeUpload.tap; - return; + const handleSubmit: MediaInfoFormProps['onSubmit'] = async (values) => { + if (values.submitAction === 'delete') { + if (!values.id) { + return; + } + await deleteMedia({ + variables: { + deleteProgressReportMediaId: values.id, + }, + }); } - await createMedia({ - variables: { - input: { - file: uploadedImageInfo, - reportId: report.id, - variant: 'published', + + if (!values.id) { + if (!values.newFile || values.newFile.length === 0) { + return; + } + const [uploadedImageInfo, finalizeUpload] = await uploadFile( + values.newFile[0] + ); + await createMedia({ + variables: { + input: { + reportId: report.id, + file: uploadedImageInfo!, + variant: values.variant.key, + }, }, - }, - }).then(...finalizeUpload.tap); + }).then(...finalizeUpload.tap); + } }; - const { - getRootProps, - getInputProps, - isDragActive, - open: openFileBrowser, - } = useDropzone({ - onDrop: handleFileUpload, - noClick: true, - noKeyboard: true, - disabled: false, - }); - return ( -
- - - Upload an image to go with your Report - - {report.media.uploadableVariants - .slice() - .reverse() - .map((variant) => { - return ( - - item.variant.key === variant.key - ), - uploadVariant: variant, - }} - sensitivity={report.parent.sensitivity} - initiallyOpen={false} - /> - - ); - })} - -
+ + + Upload an image to go with your Report + + {/* TODO merge lists */} + {report.media.items.map((media) => ( + + + + ))} + {report.media.uploadableVariants + .slice() + .reverse() + .map((variant) => { + return ( + + + + ); + })} + ); }; diff --git a/src/scenes/ProgressReports/EditForm/Steps/Media/VariantMediaAccordion.tsx b/src/scenes/ProgressReports/EditForm/Steps/Media/VariantMediaAccordion.tsx index 8db96dd09d..431900cdf8 100644 --- a/src/scenes/ProgressReports/EditForm/Steps/Media/VariantMediaAccordion.tsx +++ b/src/scenes/ProgressReports/EditForm/Steps/Media/VariantMediaAccordion.tsx @@ -2,21 +2,16 @@ import { Add, AddPhotoAlternate, Delete, - ExpandMore, GetApp, MoreVert, Publish, } from '@mui/icons-material'; import { - Accordion, - AccordionDetails, - AccordionSummary, Box, Button, ClickAwayListener, Grow, IconButton, - Link, MenuItem, MenuList, Paper, @@ -26,17 +21,13 @@ import { import { KeyboardEvent, SyntheticEvent, - useCallback, useEffect, - useMemo, useRef, useState, } from 'react'; import { Sensitivity as SensitivityType } from '~/api/schema/schema.graphql'; -import { RoleIcon } from '~/components/RoleIcon'; import { SensitivityIcon } from '~/components/Sensitivity'; -import { MediaInfoForm } from './MediaInfoForm'; -import { ProgressReportMediaFragment } from './progressReportMedia.graphql'; +import { Link } from '../../../../../components/Routing'; // TODO update types const MoreActionsButton = ({ @@ -99,6 +90,7 @@ const MoreActionsButton = ({ > + {/* TODO replace with higher level Menu component */} - + Download @@ -148,20 +140,20 @@ const MoreActionsButton = ({ ); }; -const ImageBox = ({ +export const ImageBox = ({ media, sensitivity, openFileBrowser, deleteMedia, }: { - media: any[]; + media?: any[]; sensitivity: SensitivityType; - openFileBrowser: any; - deleteMedia: any; + openFileBrowser?: any; + deleteMedia?: any; }) => { return ( <> - {media.length ? ( + {media?.length ? ( ); }; - -// TODO proper types -export const VariantMediaAccordion = ({ - openFileBrowser, - existingMedia, - initiallyOpen, - sensitivity, - deleteMedia, -}: { - openFileBrowser: any; - existingMedia?: ProgressReportMediaFragment; - initiallyOpen: boolean; - sensitivity: SensitivityType; - deleteMedia: any; -}) => { - const [expanded, setExpanded] = useState(initiallyOpen); - const initialValues = useMemo( - () => - existingMedia - ? { - category: existingMedia.category, - caption: existingMedia.media.caption, - id: existingMedia.id, - } - : undefined, - [existingMedia] - ); - const deleteCurrentMedia = useCallback(async () => { - await deleteMedia({ - variables: { deleteProgressReportMediaId: response.item[0]?.id }, - }); - }, [deleteMedia, response.item]); - return ( - - } - sx={{ - '& .MuiAccordionSummary-content': { - alignItems: 'center', - }, - }} - onClick={() => setExpanded(!expanded)} - > - - {response.uploadVariant.label} - - - - - - - ); -}; diff --git a/src/scenes/ProgressReports/EditForm/Steps/PromptVariant/VariantResponsesAccordion.tsx b/src/scenes/ProgressReports/EditForm/Steps/PromptVariant/VariantResponsesAccordion.tsx index f1ae3f4128..ca08c67a24 100644 --- a/src/scenes/ProgressReports/EditForm/Steps/PromptVariant/VariantResponsesAccordion.tsx +++ b/src/scenes/ProgressReports/EditForm/Steps/PromptVariant/VariantResponsesAccordion.tsx @@ -1,16 +1,10 @@ -import { ExpandMore } from '@mui/icons-material'; -import { - Accordion, - AccordionDetails, - AccordionSummary, - Typography, -} from '@mui/material'; +import { Typography } from '@mui/material'; import { ReactNode, useState } from 'react'; import { Scalars } from '~/api/schema/schema.graphql'; import { VariantResponseFragment as VariantResponse } from '~/common/fragments'; import { Form, FormProps, SavingStatus } from '~/components/form'; import { RichTextField, RichTextView } from '~/components/RichText'; -import { RoleIcon } from '~/components/RoleIcon'; +import { VariantAccordion } from '../VariantAccordion'; import { InstructionsToggle } from './VariantInstructionsToggle'; interface FormShape { @@ -27,11 +21,10 @@ export interface VariantResponsesAccordionProps export const VariantResponsesAccordion = ({ response, - expanded: _expanded, + expanded, instructions, onSubmit, }: VariantResponsesAccordionProps) => { - const [expanded, setExpanded] = useState(_expanded ?? false); const [initialValues] = useState(() => ({ variant: response.variant.key, response: response.response.value, @@ -42,68 +35,47 @@ export const VariantResponsesAccordion = ({ } return ( - - } - sx={{ - '& .MuiAccordionSummary-content': { - alignItems: 'center', - }, - }} - onClick={() => setExpanded(!expanded)} - > - - {response.variant.label} - + + {response.response.canEdit ? ( + + onSubmit={onSubmit} + initialValues={initialValues} + autoSubmit + > + {({ handleSubmit, submitting }) => ( +
+ {instructions && ( + + {instructions} + + )} - - {response.response.canEdit ? ( - - onSubmit={onSubmit} - initialValues={initialValues} - autoSubmit - > - {({ handleSubmit, submitting }) => ( - - {instructions && ( - - {instructions} - - )} - - - } - /> - - )} - - ) : response.response.value ? ( - - ) : ( - - No response given yet - - )} -
-
+ + } + /> + + )} + + ) : response.response.value ? ( + + ) : ( + + No response given yet + + )} + ); }; diff --git a/src/scenes/ProgressReports/EditForm/Steps/VariantAccordion.tsx b/src/scenes/ProgressReports/EditForm/Steps/VariantAccordion.tsx new file mode 100644 index 0000000000..e5bdedc23b --- /dev/null +++ b/src/scenes/ProgressReports/EditForm/Steps/VariantAccordion.tsx @@ -0,0 +1,39 @@ +import { ExpandMore } from '@mui/icons-material'; +import { Accordion, AccordionDetails, AccordionSummary } from '@mui/material'; +import { useToggle } from 'ahooks'; +import { ChildrenProp } from '~/common'; +import { VariantFragment } from '~/common/fragments'; +import { RoleIcon } from '~/components/RoleIcon'; + +export interface VariantAccordionProps extends ChildrenProp { + variant: VariantFragment; + expanded?: boolean; +} + +export const VariantAccordion = ({ + variant, + expanded: expandedInput, + children, +}: VariantAccordionProps) => { + const [expanded, { toggle }] = useToggle(expandedInput ?? false); + + return ( + + } + sx={{ + '& .MuiAccordionSummary-content': { + alignItems: 'center', + }, + }} + onClick={toggle} + > + + {variant.label} + + + {children} + + ); +};