diff --git a/databox/client/src/components/Dialog/Collection/EditCollection.tsx b/databox/client/src/components/Dialog/Collection/EditCollection.tsx index 5a2c7fb85..14dd8d6e3 100644 --- a/databox/client/src/components/Dialog/Collection/EditCollection.tsx +++ b/databox/client/src/components/Dialog/Collection/EditCollection.tsx @@ -1,5 +1,5 @@ import {Collection} from '../../../types'; -import {putCollection} from '../../../api/collection'; +import {clearWorkspaceCache, postCollection, putCollection} from '../../../api/collection'; import {useTranslation} from 'react-i18next'; import {toast} from 'react-toastify'; import {useFormSubmit} from '@alchemy/api'; @@ -17,7 +17,11 @@ type Props = { export default function EditCollection({data, onClose, minHeight}: Props) { const {t} = useTranslation(); - const {submitting, submitted, handleSubmit, errors} = useFormSubmit({ + const usedFormSubmit = useFormSubmit({ + defaultValues: { + title: '', + privacy: 0, + }, onSubmit: async (data: Collection) => { return await putCollection(data.id, data); }, @@ -32,6 +36,11 @@ export default function EditCollection({data, onClose, minHeight}: Props) { }, }); + const { + submitting, + remoteErrors, + } = usedFormSubmit; + const formId = 'edit-collection'; return ( @@ -39,15 +48,13 @@ export default function EditCollection({data, onClose, minHeight}: Props) { onClose={onClose} formId={formId} loading={submitting} - errors={errors} + errors={remoteErrors} minHeight={minHeight} > ); diff --git a/databox/client/src/components/Dialog/Workspace/DefinitionManager.tsx b/databox/client/src/components/Dialog/Workspace/DefinitionManager.tsx index fb2dd9a31..f516a0491 100644 --- a/databox/client/src/components/Dialog/Workspace/DefinitionManager.tsx +++ b/databox/client/src/components/Dialog/Workspace/DefinitionManager.tsx @@ -21,7 +21,7 @@ import {ApiHydraObjectResponse} from '../../../api/hydra'; import DialogActions from '@mui/material/DialogActions'; import {useTranslation} from 'react-i18next'; import AddBoxIcon from '@mui/icons-material/AddBox'; -import useFormSubmit, {UseFormHandleSubmit} from '../../../hooks/useFormSubmit'; +import {useFormSubmit} from '@alchemy/api'; import {LoadingButton} from '@mui/lab'; import {toast} from 'react-toastify'; import RemoteErrors from '../../Form/RemoteErrors'; @@ -30,17 +30,18 @@ import SortableList, { SortableItem, SortableItemProps, } from '../../Ui/Sortable/SortableList'; +import {UseFormSubmit} from '@alchemy/api'; +import {FieldValues} from "react-hook-form"; -type DefinitionBase = ApiHydraObjectResponse & {id: string}; +type DefinitionBase = ApiHydraObjectResponse & { id: string }; export type DefinitionItemProps = { data: D; }; -export type DefinitionItemFormProps = { +export type DefinitionItemFormProps = { formId: string; - handleSubmit: UseFormHandleSubmit; - submitting: boolean; + usedFormSubmit: UseFormSubmit; workspaceId: string; } & DefinitionItemProps; @@ -65,19 +66,17 @@ const SortableListItem = React.memo( } & SortableItemProps) => { const {selectedItem, onClick, listComponent} = itemProps; - return ( - - - {React.createElement(listComponent, { - data, - key: data.id, - })} - - - ); + return + + {React.createElement(listComponent, { + data, + key: data.id, + })} + + } ); @@ -151,7 +150,8 @@ export default function DefinitionManager({ }); }, []); - const {submitting, handleSubmit, errors} = useFormSubmit({ + const usedFormSubmit = useFormSubmit({ + onSubmit: async (data: D) => { const newData = await handleSave(data); @@ -185,6 +185,12 @@ export default function DefinitionManager({ }, }); + const { + submitting, + handleSubmit, + remoteErrors + } = usedFormSubmit; + const onDelete = useCallback(() => { if (handleDelete && typeof item === 'object') { if ( @@ -231,159 +237,148 @@ export default function DefinitionManager({ }; }, [onSort, handleItemClick, item, listComponent]); - return ( - <> - + + ({ display: 'flex', - flex: '1 1 auto', - minHeight, - }} - style={{ - padding: 0, - }} + overflowY: 'auto', + borderRight: `1px solid ${theme.palette.divider}`, + })} > - ({ - display: 'flex', - overflowY: 'auto', - borderRight: `1px solid ${theme.palette.divider}`, - })} + - - - - - - - - - - - - {onSort && list && ( - - list={ - list as (D & - SortableItem & - DefinitionBase)[] - } - onOrderChange={onOrderChange} - itemComponent={SortableListItem} - itemProps={itemProps!} - /> - )} + + + + + + + + + - {!onSort && - list && - list.map(i => { - return ( - - - {React.createElement( - listComponent, - { - data: i, - key: i.id, - } - )} - - - ); - })} + {onSort && list && ( + + list={ + list as (D & + SortableItem & + DefinitionBase)[] + } + onOrderChange={onOrderChange} + itemComponent={SortableListItem} + itemProps={itemProps!} + /> + )} - {!list && - [0, 1, 2].map(i => ( - - - - - } - secondary={ - + {!onSort && + list && + list.map(i => { + return + + {React.createElement( + listComponent, + { + data: i, + key: i.id, } - /> - - ))} - - - - {item && - React.createElement(itemComponent, { - data: - item === 'new' ? (createNewItem() as D) : item!, - key: item === 'new' ? 'new' : item!.id, - formId, - handleSubmit, - submitting, - workspaceId, + )} + + })} - - {item && item !== 'new' && handleDelete && ( - <> -
- - - )} -
-
- - {item && ( - <> - - - {t('dialog.save', 'Save')} - - - )} - {!item && ( - - )} - - - ); + } + +
+ + {item && <> + + + {t('dialog.save', 'Save')} + + } + {!item && ( + + )} + + } diff --git a/databox/client/src/components/Dialog/Workspace/EditWorkspace.tsx b/databox/client/src/components/Dialog/Workspace/EditWorkspace.tsx index 866833a57..6dbd0e783 100644 --- a/databox/client/src/components/Dialog/Workspace/EditWorkspace.tsx +++ b/databox/client/src/components/Dialog/Workspace/EditWorkspace.tsx @@ -3,9 +3,10 @@ import {putWorkspace} from '../../../api/collection'; import {useTranslation} from 'react-i18next'; import {toast} from 'react-toastify'; import {useFormSubmit} from '@alchemy/api'; -import {WorkspaceForm} from '../../Form/WorkspaceForm'; +import {WorkspaceForm, WorkspaceFormData} from '../../Form/WorkspaceForm'; import FormTab from '../Tabbed/FormTab'; import {DialogTabProps} from '../Tabbed/TabbedDialog'; +import {extendSortableList, flattenSortableList} from "../../Form/SortableCollectionWidget.tsx"; type Props = { id: string; @@ -16,8 +17,9 @@ export default function EditWorkspace({data, onClose, minHeight}: Props) { const {t} = useTranslation(); const {submitting, submitted, handleSubmit, errors} = useFormSubmit({ - onSubmit: async (data: Workspace) => { - return await putWorkspace(data.id, data); + defaultValues: normalizeFormData(data), + onSubmit: async (data: WorkspaceFormData) => { + return await putWorkspace(data.id, denormalizeFormData(data)); }, onSuccess: () => { toast.success( @@ -38,6 +40,7 @@ export default function EditWorkspace({data, onClose, minHeight}: Props) { minHeight={minHeight} > ); } + + +function denormalizeFormData(data: WorkspaceFormData): Workspace { + return { + ...data, + enabledLocales: flattenSortableList(data.enabledLocales), + localeFallbacks: flattenSortableList(data.localeFallbacks), + }; +} + + +function normalizeFormData(data: Workspace): WorkspaceFormData { + return { + ...data, + enabledLocales: extendSortableList(data.enabledLocales), + localeFallbacks: extendSortableList(data.localeFallbacks), + }; +} diff --git a/databox/client/src/components/Dialog/Workspace/RenditionDefinitionManager.tsx b/databox/client/src/components/Dialog/Workspace/RenditionDefinitionManager.tsx index 0cb9c9d1a..211f45273 100644 --- a/databox/client/src/components/Dialog/Workspace/RenditionDefinitionManager.tsx +++ b/databox/client/src/components/Dialog/Workspace/RenditionDefinitionManager.tsx @@ -22,27 +22,20 @@ import {toast} from 'react-toastify'; import {useDirtyFormPrompt} from '../Tabbed/FormTab'; function Item({ - data, - handleSubmit: onSubmit, formId, - submitting, + usedFormSubmit: { + register, + handleSubmit, + control, + formState: {errors}, + submitting, + forbidNavigation, + }, workspaceId, }: DefinitionItemFormProps) { const {t} = useTranslation(); - const { - register, - handleSubmit, - setError, - control, - formState: {errors, isDirty}, - } = useForm({ - defaultValues: { - ...data, - class: data?.class && (data?.class as RenditionClass)['@id'], - }, - }); - useDirtyFormPrompt(isDirty); + useDirtyFormPrompt(forbidNavigation); return (
@@ -52,7 +45,7 @@ function Item({ {...register('name')} disabled={submitting} /> - + @@ -65,7 +58,7 @@ function Item({ control={control} workspaceId={workspaceId} /> - + @@ -79,7 +72,7 @@ function Item({ name={'pickSourceFile'} control={control} /> - + @@ -93,7 +86,7 @@ function Item({ name={'useAsOriginal'} control={control} /> - + @@ -107,7 +100,7 @@ function Item({ name={'useAsPreview'} control={control} /> - + @@ -121,7 +114,7 @@ function Item({ name={'useAsThumbnail'} control={control} /> - + @@ -146,7 +139,7 @@ function Item({ } function ListItem({data}: DefinitionItemProps) { - return ; + return ; } type Props = { diff --git a/databox/client/src/components/Dialog/Workspace/TagManager.tsx b/databox/client/src/components/Dialog/Workspace/TagManager.tsx index c2d8ba99e..a7dcc66f7 100644 --- a/databox/client/src/components/Dialog/Workspace/TagManager.tsx +++ b/databox/client/src/components/Dialog/Workspace/TagManager.tsx @@ -6,30 +6,26 @@ import DefinitionManager, { DefinitionItemProps, } from './DefinitionManager'; import {useTranslation} from 'react-i18next'; -import {Controller, useForm} from 'react-hook-form'; +import {Controller} from 'react-hook-form'; import FormFieldErrors from '../../Form/FormFieldErrors'; import {deleteTag, getTags, postTag, putTag} from '../../../api/tag'; import {useDirtyFormPrompt} from '../Tabbed/FormTab'; import ColorPicker from '../../Form/ColorPicker'; function Item({ - data, - handleSubmit: onSubmit, + usedFormSubmit: { + register, + control, + handleSubmit, + submitting, + forbidNavigation, + formState: {errors}, + }, formId, - submitting, }: DefinitionItemFormProps) { const {t} = useTranslation(); - const { - register, - control, - handleSubmit, - setError, - formState: {errors, isDirty}, - } = useForm({ - defaultValues: data, - }); - useDirtyFormPrompt(isDirty); + useDirtyFormPrompt(forbidNavigation); return ( diff --git a/databox/client/src/components/Dialog/Workspace/WorkspaceDialog.tsx b/databox/client/src/components/Dialog/Workspace/WorkspaceDialog.tsx index d68e26bfd..eb16bf6f3 100644 --- a/databox/client/src/components/Dialog/Workspace/WorkspaceDialog.tsx +++ b/databox/client/src/components/Dialog/Workspace/WorkspaceDialog.tsx @@ -34,7 +34,7 @@ export default function WorkspaceDialog({}: Props) { return ( > = function ({ formId, - data, - onSubmit, - submitting, - submitted, + usedFormSubmit: { + handleSubmit, + submitting, + register, + control, + forbidNavigation, + formState: {errors}, + } }) { const {t} = useTranslation(); - const { - register, - handleSubmit, - setError, - control, - formState: {errors, isDirty}, - } = useForm({ - defaultValues: data ?? { - title: '', - privacy: 0, - }, - }); - useDirtyFormPrompt(!submitted && isDirty); + useDirtyFormPrompt(forbidNavigation); return ( diff --git a/databox/client/src/components/Form/WorkspaceForm.tsx b/databox/client/src/components/Form/WorkspaceForm.tsx index 11e50bbe8..229474bd1 100644 --- a/databox/client/src/components/Form/WorkspaceForm.tsx +++ b/databox/client/src/components/Form/WorkspaceForm.tsx @@ -1,5 +1,4 @@ import {Hidden, TextField} from '@mui/material'; -import {useForm} from 'react-hook-form'; import {FC} from 'react'; import {Trans, useTranslation} from 'react-i18next'; import {Workspace} from '../../types'; @@ -9,8 +8,6 @@ import {FormProps} from './types'; import FlagIcon from '@mui/icons-material/Flag'; import IconFormLabel from './IconFormLabel'; import SortableCollectionWidget, { - extendSortableList, - flattenSortableList, SortableValue, } from './SortableCollectionWidget'; import Flag from '../Ui/Flag'; @@ -26,45 +23,21 @@ export type WorkspaceFormData = { localeFallbacks: SortableValue[] | undefined; } & Omit; -function normalizeFormData(data: Workspace): WorkspaceFormData { - return { - ...data, - enabledLocales: extendSortableList(data.enabledLocales), - localeFallbacks: extendSortableList(data.localeFallbacks), - }; -} - -function denormalizeFormData( - handler: (data: Workspace) => Promise -): (data: WorkspaceFormData) => Promise { - return async (data: WorkspaceFormData) => - await handler({ - ...data, - enabledLocales: flattenSortableList(data.enabledLocales), - localeFallbacks: flattenSortableList(data.localeFallbacks), - }); -} - -export const WorkspaceForm: FC> = function ({ +export const WorkspaceForm: FC> = function ({ formId, - data, - onSubmit, - submitting, - submitted, -}) { - const {t} = useTranslation(); - - const { + usedFormSubmit: { register, control, handleSubmit, watch, - setError, - formState: {errors, isDirty}, - } = useForm({ - defaultValues: data ? normalizeFormData(data) : data, - }); - useDirtyFormPrompt(!submitted && isDirty); + submitting, + forbidNavigation, + formState: {errors}, + }, +}) { + const {t} = useTranslation(); + + useDirtyFormPrompt(forbidNavigation); const locales = watch('enabledLocales'); @@ -72,7 +45,7 @@ export const WorkspaceForm: FC> = function ({ <> > = function ({ mr: 1, }} locale={ - locales[index].value + locales![index].value } /> ), diff --git a/databox/client/src/components/Form/types.ts b/databox/client/src/components/Form/types.ts index 9d93d79b5..e640209d1 100644 --- a/databox/client/src/components/Form/types.ts +++ b/databox/client/src/components/Form/types.ts @@ -1,9 +1,8 @@ -import {UseFormHandleSubmit} from 'react-hook-form/dist/types/form'; +import {FieldValues} from "react-hook-form"; +import {UseFormSubmit} from '@alchemy/api'; -export type FormProps = { +export type FormProps = { formId: string; + usedFormSubmit: UseFormSubmit; data?: D | undefined; - onSubmit: UseFormHandleSubmit; - submitting: boolean; - submitted: boolean; }; diff --git a/databox/client/src/components/Media/Asset/Actions/CopyAssetsDialog.tsx b/databox/client/src/components/Media/Asset/Actions/CopyAssetsDialog.tsx index f1fa04dba..45f1d49cc 100644 --- a/databox/client/src/components/Media/Asset/Actions/CopyAssetsDialog.tsx +++ b/databox/client/src/components/Media/Asset/Actions/CopyAssetsDialog.tsx @@ -1,6 +1,5 @@ import {useMemo, useState} from 'react'; import {useTranslation} from 'react-i18next'; -import {useForm} from 'react-hook-form'; import {Alert, Typography} from '@mui/material'; import FormDialog from '../../../Dialog/FormDialog'; import {useFormSubmit} from '@alchemy/api'; @@ -60,7 +59,7 @@ function AssetList({ ); } -export default function CopyAssetsDialog({assets, onComplete, open}: Props) { +export default function CopyAssetsDialog({assets, onComplete, open, modalIndex}: Props) { const [workspaceDest, setWorkspaceDest] = useState(); const {t} = useTranslation(); const {closeModal} = useModals(); @@ -69,17 +68,13 @@ export default function CopyAssetsDialog({assets, onComplete, open}: Props) { const count = assets.length; - const byRef = watch('byReference'); - const { handleSubmit, - setError, control, watch, - formState: {errors, isDirty}, - errors: remoteErrors, + formState: {errors}, + remoteErrors, submitting, - submitted, forbidNavigation } = useFormSubmit({ defaultValues: { @@ -119,6 +114,7 @@ export default function CopyAssetsDialog({assets, onComplete, open}: Props) { }, }); useDirtyFormPrompt(forbidNavigation); + const byRef = watch('byReference'); const nonLinkablePerm: Asset[] = useMemo( () => (byRef ? assets.filter(a => !a.capabilities.canShare) : []), @@ -128,130 +124,129 @@ export default function CopyAssetsDialog({assets, onComplete, open}: Props) { () => byRef ? assets.filter( - a => - a.capabilities.canShare && - workspaceDest && - a.workspace.id !== workspaceDest - ) + a => + a.capabilities.canShare && + workspaceDest && + a.workspace.id !== workspaceDest + ) : [], [workspaceDest, nonLinkablePerm] ); const formId = 'copy-assets'; - return ( - } - submitLabel={t('copy_assets.dialog.submit', 'Copy')} - > - - {t( - 'copy_assets.dialog.intro', - 'Where do you want to copy the selected assets?' - )} - - + return } + submitLabel={t('copy_assets.dialog.submit', 'Copy')} + > + + {t( + 'copy_assets.dialog.intro', + 'Where do you want to copy the selected assets?' + )} + + + + + +
-
- - - - - - -
- { - setWorkspaceDest(workspaceId); - }} + +
+ + { + setWorkspaceDest(workspaceId); + }} + control={control} + name={'destination'} + rules={{ + required: true, + }} + label={t( + 'form.copy_assets.destination.label', + 'Destination' + )} + /> + - {nonLinkablePerm.length > 0 && ( - - - {t( - 'form.copy_assets.asset_not_linkable.permission', - `The following assets cannot be copied by reference because you don't have sufficient permission.` - )} - - - - )} - {nonLinkableToOtherWS.length > 0 && ( - - - {t( - 'form.copy_assets.asset_not_linkable.other_ws', - `The following assets cannot be copied by reference in another workspace.` - )} - - - - )} + {nonLinkablePerm.length > 0 && ( + + + {t( + 'form.copy_assets.asset_not_linkable.permission', + `The following assets cannot be copied by reference because you don't have sufficient permission.` + )} + + + + )} + {nonLinkableToOtherWS.length > 0 && ( + + + {t( + 'form.copy_assets.asset_not_linkable.other_ws', + `The following assets cannot be copied by reference in another workspace.` + )} + + + + )} - - - -
- ); + + + +
} diff --git a/databox/client/src/components/Media/Collection/CreateCollection.tsx b/databox/client/src/components/Media/Collection/CreateCollection.tsx index dd7cf9830..551d0ddb1 100644 --- a/databox/client/src/components/Media/Collection/CreateCollection.tsx +++ b/databox/client/src/components/Media/Collection/CreateCollection.tsx @@ -29,7 +29,12 @@ export default function CreateCollection({ }: Props) { const {closeModal} = useModals(); const {t} = useTranslation(); - const {submitting, submitted, handleSubmit, remoteErrors} = useFormSubmit({ + + const usedFormSubmit = useFormSubmit({ + defaultValues: { + title: '', + privacy: 0, + }, onSubmit: async (data: Collection) => { return await postCollection({ ...data, @@ -52,6 +57,7 @@ export default function CreateCollection({ }, }); + const {submitting, remoteErrors} = usedFormSubmit; const formId = 'create-collection'; const title = titlePath ? ( @@ -86,9 +92,7 @@ export default function CreateCollection({ > ); diff --git a/lib/js/api/src/types.ts b/lib/js/api/src/types.ts index 48792a3bc..0e0e2a374 100644 --- a/lib/js/api/src/types.ts +++ b/lib/js/api/src/types.ts @@ -1,3 +1,6 @@ +import useFormSubmit from "./useFormSubmit"; +import {FieldValues} from "react-hook-form"; + export type RequestMeta = { requestStartedAt?: number; responseTime?: number; @@ -9,3 +12,5 @@ declare module 'axios' { errorHandled?: boolean; } } + +export type UseFormSubmit = ReturnType>