import React, { useEffect } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import * as GQL from "src/core/generated-graphql"; import * as yup from "yup"; import Mousetrap from "mousetrap"; import { Icon, StudioSelect, DetailsEditNavbar } from "src/components/Shared"; import { Button, Form, Col, Row } from "react-bootstrap"; import { FormUtils, ImageUtils, getStashIDs } from "src/utils"; import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars"; import { useFormik } from "formik"; import { Prompt } from "react-router-dom"; import { StringListInput } from "../../Shared/StringListInput"; interface IStudioEditPanel { studio: Partial; onSubmit: ( studio: Partial ) => void; onCancel: () => void; onDelete: () => void; onImageChange?: (image?: string | null) => void; onImageEncoding?: (loading?: boolean) => void; } export const StudioEditPanel: React.FC = ({ studio, onSubmit, onCancel, onDelete, onImageChange, onImageEncoding, }) => { const intl = useIntl(); const isNew = !studio || !studio.id; const imageEncoding = ImageUtils.usePasteImage(onImageLoad, true); const schema = yup.object({ name: yup.string().required(), url: yup.string().optional().nullable(), details: yup.string().optional().nullable(), image: yup.string().optional().nullable(), rating: yup.number().optional().nullable(), parent_id: yup.string().optional().nullable(), stash_ids: yup.mixed().optional().nullable(), aliases: yup .array(yup.string().required()) .optional() .test({ name: "unique", // eslint-disable-next-line @typescript-eslint/no-explicit-any test: (value: any) => { return (value ?? []).length === new Set(value).size; }, message: "aliases must be unique", }), }); const initialValues = { name: studio.name ?? "", url: studio.url ?? "", details: studio.details ?? "", image: undefined, rating: studio.rating ?? null, parent_id: studio.parent_studio?.id, stash_ids: studio.stash_ids ?? undefined, aliases: studio.aliases, }; type InputValues = typeof initialValues; const formik = useFormik({ initialValues, validationSchema: schema, onSubmit: (values) => onSubmit(getStudioInput(values)), }); function setRating(v: number) { formik.setFieldValue("rating", v); } function onImageLoad(imageData: string) { formik.setFieldValue("image", imageData); } function getStudioInput(values: InputValues) { const input: Partial = { ...values, stash_ids: getStashIDs(values.stash_ids), }; if (studio && studio.id) { (input as GQL.StudioUpdateInput).id = studio.id; } return input; } // set up hotkeys useEffect(() => { Mousetrap.bind("s s", () => formik.handleSubmit()); // numeric keypresses get caught by jwplayer, so blur the element // if the rating sequence is started Mousetrap.bind("r", () => { if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } Mousetrap.bind("0", () => setRating(NaN)); Mousetrap.bind("1", () => setRating(1)); Mousetrap.bind("2", () => setRating(2)); Mousetrap.bind("3", () => setRating(3)); Mousetrap.bind("4", () => setRating(4)); Mousetrap.bind("5", () => setRating(5)); setTimeout(() => { Mousetrap.unbind("0"); Mousetrap.unbind("1"); Mousetrap.unbind("2"); Mousetrap.unbind("3"); Mousetrap.unbind("4"); Mousetrap.unbind("5"); }, 1000); }); return () => { Mousetrap.unbind("s s"); Mousetrap.unbind("e"); }; }); useEffect(() => { if (onImageChange) { onImageChange(formik.values.image); } return () => onImageChange?.(); }, [formik.values.image, onImageChange]); useEffect(() => onImageEncoding?.(imageEncoding), [ onImageEncoding, imageEncoding, ]); function onImageChangeHandler(event: React.FormEvent) { ImageUtils.onImageChange(event, onImageLoad); } function onImageChangeURL(url: string) { formik.setFieldValue("image", url); } const removeStashID = (stashID: GQL.StashIdInput) => { formik.setFieldValue( "stash_ids", (formik.values.stash_ids ?? []).filter( (s) => !(s.endpoint === stashID.endpoint && s.stash_id === stashID.stash_id) ) ); }; function renderStashIDs() { if (!formik.values.stash_ids?.length) { return; } return ( StashIDs
    {formik.values.stash_ids.map((stashID) => { const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; const link = base ? ( {stashID.stash_id} ) : ( stashID.stash_id ); return (
  • {link}
  • ); })}
); } return ( <> { // Check if it's a redirect after studio creation if (action === "PUSH" && location.pathname.startsWith("/studios/")) return true; return intl.formatMessage({ id: "dialogs.unsaved_changes" }); }} />
{FormUtils.renderLabel({ title: intl.formatMessage({ id: "name" }), })} {formik.errors.name} {FormUtils.renderLabel({ title: intl.formatMessage({ id: "url" }), })} {formik.errors.url} {FormUtils.renderLabel({ title: intl.formatMessage({ id: "details" }), })} {formik.errors.details} {FormUtils.renderLabel({ title: intl.formatMessage({ id: "parent_studios" }), })} formik.setFieldValue( "parent_id", items.length > 0 ? items[0]?.id : null ) } ids={formik.values.parent_id ? [formik.values.parent_id] : []} excludeIds={studio.id ? [studio.id] : []} /> {FormUtils.renderLabel({ title: intl.formatMessage({ id: "rating" }), })} formik.setFieldValue("rating", value ?? null) } /> {renderStashIDs()} formik.setFieldValue("aliases", value)} errors={formik.errors.aliases} />
formik.handleSubmit()} saveDisabled={!formik.dirty} onImageChange={onImageChangeHandler} onImageChangeURL={onImageChangeURL} onClearImage={() => { formik.setFieldValue("image", null); }} onDelete={onDelete} acceptSVG /> ); };