From 96fdd94a01cc17a75b9a294fd98afba9a1f68927 Mon Sep 17 00:00:00 2001 From: Ian McKenzie <13459320+ikmckenz@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:34:22 -0700 Subject: [PATCH] Create a section in the history panel to reset scene activity (#5168) Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com> --- graphql/schema/schema.graphql | 7 ++ internal/api/resolver_mutation_scene.go | 18 ++++ pkg/models/mocks/SceneReaderWriter.go | 21 +++++ pkg/models/repository_scene.go | 1 + pkg/sqlite/scene.go | 24 ++++++ ui/v2.5/graphql/mutations/scene.graphql | 12 +++ .../Scenes/SceneDetails/SceneHistoryPanel.tsx | 85 ++++++++++++++++++- ui/v2.5/src/core/StashService.ts | 15 ++++ ui/v2.5/src/locales/en-GB.json | 2 + 9 files changed, 184 insertions(+), 1 deletion(-) diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index da096707ac5..f11edb46f36 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -276,6 +276,13 @@ type Mutation { "Sets the resume time point (if provided) and adds the provided duration to the scene's play duration" sceneSaveActivity(id: ID!, resume_time: Float, playDuration: Float): Boolean! + "Resets the resume time point and play duration" + sceneResetActivity( + id: ID! + reset_resume: Boolean + reset_duration: Boolean + ): Boolean! + "Increments the play count for the scene. Returns the new play count value." sceneIncrementPlayCount(id: ID!): Int! @deprecated(reason: "Use sceneAddPlay instead") diff --git a/internal/api/resolver_mutation_scene.go b/internal/api/resolver_mutation_scene.go index 356214d59e1..ca99dafc150 100644 --- a/internal/api/resolver_mutation_scene.go +++ b/internal/api/resolver_mutation_scene.go @@ -847,6 +847,24 @@ func (r *mutationResolver) SceneSaveActivity(ctx context.Context, id string, res return ret, nil } +func (r *mutationResolver) SceneResetActivity(ctx context.Context, id string, resetResume *bool, resetDuration *bool) (ret bool, err error) { + sceneID, err := strconv.Atoi(id) + if err != nil { + return false, fmt.Errorf("converting id: %w", err) + } + + if err := r.withTxn(ctx, func(ctx context.Context) error { + qb := r.repository.Scene + + ret, err = qb.ResetActivity(ctx, sceneID, utils.IsTrue(resetResume), utils.IsTrue(resetDuration)) + return err + }); err != nil { + return false, err + } + + return ret, nil +} + // deprecated func (r *mutationResolver) SceneIncrementPlayCount(ctx context.Context, id string) (ret int, err error) { sceneID, err := strconv.Atoi(id) diff --git a/pkg/models/mocks/SceneReaderWriter.go b/pkg/models/mocks/SceneReaderWriter.go index 3787d8182d3..e12ae999c6f 100644 --- a/pkg/models/mocks/SceneReaderWriter.go +++ b/pkg/models/mocks/SceneReaderWriter.go @@ -1267,6 +1267,27 @@ func (_m *SceneReaderWriter) QueryCount(ctx context.Context, sceneFilter *models return r0, r1 } +// ResetActivity provides a mock function with given fields: ctx, sceneID, resetResume, resetDuration +func (_m *SceneReaderWriter) ResetActivity(ctx context.Context, sceneID int, resetResume bool, resetDuration bool) (bool, error) { + ret := _m.Called(ctx, sceneID, resetResume, resetDuration) + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, int, bool, bool) bool); ok { + r0 = rf(ctx, sceneID, resetResume, resetDuration) + } else { + r0 = ret.Get(0).(bool) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, int, bool, bool) error); ok { + r1 = rf(ctx, sceneID, resetResume, resetDuration) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ResetO provides a mock function with given fields: ctx, id func (_m *SceneReaderWriter) ResetO(ctx context.Context, id int) (int, error) { ret := _m.Called(ctx, id) diff --git a/pkg/models/repository_scene.go b/pkg/models/repository_scene.go index bbd69606635..60783fff5cd 100644 --- a/pkg/models/repository_scene.go +++ b/pkg/models/repository_scene.go @@ -137,6 +137,7 @@ type SceneWriter interface { OHistoryWriter ViewHistoryWriter SaveActivity(ctx context.Context, sceneID int, resumeTime *float64, playDuration *float64) (bool, error) + ResetActivity(ctx context.Context, sceneID int, resetResume bool, resetDuration bool) (bool, error) } // SceneReaderWriter provides all scene methods. diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index 99da461e7d5..9b8bd73157b 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -1234,6 +1234,30 @@ func (qb *SceneStore) SaveActivity(ctx context.Context, id int, resumeTime *floa return true, nil } +func (qb *SceneStore) ResetActivity(ctx context.Context, id int, resetResume bool, resetDuration bool) (bool, error) { + if err := qb.tableMgr.checkIDExists(ctx, id); err != nil { + return false, err + } + + record := goqu.Record{} + + if resetResume { + record["resume_time"] = 0.0 + } + + if resetDuration { + record["play_duration"] = 0.0 + } + + if len(record) > 0 { + if err := qb.tableMgr.updateByID(ctx, id, record); err != nil { + return false, err + } + } + + return true, nil +} + func (qb *SceneStore) GetURLs(ctx context.Context, sceneID int) ([]string, error) { return scenesURLsTableMgr.get(ctx, sceneID) } diff --git a/ui/v2.5/graphql/mutations/scene.graphql b/ui/v2.5/graphql/mutations/scene.graphql index 874db5c7316..c04857b7fc6 100644 --- a/ui/v2.5/graphql/mutations/scene.graphql +++ b/ui/v2.5/graphql/mutations/scene.graphql @@ -34,6 +34,18 @@ mutation SceneSaveActivity( ) } +mutation SceneResetActivity( + $id: ID! + $reset_resume: Boolean! + $reset_duration: Boolean! +) { + sceneResetActivity( + id: $id + reset_resume: $reset_resume + reset_duration: $reset_duration + ) +} + mutation SceneAddPlay($id: ID!, $times: [Timestamp!]) { sceneAddPlay(id: $id, times: $times) { count diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneHistoryPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneHistoryPanel.tsx index 7baaab151a1..1ac9dd5a27b 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneHistoryPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneHistoryPanel.tsx @@ -18,8 +18,10 @@ import { useSceneIncrementPlayCount, useSceneResetO, useSceneResetPlayCount, + useSceneResetActivity, } from "src/core/StashService"; import * as GQL from "src/core/generated-graphql"; +import { useToast } from "src/hooks/Toast"; import { TextField } from "src/utils/field"; import TextUtils from "src/utils/text"; @@ -72,9 +74,19 @@ const History: React.FC<{ const HistoryMenu: React.FC<{ hasHistory: boolean; + showResetResumeDuration: boolean; onAddDate: () => void; onClearDates: () => void; -}> = ({ hasHistory, onAddDate, onClearDates }) => { + resetResume: () => void; + resetDuration: () => void; +}> = ({ + hasHistory, + showResetResumeDuration, + onAddDate, + onClearDates, + resetResume, + resetDuration, +}) => { const intl = useIntl(); return ( @@ -101,6 +113,22 @@ const HistoryMenu: React.FC<{ )} + {showResetResumeDuration && ( + resetResume()} + > + + + )} + {showResetResumeDuration && ( + resetDuration()} + > + + + )} ); @@ -142,6 +170,7 @@ interface ISceneHistoryProps { export const SceneHistoryPanel: React.FC = ({ scene }) => { const intl = useIntl(); + const Toast = useToast(); const [dialogs, setDialogs] = React.useState({ playHistory: false, @@ -160,6 +189,8 @@ export const SceneHistoryPanel: React.FC = ({ scene }) => { const [incrementOCount] = useSceneIncrementO(scene.id); const [decrementOCount] = useSceneDecrementO(scene.id); const [resetO] = useSceneResetO(scene.id); + const [resetResume] = useSceneResetActivity(scene.id, true, false); + const [resetDuration] = useSceneResetActivity(scene.id, false, true); function dateStringToISOString(time: string) { const date = TextUtils.stringToFuzzyDateTime(time); @@ -221,6 +252,52 @@ export const SceneHistoryPanel: React.FC = ({ scene }) => { }); } + async function handleResetResume() { + try { + await resetResume({ + variables: { + id: scene.id, + reset_resume: true, + reset_duration: false, + }, + }); + + Toast.success( + intl.formatMessage( + { id: "toast.updated_entity" }, + { + entity: intl.formatMessage({ id: "scene" }).toLocaleLowerCase(), + } + ) + ); + } catch (e) { + Toast.error(e); + } + } + + async function handleResetDuration() { + try { + await resetDuration({ + variables: { + id: scene.id, + reset_resume: false, + reset_duration: true, + }, + }); + + Toast.success( + intl.formatMessage( + { id: "toast.updated_entity" }, + { + entity: intl.formatMessage({ id: "scene" }).toLocaleLowerCase(), + } + ) + ); + } catch (e) { + Toast.error(e); + } + } + function maybeRenderDialogs() { return ( <> @@ -296,8 +373,11 @@ export const SceneHistoryPanel: React.FC = ({ scene }) => { 0} + showResetResumeDuration={true} onAddDate={() => setDialogPartial({ addPlay: true })} onClearDates={() => setDialogPartial({ playHistory: true })} + resetResume={() => handleResetResume()} + resetDuration={() => handleResetDuration()} /> @@ -336,8 +416,11 @@ export const SceneHistoryPanel: React.FC = ({ scene }) => { 0} + showResetResumeDuration={false} onAddDate={() => setDialogPartial({ addO: true })} onClearDates={() => setDialogPartial({ oHistory: true })} + resetResume={() => handleResetResume()} + resetDuration={() => handleResetDuration()} /> diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index e0ff90aaf70..ee1d17ef9e4 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -786,6 +786,21 @@ export const useSceneResetO = (id: string) => }, }); +export const useSceneResetActivity = ( + id: string, + reset_resume: boolean, + reset_duration: boolean +) => + GQL.useSceneResetActivityMutation({ + variables: { id, reset_resume, reset_duration }, + update(cache, result) { + if (!result.data?.sceneResetActivity) return; + + evictTypeFields(cache, sceneMutationImpactedTypeFields); + evictQueries(cache, sceneMutationImpactedQueries); + }, + }); + export const useSceneGenerateScreenshot = () => GQL.useSceneGenerateScreenshotMutation(); diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index b572140139b..b76016c8c09 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -94,6 +94,8 @@ "remove_from_gallery": "Remove from Gallery", "rename_gen_files": "Rename generated files", "rescan": "Rescan", + "reset_play_duration": "Reset play duration", + "reset_resume_time": "Reset resume time", "reset_cover": "Restore Default Cover", "reshuffle": "Reshuffle", "running": "running",