TRACES: | DR-204 484 ungated `console.*` calls across 63 non-test frontend files shipped to end users with no way to turn them off. Mechanical substitution, no control flow, error handling or message semantics changed: console.log / console.debug -> log.debug console.info -> log.info console.warn -> log.warn console.error -> log.error Hand-written `"[Scope] …"` prefixes are dropped where the logger's scope now carries them; scope names that already existed are preserved verbatim (`[Auth]`, `[VideoPlayer]`, `[PiP]`, …) and inferred from the filename where a file had none. `src/routes/player/[id]/+page.svelte` keeps its `NextEpisode` and `AutoPlay` sub-scopes as separate loggers rather than flattening them into the page scope. `grep -rn 'console\.' src/` now matches nothing outside the tests and the facade itself.
98 lines
3.0 KiB
Svelte
98 lines
3.0 KiB
Svelte
<!--
|
|
Erase watch history for a series or a season.
|
|
|
|
The backend does the work (`repository_clear_watch_history` → Jellyfin's
|
|
mark-unplayed, which is recursive over a container and also zeroes resume
|
|
positions); this only confirms the intent and reports the outcome. Clearing a
|
|
series returns it to "never watched", so it reopens on S1E1.
|
|
|
|
TRACES: UR-064 | DR-106
|
|
-->
|
|
<script lang="ts">
|
|
import { auth } from "$lib/stores/auth";
|
|
import { isServerReachable } from "$lib/stores/connectivity";
|
|
import { createLogger } from "$lib/utils/logger";
|
|
|
|
const log = createLogger("ClearHistoryButton");
|
|
|
|
interface Props {
|
|
/** Series or season id to clear. */
|
|
itemId: string;
|
|
/** Name shown in the confirm prompt. */
|
|
itemName: string;
|
|
/** What is being cleared, for the prompt wording. */
|
|
scope: "series" | "season";
|
|
size?: "sm" | "lg";
|
|
/** Called after a successful clear so the caller can reload. */
|
|
onCleared?: () => void;
|
|
}
|
|
|
|
let { itemId, itemName, scope, size = "lg", onCleared }: Props = $props();
|
|
|
|
let busy = $state(false);
|
|
|
|
const label = $derived(scope === "series" ? "Clear history" : "Clear season history");
|
|
|
|
async function handleClick() {
|
|
if (busy) return;
|
|
|
|
const subject = scope === "series" ? `all of “${itemName}”` : `“${itemName}”`;
|
|
// Destructive and not undoable — always ask, even though the server keeps
|
|
// no undo of its own.
|
|
if (
|
|
!confirm(
|
|
`Erase watch history for ${subject}?\n\n` +
|
|
"Every episode is marked unwatched and resume positions are cleared. " +
|
|
"This cannot be undone."
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
busy = true;
|
|
try {
|
|
await auth.getRepository().clearWatchHistory(itemId);
|
|
onCleared?.();
|
|
} catch (e) {
|
|
log.error("Failed to clear watch history:", e);
|
|
alert(
|
|
`Could not clear watch history: ${e instanceof Error ? e.message : String(e)}`
|
|
);
|
|
} finally {
|
|
busy = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<button
|
|
onclick={handleClick}
|
|
disabled={busy || !$isServerReachable}
|
|
title={$isServerReachable
|
|
? "Mark everything unwatched and clear resume positions"
|
|
: "Needs a connection to the server"}
|
|
class="rounded-lg font-medium flex items-center gap-2 transition-colors
|
|
bg-[var(--color-surface)] hover:bg-[var(--color-surface-hover)]
|
|
disabled:opacity-40 disabled:cursor-not-allowed
|
|
{size === 'lg' ? 'px-6 py-2' : 'px-3 py-1.5 text-sm'}"
|
|
>
|
|
{#if busy}
|
|
<div
|
|
class="border-2 border-current border-t-transparent rounded-full animate-spin
|
|
{size === 'lg' ? 'w-5 h-5' : 'w-4 h-4'}"
|
|
></div>
|
|
{:else}
|
|
<svg
|
|
class={size === "lg" ? "w-5 h-5" : "w-4 h-4"}
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
d="M13 3a9 9 0 0 0-9 9H1l3.89 3.89.07.14L9 12H6a7 7 0 1 1 7 7c-1.93
|
|
0-3.68-.79-4.94-2.06l-1.42 1.42A8.95 8.95 0 0 0 13 21a9 9 0 0 0
|
|
0-18zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"
|
|
/>
|
|
</svg>
|
|
{/if}
|
|
{busy ? "Clearing…" : label}
|
|
</button>
|