diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index 0833452ba..4a374065c 100644 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -21,6 +21,7 @@ import { useSystemStatus, } from "src/core/StashService"; import flattenMessages from "./utils/flattenMessages"; +import * as yup from "yup"; import Mousetrap from "mousetrap"; import MousetrapPause from "mousetrap-pause"; import { ErrorBoundary } from "./components/ErrorBoundary"; @@ -126,7 +127,18 @@ export const App: React.FC = () => { } ); - setMessages(flattenMessages(mergedMessages)); + const newMessages = flattenMessages(mergedMessages) as Record< + string, + string + >; + + yup.setLocale({ + mixed: { + required: newMessages["validation.required"], + }, + }); + + setMessages(newMessages); }; setLocale(); diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx index 575653a3a..5e7654d03 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx @@ -38,6 +38,7 @@ import { faSyncAlt } from "@fortawesome/free-solid-svg-icons"; import { galleryTitle } from "src/core/galleries"; import { useRatingKeybinds } from "src/hooks/keybinds"; import { ConfigurationContext } from "src/hooks/Config"; +import isEqual from "lodash-es/isEqual"; interface IProps { gallery: Partial; @@ -79,44 +80,50 @@ export const GalleryEditPanel: React.FC = ({ isNew || (gallery?.files?.length === 0 && !gallery?.folder); const schema = yup.object({ - title: titleRequired - ? yup.string().required() - : yup.string().optional().nullable(), - details: yup.string().optional().nullable(), - url: yup.string().optional().nullable(), - date: yup.string().optional().nullable(), - rating100: yup.number().optional().nullable(), - studio_id: yup.string().optional().nullable(), - performer_ids: yup.array(yup.string().required()).optional().nullable(), - tag_ids: yup.array(yup.string().required()).optional().nullable(), - scene_ids: yup.array(yup.string().required()).optional().nullable(), + title: titleRequired ? yup.string().required() : yup.string().ensure(), + url: yup.string().ensure(), + date: yup + .string() + .ensure() + .test({ + name: "date", + test: (value) => { + if (!value) return true; + if (!value.match(/^\d{4}-\d{2}-\d{2}$/)) return false; + if (Number.isNaN(Date.parse(value))) return false; + return true; + }, + message: intl.formatMessage({ id: "validation.date_invalid_form" }), + }), + rating100: yup.number().nullable().defined(), + studio_id: yup.string().required().nullable(), + performer_ids: yup.array(yup.string().required()).defined(), + tag_ids: yup.array(yup.string().required()).defined(), + scene_ids: yup.array(yup.string().required()).defined(), + details: yup.string().ensure(), }); const initialValues = { title: gallery?.title ?? "", - details: gallery?.details ?? "", url: gallery?.url ?? "", date: gallery?.date ?? "", rating100: gallery?.rating100 ?? null, - studio_id: gallery?.studio?.id, + studio_id: gallery?.studio?.id ?? null, performer_ids: (gallery?.performers ?? []).map((p) => p.id), tag_ids: (gallery?.tags ?? []).map((t) => t.id), scene_ids: (gallery?.scenes ?? []).map((s) => s.id), + details: gallery?.details ?? "", }; - type InputValues = typeof initialValues; + type InputValues = yup.InferType; - const formik = useFormik({ + const formik = useFormik({ initialValues, + enableReinitialize: true, validationSchema: schema, - onSubmit: (values) => onSave(getGalleryInput(values)), + onSubmit: (values) => onSave(values), }); - // always dirty if creating a new gallery with a title - if (isNew && gallery?.title) { - formik.dirty = true; - } - function setRating(v: number) { formik.setFieldValue("rating100", v); } @@ -166,24 +173,13 @@ export const GalleryEditPanel: React.FC = ({ setQueryableScrapers(newQueryableScrapers); }, [Scrapers]); - function getGalleryInput( - input: InputValues - ): GQL.GalleryCreateInput | GQL.GalleryUpdateInput { - return { - id: isNew ? undefined : gallery?.id ?? "", - ...input, - }; - } - - async function onSave( - input: GQL.GalleryCreateInput | GQL.GalleryUpdateInput - ) { + async function onSave(input: GQL.GalleryCreateInput) { setIsLoading(true); try { if (isNew) { const result = await createGallery({ variables: { - input: input as GQL.GalleryCreateInput, + input, }, }); if (result.data?.galleryCreate) { @@ -202,7 +198,10 @@ export const GalleryEditPanel: React.FC = ({ } else { const result = await updateGallery({ variables: { - input: input as GQL.GalleryUpdateInput, + input: { + id: gallery.id!, + ...input, + }, }, }); if (result.data?.galleryUpdate) { @@ -216,7 +215,7 @@ export const GalleryEditPanel: React.FC = ({ } ), }); - formik.resetForm({ values: formik.values }); + formik.resetForm(); } } } catch (e) { @@ -271,7 +270,10 @@ export const GalleryEditPanel: React.FC = ({ return; } - const currentGallery = getGalleryInput(formik.values); + const currentGallery = { + id: gallery.id!, + ...formik.values, + }; return ( = ({ function renderTextField(field: string, title: string, placeholder?: string) { return ( - + {FormUtils.renderLabel({ title, })} @@ -419,7 +421,9 @@ export const GalleryEditPanel: React.FC = ({