Lokales Tag-Handling implementiert
This commit is contained in:
@@ -1,71 +1,225 @@
|
||||
<script lang="ts">
|
||||
import type { BuzzerFile } from "../lib/types";
|
||||
import { FileAudioIcon } from "phosphor-svelte";
|
||||
import { isFetchingRemote, transferStats, transferDetails, buzzerAudioFiles } from "../lib/store";
|
||||
import {
|
||||
MusicNotesIcon,
|
||||
DotsThreeVerticalIcon,
|
||||
PlayIcon,
|
||||
TrashIcon,
|
||||
InfoIcon,
|
||||
CircleIcon,
|
||||
QuestionIcon,
|
||||
UserCircleCheckIcon,
|
||||
WarningCircleIcon,
|
||||
PersonIcon,
|
||||
TagIcon,
|
||||
UserIcon,
|
||||
} from "phosphor-svelte";
|
||||
import {
|
||||
isFetchingRemote,
|
||||
transferStats,
|
||||
transferDetails,
|
||||
buzzerAudioFiles,
|
||||
localAudioFiles,
|
||||
} from "../lib/store";
|
||||
|
||||
import { SETTINGS } from "../lib/settings";
|
||||
import { tagEditorState } from "../lib/store";
|
||||
import { tooltip } from "../lib/actions/tooltip";
|
||||
|
||||
export let file: BuzzerFile;
|
||||
export let type: "local" | "buzzer" = "buzzer";
|
||||
let menuOpen = false;
|
||||
|
||||
// Status-Berechnung für die Queue
|
||||
$: selectedFiles = $buzzerAudioFiles.filter(f => f.selected);
|
||||
$: currentIndex = selectedFiles.findIndex(f => f.name === $transferStats.currentFileName);
|
||||
$: myIndex = selectedFiles.findIndex(f => f.name === file.name);
|
||||
$: activeStore = type === "local" ? localAudioFiles : buzzerAudioFiles;
|
||||
$: selectedFiles = $activeStore.filter((f) => f.selected);
|
||||
$: currentIndex = selectedFiles.findIndex((f) => f.name === $transferStats.currentFileName);
|
||||
$: myIndex = selectedFiles.findIndex((f) => f.name === file.name);
|
||||
|
||||
$: state = (() => {
|
||||
if (!file.selected || !$isFetchingRemote) return 'default';
|
||||
if (file.name === $transferStats.currentFileName) return 'active';
|
||||
if (myIndex < currentIndex) return 'done';
|
||||
if (myIndex > currentIndex) return 'pending';
|
||||
return 'default';
|
||||
if (!file.selected || !$isFetchingRemote) return "default";
|
||||
if (file.name === $transferStats.currentFileName) return "active";
|
||||
if (myIndex < currentIndex) return "done";
|
||||
if (myIndex > currentIndex) return "pending";
|
||||
return "default";
|
||||
})();
|
||||
|
||||
function toggleSelection() {
|
||||
if ($isFetchingRemote) return; // Blockiert Änderungen während des Transfers
|
||||
file.selected = !file.selected;
|
||||
buzzerAudioFiles.update(files => files); // Triggert die Reaktivität im Svelte-Store
|
||||
if ($isFetchingRemote) return;
|
||||
|
||||
if (type === "buzzer") {
|
||||
buzzerAudioFiles.update((files) =>
|
||||
files.map((entry) =>
|
||||
entry.name === file.name ? { ...entry, selected: !entry.selected } : entry,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
localAudioFiles.update((files) =>
|
||||
files.map((entry) =>
|
||||
entry.name === file.name ? { ...entry, selected: !entry.selected } : entry,
|
||||
),
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative overflow-hidden">
|
||||
|
||||
{#if state === 'active'}
|
||||
<div
|
||||
<svelte:window on:click={() => (menuOpen = false)} />
|
||||
|
||||
<div class="relative overflow-hidden group/item">
|
||||
{#if state === "active"}
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full bg-indigo-100 z-0"
|
||||
style="width: {$transferDetails.filePercent}%; transition: {$transferDetails.filePercent === 0 ? 'none' : `width ${SETTINGS.ui.transferUpdateIntervalMs}ms linear`};"
|
||||
style="width: {$transferDetails.filePercent}%; transition: {$transferDetails.filePercent === 0
|
||||
? 'none'
|
||||
: `width ${SETTINGS.ui.transferUpdateIntervalMs}ms linear`};"
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class="relative z-10 w-full text-left flex-1 px-3 py-1 flex items-center border-l-4 transition-colors border-b border-b-border-card
|
||||
{file.selected ? 'border-l-blue-600' : 'border-l-transparent'}
|
||||
{file.selected && state !== 'active' ? 'bg-blue-50' : ''}
|
||||
{!$isFetchingRemote && file.selected ? 'hover:bg-blue-100 cursor-pointer' : ''}
|
||||
{!$isFetchingRemote && !file.selected ? 'hover:bg-slate-100 hover:border-l-blue-200 cursor-pointer' : ''}
|
||||
{$isFetchingRemote ? 'cursor-default' : ''}
|
||||
{state === 'pending' ? 'grayscale opacity-80' : ''}"
|
||||
class="relative z-10 w-full text-left flex-1 px-3 py-1 pr-16 flex items-center border-l-4 transition-colors border-b border-b-border-card
|
||||
{file.selected ? 'border-l-blue-600' : 'border-l-transparent'}
|
||||
{file.selected && state !== 'active' ? 'bg-blue-50' : ''}
|
||||
{!$isFetchingRemote && file.selected ? 'hover:bg-blue-100 cursor-pointer' : ''}
|
||||
{!$isFetchingRemote && !file.selected
|
||||
? 'hover:bg-slate-100 hover:border-l-blue-200 cursor-pointer'
|
||||
: ''}
|
||||
{$isFetchingRemote ? 'cursor-default' : ''}
|
||||
{state === 'pending' ? 'grayscale opacity-80' : ''}"
|
||||
on:click={toggleSelection}
|
||||
disabled={$isFetchingRemote}
|
||||
>
|
||||
<FileAudioIcon class="text-blue-600 mr-3 w-5 h-5" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-light">
|
||||
{file.name || "Unbekannte Datei"}
|
||||
{#if file.metaTags?.t}
|
||||
 - 
|
||||
<span class="font-medium">{file.metaTags.t}</span>
|
||||
<MusicNotesIcon weight="fill" class="mr-3 w-5 h-5 shrink-0" />
|
||||
|
||||
<div class="flex flex-col flex-1 min-w-0 overflow-hidden">
|
||||
<div class="flex items-center min-w-0">
|
||||
<span class="font-light truncate min-w-0 text-sm">
|
||||
{file.name || "Unbekannte Datei"}
|
||||
{#if file.metaTags?.t}
|
||||
 - 
|
||||
<span class="font-normal">{file.metaTags.t}</span>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-0 text-xs text-text-muted mt-0.5 min-w-0">
|
||||
{#if file.sysTags?.crc32}
|
||||
<span
|
||||
use:tooltip={{
|
||||
text:
|
||||
"crc32: <span class='font-mono'>0x" +
|
||||
file.sysTags.crc32.toString(16).toUpperCase() +
|
||||
"</span>",
|
||||
pos: "right",
|
||||
}}
|
||||
>
|
||||
<CircleIcon weight="fill" class="mr-1 shrink-0 text-emerald-600 w-3.5 h-3.5" />
|
||||
</span>
|
||||
{:else}
|
||||
<span
|
||||
use:tooltip={{
|
||||
text: "Keine Prüfsumme in den Tags verfügbar. Bitte aktualisiere über das Menü die CRC32-Tags.",
|
||||
pos: "right",
|
||||
variant: "warning",
|
||||
}}
|
||||
>
|
||||
<QuestionIcon weight="fill" class="mr-1 shrink-0 text-amber-500 w-3.5 h-3.5" />
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
<div class="text-xs">
|
||||
<span class="font-light text-text-muted text-xs">
|
||||
{parseFloat((file.size/1024).toFixed(1))} kB
|
||||
</span>
|
||||
<span>
|
||||
{#if file.metaTags?.a}<span class="text-text-muted"> | Author:</span> {file.metaTags.a}{/if}
|
||||
|
||||
<span class="font-light shrink-0">
|
||||
{parseFloat((file.size / 1024).toFixed(1))} kB
|
||||
</span>
|
||||
{#if file.metaTags?.a}
|
||||
<UserIcon weight="fill" class="ml-1 pl-1 mr-0.5 shrink-0 text-slate-500 w-4.5 h-3.5 border-l border-l-text-muted" />
|
||||
<span class="truncate min-w-0">{file.metaTags.a}</span>
|
||||
{/if}
|
||||
{#if file.metaTags?.c}
|
||||
<TagIcon weight="fill" class="ml-1 pl-1 mr-0.5 shrink-0 text-slate-500 w-4.5 h-3.5 border-l border-l-text-muted" />
|
||||
<span class="truncate min-w-0">{file.metaTags.c}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div class="menu-btn-grp group/menu" class:is-open={menuOpen}>
|
||||
<div
|
||||
class="flex items-center overflow-hidden transition-all duration-300 ease-in-out
|
||||
{menuOpen
|
||||
? 'max-w-[120px] opacity-100'
|
||||
: 'max-w-0 opacity-0 group-hover/menu:max-w-[120px] group-hover/menu:opacity-100'}"
|
||||
>
|
||||
<button
|
||||
class="menu-btn danger"
|
||||
title="Löschen"
|
||||
on:click|stopPropagation={() => {
|
||||
console.log("Delete", file.name);
|
||||
menuOpen = false;
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
class="menu-btn"
|
||||
title="Abspielen"
|
||||
on:click|stopPropagation={() => {
|
||||
console.log("Play", file.name);
|
||||
menuOpen = false;
|
||||
}}
|
||||
>
|
||||
<PlayIcon class="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
class="menu-btn"
|
||||
title="Datei-Info"
|
||||
on:click|stopPropagation={() => {
|
||||
console.log("Info", file.name);
|
||||
tagEditorState.set({ show: true, type, fileName: file.name });
|
||||
menuOpen = false;
|
||||
}}
|
||||
>
|
||||
<InfoIcon class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="menu-btn !border-r-transparent"
|
||||
on:click|stopPropagation={() => (menuOpen = !menuOpen)}
|
||||
>
|
||||
<DotsThreeVerticalIcon class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@reference "../styles/app.css";
|
||||
|
||||
.menu-btn-grp {
|
||||
@apply absolute right-2 top-1/2 -translate-y-1/2 z-20 overflow-hidden
|
||||
p-0 flex items-center backdrop-blur-sm
|
||||
border border-transparent rounded-full transition-all;
|
||||
}
|
||||
|
||||
/* Kombiniert Hover von Mausnutzern und aktiven Touch-Zustand */
|
||||
.menu-btn-grp:hover,
|
||||
.menu-btn-grp.is-open {
|
||||
@apply border-border-card bg-white shadow-sm;
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
@apply p-1.5 border-r border-r-border-card
|
||||
flex items-center justify-center shrink-0 transition-colors;
|
||||
|
||||
&:not(:disabled):not(.danger) {
|
||||
@apply hover:bg-surface-hover;
|
||||
}
|
||||
|
||||
&.danger:not(:disabled) {
|
||||
@apply text-red-700 hover:bg-red-100;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: color-mix(in srgb, currentColor 50%, transparent);
|
||||
@apply cursor-not-allowed grayscale;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user