Skip to content

Commit

Permalink
feat: submit individually file data for edit (#1552)
Browse files Browse the repository at this point in the history
* feat: submit individually file data for edit

* refactor: apply PR requested changes
  • Loading branch information
pyphilia authored Oct 28, 2024
1 parent 9e2df1b commit 69191f0
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 115 deletions.
86 changes: 47 additions & 39 deletions src/components/item/edit/EditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,14 @@ import { BUILDER } from '../../../langs/constants';
import BaseItemForm from '../form/BaseItemForm';
import FileForm from '../form/FileForm';
import FolderForm from '../form/FolderForm';
import NameForm from '../form/NameForm';
import DocumentForm from '../form/document/DocumentForm';
import EditShortcutForm from '../shortcut/EditShortcutForm';

const { editItemRoutine } = routines;

export interface EditModalContentPropType {
item?: DiscriminatedItem;
setChanges: (payload: Partial<DiscriminatedItem>) => void;
updatedProperties: Partial<DiscriminatedItem>;
}
export type EditModalContentType = CT<EditModalContentPropType>;

Expand Down Expand Up @@ -68,20 +67,13 @@ const EditModal = ({ item, onClose, open }: Props): JSX.Element => {
setUpdatedItem({ ...updatedItem, ...payload } as DiscriminatedItem);
};

const renderComponent = (): JSX.Element => {
// files are handled beforehand
const renderDialogContent = (): JSX.Element => {
switch (item.type) {
case ItemType.DOCUMENT:
return <DocumentForm setChanges={setChanges} item={item} />;
case ItemType.LOCAL_FILE:
case ItemType.S3_FILE:
return <FileForm setChanges={setChanges} item={item} />;
case ItemType.SHORTCUT:
return (
<NameForm
name={updatedItem?.name ?? item?.name}
setChanges={setChanges}
/>
);
return <EditShortcutForm item={item} setChanges={setChanges} />;
case ItemType.FOLDER:
return <FolderForm setChanges={setChanges} item={item} />;
case ItemType.LINK:
Expand Down Expand Up @@ -131,6 +123,48 @@ const EditModal = ({ item, onClose, open }: Props): JSX.Element => {
onClose();
};

// temporary solution for displaying separate dialog content
const renderContent = () => {
if (item.type === ItemType.LOCAL_FILE || item.type === ItemType.S3_FILE) {
return <FileForm onClose={onClose} item={item} />;
}

return (
<>
<DialogContent
sx={{
display: 'flex',
flexDirection: 'column',
}}
>
{renderDialogContent()}
</DialogContent>
<DialogActions>
<CancelButton
id={EDIT_ITEM_MODAL_CANCEL_BUTTON_ID}
onClick={onClose}
/>
<Button
// should not allow users to save if the item is not valid
disabled={
// // maybe we do not need the state variable and can just check the item
// isConfirmButtonDisabled ||
// isItem Valid checks a full item, so we add the updated properties to the item to check
!isItemValid({
...item,
...updatedItem,
})
}
onClick={submit}
id={ITEM_FORM_CONFIRM_BUTTON_ID}
>
{translateCommon(COMMON.SAVE_BUTTON)}
</Button>
</DialogActions>
</>
);
};

return (
<Dialog
onClose={onClose}
Expand All @@ -142,33 +176,7 @@ const EditModal = ({ item, onClose, open }: Props): JSX.Element => {
<DialogTitle id={item?.id}>
{translateBuilder(BUILDER.EDIT_ITEM_MODAL_TITLE)}
</DialogTitle>
<DialogContent
sx={{
display: 'flex',
flexDirection: 'column',
}}
>
{renderComponent()}
</DialogContent>
<DialogActions>
<CancelButton id={EDIT_ITEM_MODAL_CANCEL_BUTTON_ID} onClick={onClose} />
<Button
// should not allow users to save if the item is not valid
disabled={
// // maybe we do not need the state variable and can just check the item
// isConfirmButtonDisabled ||
// isItem Valid checks a full item, so we add the updated properties to the item to check
!isItemValid({
...item,
...updatedItem,
})
}
onClick={submit}
id={ITEM_FORM_CONFIRM_BUTTON_ID}
>
{translateCommon(COMMON.SAVE_BUTTON)}
</Button>
</DialogActions>
{renderContent()}
</Dialog>
);
};
Expand Down
151 changes: 103 additions & 48 deletions src/components/item/form/FileForm.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
import { useEffect } from 'react';
import { ReactNode } from 'react';
import { useForm } from 'react-hook-form';

import { TextField } from '@mui/material';
import {
Box,
Button,
DialogActions,
DialogContent,
TextField,
} from '@mui/material';

import {
DescriptionPlacementType,
DiscriminatedItem,
ItemType,
LocalFileItemExtra,
LocalFileItemType,
MimeTypes,
S3FileItemExtra,
S3FileItemType,
} from '@graasp/sdk';
import { COMMON } from '@graasp/translations';

import { useBuilderTranslation } from '@/config/i18n';
import { ITEM_FORM_IMAGE_ALT_TEXT_EDIT_FIELD_ID } from '@/config/selectors';
import CancelButton from '@/components/common/CancelButton';
import { useBuilderTranslation, useCommonTranslation } from '@/config/i18n';
import { mutations } from '@/config/queryClient';
import {
EDIT_ITEM_MODAL_CANCEL_BUTTON_ID,
ITEM_FORM_CONFIRM_BUTTON_ID,
ITEM_FORM_IMAGE_ALT_TEXT_EDIT_FIELD_ID,
} from '@/config/selectors';
import { getExtraFromPartial } from '@/utils/itemExtra';

import { BUILDER } from '../../../langs/constants';
import type { EditModalContentPropType } from '../edit/EditModal';
import DescriptionForm from './DescriptionForm';
import NameForm from './NameForm';

Expand All @@ -32,60 +43,94 @@ type Inputs = {

const FileForm = ({
item,
setChanges,
onClose,
}: {
item: DiscriminatedItem;
setChanges: EditModalContentPropType['setChanges'];
}): JSX.Element | null => {
item: LocalFileItemType | S3FileItemType;
onClose: () => void;
}): ReactNode => {
const { t: translateBuilder } = useBuilderTranslation();
const { register, watch, setValue } = useForm<Inputs>();
const { t: translateCommon } = useCommonTranslation();
const {
register,
watch,
setValue,
handleSubmit,
reset,
formState: { errors, isValid },
} = useForm<Inputs>();
const altText = watch('altText');
const description = watch('description');
const descriptionPlacement = watch('descriptionPlacement');
const name = watch('name');

useEffect(() => {
let newExtra: S3FileItemExtra | LocalFileItemExtra | undefined;
const { mimetype, altText: previousAltText } = getExtraFromPartial(item);

if (item.type === ItemType.S3_FILE) {
newExtra = {
[ItemType.S3_FILE]: {
...item.extra[ItemType.S3_FILE],
altText,
},
};
} else if (item.type === ItemType.LOCAL_FILE) {
newExtra = {
[ItemType.LOCAL_FILE]: {
...item.extra[ItemType.LOCAL_FILE],
altText,
},
};
}
const { mutateAsync: editItem } = mutations.useEditItem();

setChanges({
name,
description,
settings: { descriptionPlacement },
extra: newExtra,
} as S3FileItemType | LocalFileItemType);
function buildFileExtra() {
if (altText) {
if (item.type === ItemType.S3_FILE) {
return {
[ItemType.S3_FILE]: {
altText,
},
} as S3FileItemExtra;
}
if (item.type === ItemType.LOCAL_FILE) {
return {
[ItemType.LOCAL_FILE]: {
altText,
},
} as LocalFileItemExtra;
}
}
console.error(`item type ${item.type} is not handled`);
return undefined;
}

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [altText, description, descriptionPlacement, name, setChanges]);
async function onSubmit(data: Inputs) {
try {
await editItem({
id: item.id,
name: data.name,
description: data.description,
// only post extra if it has been changed
extra: altText !== previousAltText ? buildFileExtra() : undefined,
// only patch settings it it has been changed
settings:
descriptionPlacement !== item.settings.descriptionPlacement
? { descriptionPlacement }
: undefined,
});
onClose();
} catch (e) {
console.error(e);
}
}

if (item) {
const itemExtra = getExtraFromPartial(item);
const { mimetype, altText: previousAltText } = itemExtra;
return (
<>
<NameForm nameForm={register('name', { value: item.name })} />
return (
<Box component="form" onSubmit={handleSubmit(onSubmit)}>
<DialogContent
sx={{
display: 'flex',
flexDirection: 'column',
}}
>
<NameForm
nameForm={register('name', {
value: item.name,
required: true,
})}
error={errors.name}
showClearButton={Boolean(watch('name'))}
reset={() => reset({ name: '' })}
/>
{mimetype && MimeTypes.isImage(mimetype) && (
<TextField
variant="standard"
id={ITEM_FORM_IMAGE_ALT_TEXT_EDIT_FIELD_ID}
label={translateBuilder(BUILDER.EDIT_ITEM_IMAGE_ALT_TEXT_LABEL)}
// always shrink because setting name from defined app does not shrink automatically
InputLabelProps={{ shrink: true }}
slotProps={{ inputLabel: { shrink: true } }}
sx={{ width: '50%', my: 1 }}
multiline
{...register('altText', { value: previousAltText })}
Expand All @@ -103,10 +148,20 @@ const FileForm = ({
descriptionPlacement ?? item?.settings?.descriptionPlacement
}
/>
</>
);
}
return null;
</DialogContent>
<DialogActions>
<CancelButton id={EDIT_ITEM_MODAL_CANCEL_BUTTON_ID} onClick={onClose} />
<Button
variant="contained"
type="submit"
id={ITEM_FORM_CONFIRM_BUTTON_ID}
disabled={!isValid}
>
{translateCommon(COMMON.SAVE_BUTTON)}
</Button>
</DialogActions>
</Box>
);
};

export default FileForm;
Loading

0 comments on commit 69191f0

Please sign in to comment.