summaryrefslogtreecommitdiff
path: root/apps/web/src/actions/freeze-frame-store.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/actions/freeze-frame-store.ts')
-rw-r--r--apps/web/src/actions/freeze-frame-store.ts72
1 files changed, 72 insertions, 0 deletions
diff --git a/apps/web/src/actions/freeze-frame-store.ts b/apps/web/src/actions/freeze-frame-store.ts
new file mode 100644
index 00000000..17102780
--- /dev/null
+++ b/apps/web/src/actions/freeze-frame-store.ts
@@ -0,0 +1,72 @@
+"use client";
+
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+
+export const DEFAULT_FREEZE_DURATION_SECONDS = 3;
+export const MAX_FREEZE_DURATION_SECONDS = 600;
+
+export function isValidFreezeDuration({
+ seconds,
+}: {
+ seconds: number;
+}): boolean {
+ return (
+ Number.isFinite(seconds) &&
+ seconds > 0 &&
+ seconds <= MAX_FREEZE_DURATION_SECONDS
+ );
+}
+
+type DurationResolver = ({ seconds }: { seconds: number | null }) => void;
+
+interface FreezeFrameState {
+ isOpen: boolean;
+ lastDurationSeconds: number;
+ resolver: DurationResolver | null;
+ requestDuration: () => Promise<number | null>;
+ confirmDuration: ({ seconds }: { seconds: number }) => void;
+ cancelDuration: () => void;
+}
+
+export const useFreezeFrameStore = create<FreezeFrameState>()(
+ persist(
+ (set, get) => ({
+ isOpen: false,
+ lastDurationSeconds: DEFAULT_FREEZE_DURATION_SECONDS,
+ resolver: null,
+
+ requestDuration: () =>
+ new Promise<number | null>((resolve) => {
+ // A new invocation supersedes any pending one: resolve it as
+ // cancelled so its handler aborts instead of hanging.
+ get().resolver?.({ seconds: null });
+ set({
+ isOpen: true,
+ resolver: ({ seconds }) => resolve(seconds),
+ });
+ }),
+ confirmDuration: ({ seconds }) => {
+ if (!isValidFreezeDuration({ seconds })) {
+ return;
+ }
+ get().resolver?.({ seconds });
+ set({
+ isOpen: false,
+ resolver: null,
+ lastDurationSeconds: seconds,
+ });
+ },
+ cancelDuration: () => {
+ get().resolver?.({ seconds: null });
+ set({ isOpen: false, resolver: null });
+ },
+ }),
+ {
+ name: "freeze-frame-store",
+ partialize: (state) => ({
+ lastDurationSeconds: state.lastDurationSeconds,
+ }),
+ },
+ ),
+);