103 lines
3.3 KiB
Svelte
103 lines
3.3 KiB
Svelte
<script lang="ts">
|
|
import { untrack } from 'svelte';
|
|
import { Flag } from 'lucide-svelte';
|
|
import { useQueryClient } from '@tanstack/svelte-query';
|
|
import { flagTrack } from '$lib/api/quarantine';
|
|
import { errCode } from '$lib/api/errors';
|
|
import { qk } from '$lib/api/queries';
|
|
import type { TrackRef, LidarrQuarantineReason } from '$lib/api/types';
|
|
|
|
let {
|
|
track,
|
|
onClose,
|
|
initialReason,
|
|
initialNotes
|
|
}: {
|
|
track: TrackRef;
|
|
onClose: () => void;
|
|
initialReason?: LidarrQuarantineReason;
|
|
initialNotes?: string;
|
|
} = $props();
|
|
|
|
let reason: LidarrQuarantineReason = $state(untrack(() => initialReason ?? 'bad_rip'));
|
|
let notes = $state(untrack(() => initialNotes ?? ''));
|
|
let submitting = $state(false);
|
|
let error = $state<string | null>(null);
|
|
|
|
const isUpdate = $derived(!!initialReason);
|
|
const client = useQueryClient();
|
|
|
|
async function submit() {
|
|
submitting = true;
|
|
error = null;
|
|
try {
|
|
await flagTrack({ track_id: track.id, reason, notes: notes.trim() || undefined });
|
|
await client.invalidateQueries({ queryKey: qk.myQuarantine() });
|
|
onClose();
|
|
} catch (e) {
|
|
const code = errCode(e);
|
|
error = code === 'unknown' ? 'flag_failed' : code;
|
|
} finally {
|
|
submitting = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div
|
|
role="dialog"
|
|
tabindex="-1"
|
|
aria-modal="false"
|
|
aria-labelledby="flag-popover-title"
|
|
class="absolute right-0 z-30 mt-1 w-72 rounded-lg border border-border bg-surface p-3 shadow-xl"
|
|
onclick={(e) => e.stopPropagation()}
|
|
onkeydown={(e) => { e.stopPropagation(); if (e.key === 'Escape') onClose(); }}
|
|
>
|
|
<h4 id="flag-popover-title" class="text-sm font-medium text-text-primary">
|
|
Flag this track as broken
|
|
</h4>
|
|
<label class="mt-2 block">
|
|
<span class="block text-xs text-text-secondary">Reason</span>
|
|
<select
|
|
bind:value={reason}
|
|
class="mt-1 w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent"
|
|
>
|
|
<option value="bad_rip">Bad rip</option>
|
|
<option value="wrong_file">Wrong file</option>
|
|
<option value="wrong_tags">Wrong tags</option>
|
|
<option value="duplicate">Duplicate</option>
|
|
<option value="other">Other</option>
|
|
</select>
|
|
</label>
|
|
<label class="mt-2 block">
|
|
<span class="block text-xs text-text-secondary">Notes (optional)</span>
|
|
<textarea
|
|
bind:value={notes}
|
|
maxlength="200"
|
|
placeholder="What's wrong with it?"
|
|
class="mt-1 w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
|
|
rows="2"
|
|
></textarea>
|
|
</label>
|
|
{#if error}
|
|
<p class="mt-2 text-xs text-error">Couldn't save flag — {error}</p>
|
|
{/if}
|
|
<div class="mt-3 flex justify-end gap-2">
|
|
<button
|
|
type="button"
|
|
class="rounded-md border border-border px-2.5 py-1 text-sm text-text-secondary hover:text-text-primary"
|
|
onclick={onClose}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onclick={submit}
|
|
disabled={submitting}
|
|
class="inline-flex items-center gap-1 rounded-md bg-action-secondary px-2.5 py-1 text-sm text-action-fg disabled:opacity-50"
|
|
>
|
|
<Flag size={14} strokeWidth={1} />
|
|
{isUpdate ? 'Update flag' : 'Flag'}
|
|
</button>
|
|
</div>
|
|
</div>
|