First working POC
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { volume, isMuted } from "$lib/stores/player";
|
||||
import { platform } from "@tauri-apps/plugin-os";
|
||||
|
||||
interface Props {
|
||||
size?: "sm" | "md" | "lg";
|
||||
}
|
||||
|
||||
let { size = "md" }: Props = $props();
|
||||
|
||||
// On Android, volume is controlled by system volume buttons (not a slider)
|
||||
const isAndroid = platform() === "android";
|
||||
|
||||
let showSlider = $state(false);
|
||||
let sliderValue = $state($volume);
|
||||
|
||||
// Sync slider with store value
|
||||
$effect(() => {
|
||||
sliderValue = $volume;
|
||||
});
|
||||
|
||||
async function handleVolumeChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const newVolume = parseFloat(target.value);
|
||||
sliderValue = newVolume;
|
||||
await invoke("player_set_volume", { volume: newVolume });
|
||||
}
|
||||
|
||||
async function toggleMute() {
|
||||
await invoke("player_toggle_mute");
|
||||
}
|
||||
|
||||
function toggleSlider() {
|
||||
showSlider = !showSlider;
|
||||
}
|
||||
|
||||
// Icon sizes based on prop (use $derived for reactivity)
|
||||
const iconSize = $derived(size === "sm" ? "w-4 h-4" : size === "md" ? "w-5 h-5" : "w-6 h-6");
|
||||
const buttonPadding = $derived(size === "sm" ? "p-1" : size === "md" ? "p-2" : "p-3");
|
||||
</script>
|
||||
|
||||
{#if !isAndroid}
|
||||
<div class="relative flex items-center gap-1">
|
||||
<!-- Volume Icon Button (click to toggle slider) -->
|
||||
<button
|
||||
onclick={toggleSlider}
|
||||
class="{buttonPadding} rounded-full hover:bg-white/10 transition-colors"
|
||||
title="Volume"
|
||||
>
|
||||
{#if $isMuted || sliderValue === 0}
|
||||
<!-- Muted Icon -->
|
||||
<svg class={iconSize} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"
|
||||
/>
|
||||
</svg>
|
||||
{:else if sliderValue < 0.33}
|
||||
<!-- Low Volume Icon -->
|
||||
<svg class={iconSize} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15.536 8.464a5 5 0 010 7.072m-9.95-9.193L4 8.929V5.071a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if sliderValue < 0.66}
|
||||
<!-- Medium Volume Icon -->
|
||||
<svg class={iconSize} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15.536 8.464a5 5 0 010 7.072M6.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h2.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L6.586 15z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<!-- High Volume Icon -->
|
||||
<svg class={iconSize} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Volume Slider (toggle on click) -->
|
||||
{#if showSlider}
|
||||
<div
|
||||
class="absolute left-full ml-2 bg-[var(--color-surface)] rounded-lg shadow-lg p-3 z-[70] flex items-center gap-2"
|
||||
role="group"
|
||||
aria-label="Volume controls"
|
||||
>
|
||||
<!-- Mute button inside slider popup -->
|
||||
<button
|
||||
onclick={toggleMute}
|
||||
class="p-1 rounded hover:bg-white/10 transition-colors"
|
||||
title={$isMuted ? "Unmute" : "Mute"}
|
||||
>
|
||||
{#if $isMuted || sliderValue === 0}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15.536 8.464a5 5 0 010 7.072M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={sliderValue}
|
||||
oninput={handleVolumeChange}
|
||||
class="w-24 h-1 accent-[var(--color-jellyfin)] cursor-pointer"
|
||||
/>
|
||||
<span class="text-xs text-gray-400 w-8 text-right">{Math.round(sliderValue * 100)}%</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Click outside to close volume slider -->
|
||||
{#if showSlider}
|
||||
<button
|
||||
class="fixed inset-0 z-[65]"
|
||||
onclick={() => showSlider = false}
|
||||
aria-label="Close volume"
|
||||
></button>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user