From e667dbe6f02f146c882a3bde5cb935e49d2bb0bc Mon Sep 17 00:00:00 2001 From: Gykes <24581046+Gykes@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:32:42 -0800 Subject: [PATCH 1/5] initial --- internal/api/resolver_mutation_scene.go | 19 ++++++++-------- .../Scenes/SceneDetails/SceneEditPanel.tsx | 15 ++++++++++++- ui/v2.5/src/components/Shared/ImageInput.tsx | 22 +++++++++++++++++-- ui/v2.5/src/core/StashService.ts | 9 +++++++- ui/v2.5/src/locales/en-GB.json | 1 + 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/internal/api/resolver_mutation_scene.go b/internal/api/resolver_mutation_scene.go index c08184add0..b50ba301ff 100644 --- a/internal/api/resolver_mutation_scene.go +++ b/internal/api/resolver_mutation_scene.go @@ -297,7 +297,8 @@ func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUp } var coverImageData []byte - if input.CoverImage != nil { + coverImageProvided := input.CoverImage != nil + if coverImageProvided { var err error coverImageData, err = utils.ProcessImageInput(ctx, *input.CoverImage) if err != nil { @@ -310,21 +311,21 @@ func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUp return nil, err } - if err := r.sceneUpdateCoverImage(ctx, scene, coverImageData); err != nil { - return nil, err + if coverImageProvided { + if err := r.sceneUpdateCoverImage(ctx, scene, coverImageData); err != nil { + return nil, err + } } return scene, nil } func (r *mutationResolver) sceneUpdateCoverImage(ctx context.Context, s *models.Scene, coverImageData []byte) error { - if len(coverImageData) > 0 { - qb := r.repository.Scene + qb := r.repository.Scene - // update cover table - if err := qb.UpdateCover(ctx, s.ID, coverImageData); err != nil { - return err - } + // update cover table - empty data will clear the cover + if err := qb.UpdateCover(ctx, s.ID, coverImageData); err != nil { + return err } return nil diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index 9ba5059de2..bfc8a2e4e5 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -161,7 +161,14 @@ export const SceneEditPanel: React.FC = ({ initialValues, enableReinitialize: true, validate: yupFormikValidate(schema), - onSubmit: (values) => onSave(schema.cast(values)), + onSubmit: (values) => { + const input = schema.cast(values); + // Preserve empty string for cover_image to allow clearing + if (values.cover_image === "") { + input.cover_image = ""; + } + return onSave(input); + }, }); const { tags, updateTagsStateFromScraper, tagsControl } = useTagsEdit( @@ -289,6 +296,11 @@ export const SceneEditPanel: React.FC = ({ ImageUtils.onImageChange(event, onImageLoad); } + function onResetCover() { + formik.setFieldValue("cover_image", ""); + Toast.success(intl.formatMessage({ id: "toast.cover_image_reset" })); + } + async function onScrapeClicked(s: GQL.ScraperSourceInput) { setIsLoading(true); try { @@ -827,6 +839,7 @@ export const SceneEditPanel: React.FC = ({ isEditing onImageChange={onCoverImageChange} onImageURL={onImageLoad} + onReset={scene.id ? onResetCover : undefined} /> diff --git a/ui/v2.5/src/components/Shared/ImageInput.tsx b/ui/v2.5/src/components/Shared/ImageInput.tsx index 94a83f952d..931e36608e 100644 --- a/ui/v2.5/src/components/Shared/ImageInput.tsx +++ b/ui/v2.5/src/components/Shared/ImageInput.tsx @@ -10,7 +10,7 @@ import { import { useIntl } from "react-intl"; import { ModalComponent } from "./Modal"; import { Icon } from "./Icon"; -import { faFile, faLink } from "@fortawesome/free-solid-svg-icons"; +import { faFile, faLink, faTrashAlt } from "@fortawesome/free-solid-svg-icons"; import { PatchComponent } from "src/patch"; interface IImageInput { @@ -18,6 +18,7 @@ interface IImageInput { text?: string; onImageChange: (event: React.ChangeEvent) => void; onImageURL?: (url: string) => void; + onReset?: () => void; acceptSVG?: boolean; } @@ -27,7 +28,14 @@ function acceptExtensions(acceptSVG: boolean = false) { export const ImageInput: React.FC = PatchComponent( "ImageInput", - ({ isEditing, text, onImageChange, onImageURL, acceptSVG = false }) => { + ({ + isEditing, + text, + onImageChange, + onImageURL, + onReset, + acceptSVG = false, + }) => { const [isShowDialog, setIsShowDialog] = useState(false); const [url, setURL] = useState(""); const intl = useIntl(); @@ -137,6 +145,16 @@ export const ImageInput: React.FC = PatchComponent( {text ?? intl.formatMessage({ id: "actions.set_image" })} + {onReset && ( + + )} ); } diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index c4f3d4732e..7489675d6f 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -831,7 +831,14 @@ export const useSceneResetActivity = ( }); export const useSceneGenerateScreenshot = () => - GQL.useSceneGenerateScreenshotMutation(); + GQL.useSceneGenerateScreenshotMutation({ + update(cache, result) { + if (!result.data?.sceneGenerateScreenshot) return; + + evictTypeFields(cache, sceneMutationImpactedTypeFields); + evictQueries(cache, sceneMutationImpactedQueries); + }, + }); export const mutateSceneSetPrimaryFile = (id: string, fileID: string) => client.mutate({ diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 21d3f6a241..1b6555c6a7 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -1556,6 +1556,7 @@ "toast": { "added_entity": "Added {count, plural, one {{singularEntity}} other {{pluralEntity}}}", "added_generation_job_to_queue": "Added generation job to queue", + "cover_image_reset": "Cover image will be reset on save", "created_entity": "Created {entity}", "default_filter_set": "Default filter set", "delete_past_tense": "Deleted {count, plural, one {{singularEntity}} other {{pluralEntity}}}", From 5913c2584cfe956df3d25f7b0024404e201ddd4a Mon Sep 17 00:00:00 2001 From: Gykes <24581046+Gykes@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:39:05 -0800 Subject: [PATCH 2/5] revert cache clear --- ui/v2.5/src/core/StashService.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index 7489675d6f..c4f3d4732e 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -831,14 +831,7 @@ export const useSceneResetActivity = ( }); export const useSceneGenerateScreenshot = () => - GQL.useSceneGenerateScreenshotMutation({ - update(cache, result) { - if (!result.data?.sceneGenerateScreenshot) return; - - evictTypeFields(cache, sceneMutationImpactedTypeFields); - evictQueries(cache, sceneMutationImpactedQueries); - }, - }); + GQL.useSceneGenerateScreenshotMutation(); export const mutateSceneSetPrimaryFile = (id: string, fileID: string) => client.mutate({ From f3b56c96bf384e31a0085bf0eae3a81d6fe3a477 Mon Sep 17 00:00:00 2001 From: Gykes <24581046+Gykes@users.noreply.github.com> Date: Sun, 21 Dec 2025 10:42:36 -0800 Subject: [PATCH 3/5] update from trashcan to clear image --- ui/v2.5/src/components/Shared/ImageInput.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/ui/v2.5/src/components/Shared/ImageInput.tsx b/ui/v2.5/src/components/Shared/ImageInput.tsx index 931e36608e..7675da41ff 100644 --- a/ui/v2.5/src/components/Shared/ImageInput.tsx +++ b/ui/v2.5/src/components/Shared/ImageInput.tsx @@ -10,7 +10,7 @@ import { import { useIntl } from "react-intl"; import { ModalComponent } from "./Modal"; import { Icon } from "./Icon"; -import { faFile, faLink, faTrashAlt } from "@fortawesome/free-solid-svg-icons"; +import { faFile, faLink } from "@fortawesome/free-solid-svg-icons"; import { PatchComponent } from "src/patch"; interface IImageInput { @@ -146,13 +146,8 @@ export const ImageInput: React.FC = PatchComponent( {onReset && ( - )} From 9076f2f9d7425cd13d4c477c3e19b42a5b8649bf Mon Sep 17 00:00:00 2001 From: Gykes <24581046+Gykes@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:35:47 -0800 Subject: [PATCH 4/5] convert to match performers --- internal/api/resolver_mutation_scene.go | 6 +++--- internal/manager/running_streams.go | 6 ++++++ .../Scenes/SceneDetails/SceneEditPanel.tsx | 12 ++---------- ui/v2.5/src/locales/en-GB.json | 1 - 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/internal/api/resolver_mutation_scene.go b/internal/api/resolver_mutation_scene.go index b50ba301ff..cb2aa7d243 100644 --- a/internal/api/resolver_mutation_scene.go +++ b/internal/api/resolver_mutation_scene.go @@ -297,8 +297,8 @@ func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUp } var coverImageData []byte - coverImageProvided := input.CoverImage != nil - if coverImageProvided { + coverImageIncluded := translator.hasField("cover_image") + if input.CoverImage != nil { var err error coverImageData, err = utils.ProcessImageInput(ctx, *input.CoverImage) if err != nil { @@ -311,7 +311,7 @@ func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUp return nil, err } - if coverImageProvided { + if coverImageIncluded { if err := r.sceneUpdateCoverImage(ctx, scene, coverImageData); err != nil { return nil, err } diff --git a/internal/manager/running_streams.go b/internal/manager/running_streams.go index 18ac3b0426..175ee0a205 100644 --- a/internal/manager/running_streams.go +++ b/internal/manager/running_streams.go @@ -62,6 +62,12 @@ func (s *SceneServer) StreamSceneDirect(scene *models.Scene, w http.ResponseWrit } func (s *SceneServer) ServeScreenshot(scene *models.Scene, w http.ResponseWriter, r *http.Request) { + // if default flag is set, return the default image + if r.URL.Query().Get("default") == "true" { + utils.ServeImage(w, r, static.ReadAll(static.DefaultSceneImage)) + return + } + var cover []byte readTxnErr := txn.WithReadTxn(r.Context(), s.TxnManager, func(ctx context.Context) error { var err error diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index bfc8a2e4e5..39b96ce457 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -161,14 +161,7 @@ export const SceneEditPanel: React.FC = ({ initialValues, enableReinitialize: true, validate: yupFormikValidate(schema), - onSubmit: (values) => { - const input = schema.cast(values); - // Preserve empty string for cover_image to allow clearing - if (values.cover_image === "") { - input.cover_image = ""; - } - return onSave(input); - }, + onSubmit: (values) => onSave(schema.cast(values)), }); const { tags, updateTagsStateFromScraper, tagsControl } = useTagsEdit( @@ -297,8 +290,7 @@ export const SceneEditPanel: React.FC = ({ } function onResetCover() { - formik.setFieldValue("cover_image", ""); - Toast.success(intl.formatMessage({ id: "toast.cover_image_reset" })); + formik.setFieldValue("cover_image", null); } async function onScrapeClicked(s: GQL.ScraperSourceInput) { diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 1b6555c6a7..21d3f6a241 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -1556,7 +1556,6 @@ "toast": { "added_entity": "Added {count, plural, one {{singularEntity}} other {{pluralEntity}}}", "added_generation_job_to_queue": "Added generation job to queue", - "cover_image_reset": "Cover image will be reset on save", "created_entity": "Created {entity}", "default_filter_set": "Default filter set", "delete_past_tense": "Deleted {count, plural, one {{singularEntity}} other {{pluralEntity}}}", From c48cc2954b4b4dcef889d214e88ea24117b628c6 Mon Sep 17 00:00:00 2001 From: Gykes <24581046+Gykes@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:39:55 -0800 Subject: [PATCH 5/5] update route --- internal/api/routes_scene.go | 7 +++++++ internal/manager/running_streams.go | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/api/routes_scene.go b/internal/api/routes_scene.go index 95e7c9d448..2905bd53a5 100644 --- a/internal/api/routes_scene.go +++ b/internal/api/routes_scene.go @@ -12,6 +12,7 @@ import ( "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" + "github.com/stashapp/stash/internal/static" "github.com/stashapp/stash/pkg/ffmpeg" "github.com/stashapp/stash/pkg/file/video" "github.com/stashapp/stash/pkg/fsutil" @@ -243,6 +244,12 @@ func (rs sceneRoutes) streamSegment(w http.ResponseWriter, r *http.Request, stre } func (rs sceneRoutes) Screenshot(w http.ResponseWriter, r *http.Request) { + // if default flag is set, return the default image + if r.URL.Query().Get("default") == "true" { + utils.ServeImage(w, r, static.ReadAll(static.DefaultSceneImage)) + return + } + scene := r.Context().Value(sceneKey).(*models.Scene) ss := manager.SceneServer{ diff --git a/internal/manager/running_streams.go b/internal/manager/running_streams.go index 175ee0a205..18ac3b0426 100644 --- a/internal/manager/running_streams.go +++ b/internal/manager/running_streams.go @@ -62,12 +62,6 @@ func (s *SceneServer) StreamSceneDirect(scene *models.Scene, w http.ResponseWrit } func (s *SceneServer) ServeScreenshot(scene *models.Scene, w http.ResponseWriter, r *http.Request) { - // if default flag is set, return the default image - if r.URL.Query().Get("default") == "true" { - utils.ServeImage(w, r, static.ReadAll(static.DefaultSceneImage)) - return - } - var cover []byte readTxnErr := txn.WithReadTxn(r.Context(), s.TxnManager, func(ctx context.Context) error { var err error