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",