Files
SoulStream/src/components/SetlistScanChip.svelte
T

186 lines
5.3 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
// The listen's presence while the import modal is closed — the import
// toast's dark glass pill, one berth higher so both can show at once.
// Running: live stage + window count + a cancel. Done: click to open
// the review. Error: what happened + dismiss.
import { goToView } from "../lib/state.svelte.js";
import { setlistScan, dismissScan } from "../lib/setlistScan.svelte.js";
const label = $derived.by(() => {
if (setlistScan.error) return setlistScan.error;
if (setlistScan.draft) {
const n = setlistScan.draft.entries?.length ?? 0;
return `${setlistScan.label} — ${n} row${n === 1 ? "" : "s"}`;
}
switch (setlistScan.stage) {
case "tool":
return "Fetching yt-dlp…";
case "runtime":
return "Fetching deno…";
case "audio":
return `Fetching the set's audio…${
setlistScan.pct != null ? ` ${Math.round(setlistScan.pct)}%` : ""
}`;
case "decode":
return "Decoding the set…";
case "listen":
return `Listening… ${setlistScan.window} of ${setlistScan.total || "?"}`;
default:
return "Listening…";
}
});
function review() {
setlistScan.wantsReview = true;
goToView("playlists");
}
</script>
{#if setlistScan.active}
<div class="layer">
<div class="toast" class:err={!!setlistScan.error}>
{#if setlistScan.draft}
<button class="body" onclick={review} title="Open the review">
<span class="mark ok">✓</span>
<span>{label} — <b>review</b></span>
</button>
<button
class="x"
onclick={dismissScan}
title="Dismiss — the hearing is remembered, a re-listen is instant"
>×</button>
{:else if setlistScan.error}
<span class="body">
<span class="mark warn">!</span>
<span>{label}</span>
</span>
<button class="x" onclick={dismissScan} title="Dismiss">×</button>
{:else}
<span class="body">
<span class="mark pulse">▸</span>
<span>{label}</span>
{#if setlistScan.stage === "listen" && setlistScan.hit}
<span class="hit">{setlistScan.hit}</span>
{/if}
</span>
<button class="x" onclick={dismissScan} title="Cancel the listen">×</button>
{/if}
</div>
</div>
{/if}
<style>
/* The import toast's berth, one pill higher, so both can show at once. */
.layer {
position: fixed;
left: 0;
right: 0;
bottom: calc(var(--player-height, 62px) + 8px + 26px + 44px);
display: flex;
justify-content: center;
z-index: 80;
pointer-events: none;
}
.toast {
pointer-events: auto;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 8px 5px 16px;
border-radius: 99px;
background: rgba(22, 23, 28, 0.94);
border: 1px solid rgba(255, 255, 255, 0.12);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
font-size: 12.5px;
font-weight: 700;
color: #fff;
animation: chip-in 340ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.toast.err {
border-color: rgba(240, 205, 119, 0.4);
}
.body {
display: inline-flex;
align-items: center;
gap: 10px;
background: none;
border: 0;
padding: 4px 8px 4px 0;
color: inherit;
font: inherit;
cursor: default;
max-width: 520px;
}
button.body {
cursor: pointer;
border-radius: 99px;
}
button.body:hover {
color: var(--accent);
}
.hit {
font-weight: 600;
color: rgba(255, 255, 255, 0.6);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 240px;
}
.x {
background: none;
border: 0;
color: rgba(255, 255, 255, 0.55);
font: inherit;
font-size: 15px;
line-height: 1;
padding: 4px 8px;
border-radius: 99px;
cursor: pointer;
}
.x:hover {
color: #fff;
}
.mark {
flex: none;
font-style: normal;
}
.mark.pulse {
color: var(--accent);
animation: chip-pulse 1s ease-in-out infinite;
}
@keyframes chip-pulse {
50% {
opacity: 0.35;
}
}
.mark.ok {
color: var(--accent);
}
.mark.warn {
color: #f0cd77;
}
b {
font-weight: 800;
}
@keyframes chip-in {
from {
opacity: 0;
transform: translateY(16px) scale(0.95);
}
to {
opacity: 1;
transform: none;
}
}
@media (prefers-reduced-motion: reduce) {
.toast {
animation-duration: 1ms;
}
.mark.pulse {
animation: none;
}
}
</style>