zwischenstand
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
onMount(async () => {
|
||||
performHardwareCheck();
|
||||
|
||||
|
||||
if ($isBluetoothSupported) {
|
||||
const { restoreSession } = await import("../lib/bluetooth");
|
||||
await restoreSession();
|
||||
@@ -22,9 +22,11 @@
|
||||
</div>
|
||||
{:else if !$isBluetoothSupported}
|
||||
<div
|
||||
class="fixed lg:h-screen inset-0 flex flex-col items-center justify-center p-0 lg:p-4 z-[100] bg-white lg:bg-transparent" style="hyphens:auto;">
|
||||
class="fixed lg:h-screen inset-0 flex flex-col items-center justify-center p-0 lg:p-4 z-[100] bg-white lg:bg-transparent"
|
||||
style="hyphens:auto;"
|
||||
>
|
||||
<div
|
||||
class="w-full h-full lg:h-auto lg:max-w-md bg-red-50 lg:border border-red-600 lg:shadow-xl lg:rounded-lg p-6 lg:p-8 text-red-600 flex flex-col justify-center"
|
||||
class="w-full h-full lg:h-auto lg:max-w-md bg-red-50 shadow-red-500/30 lg:border border-red-600 lg:shadow-xl lg:rounded-lg p-6 lg:p-8 text-red-600 flex flex-col justify-center"
|
||||
>
|
||||
<h1 class="text-2xl font-bold mb-2 text-center">Dein Browser ist... suboptimal</h1>
|
||||
<div class="text-center text-7xl md:text-9xl font-bold mb-4">🥺</div>
|
||||
@@ -34,10 +36,12 @@
|
||||
Leider unterstützt dein Browser die benötigten Bluetooth-Funktionen nicht. Bitte versuche
|
||||
es mit einem aktuellen <span class="font-semibold">Chrome</span>
|
||||
oder einem andern Chromium-basierten Browser.
|
||||
<span class="font-semibold">Winzigweich Kante</span> soll gerüchteweise auch Chromium-basiert sein...
|
||||
<span class="font-semibold">Winzigweich Kante</span>
|
||||
soll gerüchteweise auch Chromium-basiert sein...
|
||||
</p>
|
||||
<p>
|
||||
Rundreise auf iOS unterstützt Bluetooth leider nicht, aber du kannst es mit einem vernünftigen Gerät oder Browser versuchen.
|
||||
Rundreise auf iOS unterstützt Bluetooth leider nicht, aber du kannst es mit einem
|
||||
vernünftigen Gerät oder Browser versuchen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,17 +15,22 @@
|
||||
WarningCircleIcon,
|
||||
} from "phosphor-svelte";
|
||||
import {
|
||||
isFetchingRemote,
|
||||
isTransferingRemote,
|
||||
transferStats,
|
||||
transferDetails,
|
||||
buzzerAudioFiles,
|
||||
localAudioFiles,
|
||||
syncStateMap,
|
||||
fsInfo,
|
||||
} from "../lib/store";
|
||||
|
||||
import { SETTINGS } from "../lib/settings";
|
||||
import { tagEditorState } from "../lib/store";
|
||||
import { tooltip } from "../lib/actions/tooltip";
|
||||
import { deleteRemoteFile } from "../lib/transport";
|
||||
import { deleteLocalFile } from "../lib/db";
|
||||
import { refreshRemote, refreshLocal } from "../lib/sync";
|
||||
import { addToast } from "../lib/toast";
|
||||
|
||||
export let file: BuzzerFile;
|
||||
export let type: "local" | "buzzer" = "buzzer";
|
||||
@@ -37,7 +42,7 @@
|
||||
$: myIndex = selectedFiles.findIndex((f) => f.name === file.name);
|
||||
|
||||
$: state = (() => {
|
||||
if (!file.selected || !$isFetchingRemote) return "default";
|
||||
if (!file.selected || !$isTransferingRemote) return "default";
|
||||
if (file.name === $transferStats.currentFileName) return "active";
|
||||
if (myIndex < currentIndex) return "done";
|
||||
if (myIndex > currentIndex) return "pending";
|
||||
@@ -93,7 +98,7 @@
|
||||
})();
|
||||
|
||||
function toggleSelection() {
|
||||
if ($isFetchingRemote) return;
|
||||
if ($isTransferingRemote) return;
|
||||
|
||||
if (type === "buzzer") {
|
||||
buzzerAudioFiles.update((files) =>
|
||||
@@ -110,6 +115,35 @@
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async function handleDeleteClick() {
|
||||
if (!confirm(`Möchten Sie die Datei "${file.name}" wirklich löschen?`)) {
|
||||
menuOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "buzzer") {
|
||||
try {
|
||||
const basePath = $fsInfo?.audioPath || "/lfs/a";
|
||||
const fullPath = `${basePath}/${file.name}`;
|
||||
await deleteRemoteFile(fullPath);
|
||||
addToast(`Datei ${file.name} erfolgreich vom Buzzer gelöscht.`, "success");
|
||||
await refreshRemote();
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Löschen:", error);
|
||||
addToast("Fehler beim Löschen der Datei auf dem Buzzer.", "error");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await deleteLocalFile(file.name);
|
||||
addToast(`Lokale Datei ${file.name} gelöscht.`, "success");
|
||||
await refreshLocal();
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Löschen:", error);
|
||||
}
|
||||
}
|
||||
menuOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:click={() => (menuOpen = false)} />
|
||||
@@ -128,14 +162,14 @@
|
||||
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
|
||||
{!$isTransferingRemote && file.selected ? 'hover:bg-blue-100 cursor-pointer' : ''}
|
||||
{!$isTransferingRemote && !file.selected
|
||||
? 'hover:bg-slate-100 hover:border-l-blue-200 cursor-pointer'
|
||||
: ''}
|
||||
{$isFetchingRemote ? 'cursor-default' : ''}
|
||||
{$isTransferingRemote ? 'cursor-default' : ''}
|
||||
{state === 'pending' ? 'grayscale opacity-80' : ''}"
|
||||
on:click={toggleSelection}
|
||||
disabled={$isFetchingRemote}
|
||||
disabled={$isTransferingRemote}
|
||||
>
|
||||
<MusicNotesIcon weight="fill" class="mr-3 w-5 h-5 shrink-0" />
|
||||
|
||||
@@ -198,10 +232,7 @@
|
||||
<button
|
||||
class="menu-btn danger"
|
||||
title="Löschen"
|
||||
on:click|stopPropagation={() => {
|
||||
console.log("Delete", file.name);
|
||||
menuOpen = false;
|
||||
}}
|
||||
on:click|stopPropagation={handleDeleteClick}
|
||||
>
|
||||
<TrashIcon class="list-menu-icon" />
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { fade, slide } from "svelte/transition";
|
||||
import { localAudioFiles, buzzerAudioFiles } from "../lib/store";
|
||||
import { deleteSelectedLocalFiles } from "../lib/sync";
|
||||
import { deleteSelectedLocalFiles, deleteSelectedRemoteFiles } from "../lib/sync";
|
||||
import { addToast } from "../lib/toast";
|
||||
import { tooltip } from "../lib/actions/tooltip";
|
||||
import { updateLocalAudioCrc } from "../lib/tagHandler";
|
||||
@@ -143,12 +143,11 @@
|
||||
class="menu-btn danger"
|
||||
disabled={selectedFileCount === 0}
|
||||
on:click={() => {
|
||||
if (type === "buzzer")
|
||||
addToast(
|
||||
"Löschen von Dateien auf dem Buzzer wird derzeit nicht unterstützt.",
|
||||
"error",
|
||||
);
|
||||
else deleteSelectedLocalFiles();
|
||||
if (type === "buzzer") {
|
||||
deleteSelectedRemoteFiles();
|
||||
} else {
|
||||
deleteSelectedLocalFiles();
|
||||
}
|
||||
closeMenu();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
import { updateFile } from "../lib/tagHandler";
|
||||
import { refreshLocal, refreshRemote } from "../lib/sync";
|
||||
import { fade, slide } from "svelte/transition";
|
||||
import { localAudioFiles, buzzerAudioFiles, fsInfo } from "../lib/store";
|
||||
import type { MetadataTags } from "../lib/types";
|
||||
import {
|
||||
localAudioFiles,
|
||||
buzzerAudioFiles,
|
||||
fsInfo,
|
||||
syncStateMap,
|
||||
} from "../lib/store";
|
||||
import { type MetadataTags, SyncState } from "../lib/types";
|
||||
import {
|
||||
XIcon,
|
||||
CaretLeftIcon,
|
||||
@@ -15,6 +20,7 @@
|
||||
PencilIcon,
|
||||
} from "phosphor-svelte";
|
||||
import { addToast } from "../lib/toast";
|
||||
import { tooltip } from "../lib/actions/tooltip";
|
||||
|
||||
export let show = false;
|
||||
export let type: "local" | "buzzer" = "buzzer";
|
||||
@@ -29,7 +35,7 @@
|
||||
|
||||
$: autoApplyIcon = applyToBoth ? CheckSquareIcon : SquareIcon;
|
||||
$: activeStore = type === "local" ? localAudioFiles : buzzerAudioFiles;
|
||||
$: fileList = $activeStore;
|
||||
$: fileList = $activeStore || [];
|
||||
|
||||
$: if (show && initialFileName !== lastOpenedName) {
|
||||
if (initialFileName) {
|
||||
@@ -44,7 +50,7 @@
|
||||
lastOpenedName = null;
|
||||
}
|
||||
|
||||
$: currentIndex = fileList.findIndex((f) => f.name === currentFileName);
|
||||
$: currentIndex = (fileList || []).findIndex((f) => f.name === currentFileName);
|
||||
$: currentFile = fileList[currentIndex];
|
||||
$: hasDraft = currentFile ? drafts[currentFile.name] !== undefined : false;
|
||||
$: hasAnyDrafts = Object.keys(drafts).length > 0;
|
||||
@@ -53,6 +59,13 @@
|
||||
$: activeTags = activeDraft ? activeDraft.tags : currentFile?.metaTags || {};
|
||||
$: activeName = activeDraft ? activeDraft.newName : currentFile?.name || "";
|
||||
|
||||
$: syncStatus = (currentFileName && $syncStateMap[type]?.[currentFileName]) || { state: SyncState.UNKNOWN, linkedFiles: [] };
|
||||
$: isDuplicate = syncStatus.state === SyncState.DUPLICATE;
|
||||
|
||||
$: if (isDuplicate) {
|
||||
applyToBoth = false;
|
||||
}
|
||||
|
||||
$: maxFilenameLength = $fsInfo ? $fsInfo.maxPathLength - $fsInfo.audioPath.length - 2 : 30;
|
||||
|
||||
function closeEditor() {
|
||||
@@ -120,13 +133,21 @@
|
||||
const newName = draft.newName;
|
||||
|
||||
try {
|
||||
await updateFile(oldName, newName, currentFile.sysTags, draft.tags, type);
|
||||
await updateFile(oldName, newName, currentFile.sysTags, draft.tags, type, applyToBoth);
|
||||
addToast(`Datei ${newName} gespeichert.`, "success");
|
||||
delete drafts[oldName];
|
||||
drafts = drafts;
|
||||
if (oldName !== newName) currentFileName = newName;
|
||||
if (type === "local") await refreshLocal();
|
||||
if (type === "buzzer") await refreshRemote();
|
||||
|
||||
if (applyToBoth) {
|
||||
// Wenn auf beide angewendet, beide Seiten neu laden
|
||||
await refreshLocal();
|
||||
await refreshRemote();
|
||||
} else {
|
||||
// Ansonsten nur die aktive Seite
|
||||
if (type === "local") await refreshLocal();
|
||||
if (type === "buzzer") await refreshRemote();
|
||||
}
|
||||
} catch (error) {
|
||||
addToast("Fehler beim Speichern.", "error");
|
||||
}
|
||||
@@ -138,14 +159,20 @@
|
||||
for (const [oldName, draft] of Object.entries(drafts)) {
|
||||
const file = fileList.find((f) => f.name === oldName);
|
||||
if (file) {
|
||||
await updateFile(oldName, draft.newName, file.sysTags, draft.tags, type);
|
||||
await updateFile(oldName, draft.newName, file.sysTags, draft.tags, type, applyToBoth);
|
||||
savedCount++;
|
||||
}
|
||||
}
|
||||
drafts = {};
|
||||
addToast(`${savedCount} Dateien gespeichert.`, "success");
|
||||
if (type === "local") await refreshLocal();
|
||||
if (type === "buzzer") await refreshRemote();
|
||||
|
||||
if (applyToBoth) {
|
||||
await refreshLocal();
|
||||
await refreshRemote();
|
||||
} else {
|
||||
if (type === "local") await refreshLocal();
|
||||
if (type === "buzzer") await refreshRemote();
|
||||
}
|
||||
} catch (error) {
|
||||
addToast("Fehler beim Speichern.", "error");
|
||||
}
|
||||
@@ -398,15 +425,27 @@
|
||||
<FloppyDiskIcon class="btn-icon" /> Speichern
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="menu-btn bg-slate-50 hover:bg-slate-100 !justify-start text-slate-700"
|
||||
on:click={() => (applyToBoth = !applyToBoth)}
|
||||
<div
|
||||
use:tooltip={{
|
||||
text: "Diese Option ist deaktiviert, da ein Duplikat-Konflikt vorliegt. Lösen Sie den Konflikt, um Änderungen auf beiden Seiten anwenden zu können.",
|
||||
pos: "top",
|
||||
variant: "danger",
|
||||
disabled: !isDuplicate,
|
||||
}}
|
||||
class:grayscale={isDuplicate}
|
||||
class:cursor-not-allowed={isDuplicate}
|
||||
>
|
||||
<svelte:component
|
||||
this={autoApplyIcon}
|
||||
class="btn-icon {applyToBoth ? 'text-blue-600' : 'text-slate-400'}"
|
||||
/> Auch {type === "buzzer" ? "lokal" : "auf dem Buzzer"} anwenden
|
||||
</button>
|
||||
<button
|
||||
class="menu-btn bg-slate-50 hover:bg-slate-100 !justify-start text-slate-700 w-full"
|
||||
on:click={() => (applyToBoth = !applyToBoth)}
|
||||
disabled={isDuplicate}
|
||||
>
|
||||
<svelte:component
|
||||
this={autoApplyIcon}
|
||||
class="btn-icon {applyToBoth ? 'text-blue-600' : 'text-slate-400'}"
|
||||
/> Auch {type === "buzzer" ? "lokal" : "auf dem Buzzer"} anwenden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import FileList from "./FileList.svelte";
|
||||
import DeviceInfo from "./DeviceInfo.svelte";
|
||||
import { refreshRemote } from "../lib/sync";
|
||||
import { transferStats, isFetchingRemote, pairedDevices, activeDeviceId } from "../lib/store";
|
||||
import { transferStats, isTransferingRemote, pairedDevices, activeDeviceId } from "../lib/store";
|
||||
import { SETTINGS } from "../lib/settings";
|
||||
import TransferProgress from "./TransferProgress.svelte";
|
||||
import FileMenuOverlay from "./FileMenuOverlay.svelte";
|
||||
@@ -25,12 +25,12 @@
|
||||
refreshRemote();
|
||||
}
|
||||
|
||||
$: if ($isFetchingRemote && $transferStats.overallTotal > 0) {
|
||||
$: if ($isTransferingRemote && $transferStats.overallTotal > 0) {
|
||||
// Transfer startet oder läuft
|
||||
showOverlay = true;
|
||||
isTransferFinished = false;
|
||||
clearTimeout(overlayTimeout);
|
||||
} else if (showOverlay && !$isFetchingRemote && $transferStats.overallDone > 0) {
|
||||
} else if (showOverlay && !$isTransferingRemote && $transferStats.overallDone > 0) {
|
||||
// Transfer wurde soeben abgeschlossen
|
||||
isTransferFinished = true;
|
||||
overlayTimeout = setTimeout(closeOverlay, SETTINGS.ui.transferOverlayPersistMs);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import { XIcon } from "phosphor-svelte";
|
||||
import { isFetchingRemote, transferStats, transferDetails } from '../lib/store';
|
||||
import { isTransferingRemote, transferStats, transferDetails } from '../lib/store';
|
||||
import { SETTINGS } from '../lib/settings';
|
||||
|
||||
let showOverlay = false;
|
||||
let isTransferFinished = false;
|
||||
let overlayTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
$: if ($isFetchingRemote && $transferStats.overallTotal > 0) {
|
||||
$: if ($isTransferingRemote && $transferStats.overallTotal > 0) {
|
||||
showOverlay = true;
|
||||
isTransferFinished = false;
|
||||
clearTimeout(overlayTimeout);
|
||||
} else if (showOverlay && !$isFetchingRemote && $transferStats.overallDone > 0) {
|
||||
} else if (showOverlay && !$isTransferingRemote && $transferStats.overallDone > 0) {
|
||||
isTransferFinished = true;
|
||||
overlayTimeout = setTimeout(closeOverlay, SETTINGS.ui.transferOverlayPersistMs);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user