Files
minstrel/web/src/lib/components/FollowControl.svelte
T
bvandeusenandClaude Opus 5.5 10d50dccbd docs: web comments say what the app does, not how it came to be (#6277)
Rewrites the web app's comments to rule 32. Task, milestone, rule and
spec ids are gone; regression stories are restated as the trap they
guard (public auth routes, the menu's stopPropagation, universal loads
under adapter-static, metric deltas with their margin). Flutter and
Tauri mentions now name the Android app or a native shell, operator
quotes and dates are dropped, and one library's counts no longer
appear outside test fixtures. Verified with a TypeScript-scanner token
comparison (template literals and regex literals re-scanned): only
comment text changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:42:12 -04:00

156 lines
4.9 KiB
Svelte

<script lang="ts">
import { BellPlus, BellRing } from 'lucide-svelte';
import { useQueryClient } from '@tanstack/svelte-query';
import {
createFollowQuery,
createReleaseProfilesQuery,
putFollow,
deleteFollow
} from '#lib/api/wanted.js';
import { qk } from '#lib/api/queries.js';
import { errMessage } from '#lib/api/errors.js';
import { pushToast } from '#lib/stores/toast.svelte.js';
import Modal from './Modal.svelte';
// Follow an artist for the wanted list: their new releases are
// wanted, filtered by a release-type profile. Admin-only; the artist page
// shows it when the artist carries a MusicBrainz id.
let { artistMbid, artistName }: { artistMbid: string; artistName: string } = $props();
const client = useQueryClient();
const followStore = $derived(createFollowQuery(artistMbid));
const follow = $derived($followStore);
const profilesStore = createReleaseProfilesQuery();
const profiles = $derived($profilesStore.data?.profiles ?? []);
const following = $derived(follow.data?.following === true);
let open = $state(false);
let profileChoice = $state('');
let wantExisting = $state(false);
let saving = $state(false);
function openModal() {
profileChoice = follow.data?.profile_id ?? profiles.find((p) => p.is_default)?.id ?? '';
wantExisting = false;
open = true;
}
async function refresh() {
await Promise.all([
client.invalidateQueries({ queryKey: qk.adminFollow(artistMbid) }),
client.invalidateQueries({ queryKey: ['adminFollows'] }),
client.invalidateQueries({ queryKey: ['adminWants'] })
]);
}
async function save() {
saving = true;
try {
await putFollow(artistMbid, {
artist_name: artistName,
profile_id: profileChoice || undefined,
want_existing: wantExisting
});
open = false;
pushToast(
wantExisting
? `Following ${artistName}. What they've released that isn't here is wanted on the next check.`
: `Following ${artistName}. New releases will be wanted.`
);
await refresh();
} catch (e) {
pushToast(errMessage(e), 'error');
} finally {
saving = false;
}
}
async function unfollow() {
saving = true;
try {
await deleteFollow(artistMbid);
open = false;
pushToast(`No longer following ${artistName}. What was already wanted stays wanted.`);
await refresh();
} catch (e) {
pushToast(errMessage(e), 'error');
} finally {
saving = false;
}
}
</script>
<button
type="button"
onclick={openModal}
disabled={follow.isPending}
class="inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary disabled:opacity-50"
>
{#if following}
<BellRing size={14} strokeWidth={1.5} />
Following · {follow.data?.profile_name}
{:else}
<BellPlus size={14} strokeWidth={1.5} />
Follow
{/if}
</button>
<Modal title={following ? `Following ${artistName}` : `Follow ${artistName}`} {open} onClose={() => (open = false)}>
<p class="-mt-2 text-sm text-text-secondary">
New releases from {artistName} go on the wanted list. The profile says which kinds of
release are wanted.
</p>
<label class="mt-4 block">
<span class="block text-sm text-text-secondary">Release-type profile</span>
<select
bind:value={profileChoice}
class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary focus:outline-hidden focus:ring-2 focus:ring-accent"
>
{#each profiles as p (p.id)}
<option value={p.id}>{p.name}{p.is_default ? ' (default)' : ''}</option>
{/each}
</select>
</label>
<label class="mt-3 flex items-start gap-3">
<input type="checkbox" bind:checked={wantExisting} class="mt-1" />
<span>
<span class="text-sm text-text-primary">Also want what they've released that isn't here</span>
<span class="block text-xs text-text-secondary">
Off wants only releases from now on.
</span>
</span>
</label>
<div class="mt-5 flex justify-end gap-2">
{#if following}
<button
type="button"
onclick={unfollow}
disabled={saving}
class="mr-auto rounded-md px-3 py-1.5 text-sm text-text-muted hover:bg-surface-hover hover:text-text-primary disabled:opacity-50"
>
Unfollow
</button>
{/if}
<button
type="button"
onclick={() => (open = false)}
class="rounded-md border border-border px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary"
>
Cancel
</button>
<button
type="button"
onclick={save}
disabled={saving || !profileChoice}
class="rounded-md bg-action-primary px-3 py-1.5 text-sm text-action-fg disabled:opacity-50"
>
{following ? 'Save' : 'Follow'}
</button>
</div>
</Modal>