persistent volume control

This commit is contained in:
ThetaDev 2023-04-12 14:45:27 +02:00
parent 4c8674d165
commit e9b48cf01d
8 changed files with 86 additions and 47 deletions

View file

@ -2,12 +2,12 @@
import { mdiChevronDown, mdiChevronUp } from "@mdi/js";
import Icon from "$lib/components/ui/Icon.svelte";
import { layout, toggleCurrentCoverLarge } from "$lib/stores/layout";
import { clientSettings, toggleCurrentCoverLarge } from "$lib/stores/clientSettings";
export let isLarge = false;
</script>
{#if $layout.currentCoverLarge === isLarge}
{#if $clientSettings.currentCoverLarge === isLarge}
<div class="relative">
<a href="/#">
<div class:small={!isLarge}>

View file

@ -3,14 +3,13 @@
mdiHeart,
mdiHeartOutline,
mdiFullscreen,
mdiVolumeHigh,
mdiCardTextOutline,
mdiPlaylistMusic,
mdiPlay,
mdiSkipPrevious,
mdiSkipNext,
mdiShuffle,
mdiRepeat,
mdiMicrophoneMessage,
} from "@mdi/js";
import Icon from "$lib/components/ui/Icon.svelte";
@ -19,11 +18,11 @@
import TrackName from "./TrackName.svelte";
import VolumeControl from "./VolumeControl.svelte";
import { layout } from "$lib/stores/layout";
import { clientSettings } from "$lib/stores/clientSettings";
</script>
<div class="playerbar">
<div class="pr-4" class:pl-4={!$layout.currentCoverLarge}>
<div class="pr-4" class:pl-4={!$clientSettings.currentCoverLarge}>
<!-- current track -->
<div class="playerbar-track">
<div class="now-playing" aria-label="Now playing: Across the Sea by Leaves' Eyes">
@ -68,7 +67,7 @@
<div class="playerbar-options">
<div>
<button class="btn btn-ghost btn-circle btn-sm">
<Icon path={mdiCardTextOutline} size={1.5} />
<Icon path={mdiMicrophoneMessage} size={1.5} />
</button>
<button class="btn btn-ghost btn-circle btn-sm">
<Icon path={mdiPlaylistMusic} size={1.5} />

View file

@ -3,11 +3,14 @@
import Icon from "$lib/components/ui/Icon.svelte";
import Slider from "$lib/components/ui/Slider.svelte";
import { clientSettings, setVolume, toggleMute } from "$lib/stores/clientSettings";
let mute = false;
let volume = 100;
let mute: boolean;
let volume = $clientSettings.volume || 100;
let icon: string;
$: mute = Boolean($clientSettings.mute);
$: icon = mute
? mdiVolumeMute
: volume > 50
@ -15,11 +18,31 @@
: volume > 10
? mdiVolumeMedium
: mdiVolumeLow;
const SCROLL_STEP = 5;
function onScroll(e: WheelEvent) {
if (e.deltaY < 0) {
volume = Math.min(volume + SCROLL_STEP, 100);
updateVolume();
} else if (e.deltaY > 0) {
volume = Math.max(volume - SCROLL_STEP, 0);
updateVolume();
}
}
function updateVolume() {
setVolume(volume);
}
</script>
<button class="btn btn-ghost btn-circle btn-sm" on:click={() => (mute = !mute)}>
<button
class="btn btn-ghost btn-circle btn-sm"
on:click={toggleMute}
on:wheel={onScroll}
>
<Icon path={icon} size={1.5} />
</button>
<div class="inline-block w-20">
<Slider min={0} max={100} bind:value={volume} />
<div class="inline-block w-20" on:wheel={onScroll}>
<Slider min={0} max={100} bind:value={volume} on:change={updateVolume} liveUpdate />
</div>

View file

@ -52,6 +52,7 @@
}
function reset() {
position = 0;
if (timer !== null) {
window.clearTimeout(timer);
timer = null;

View file

@ -7,6 +7,8 @@
export let min = 0;
export let max = 100;
export let value = 0;
// Update value in real time while dragging
export let liveUpdate = false;
// Node bindings
let element: HTMLElement;
@ -27,12 +29,9 @@
}
// Allows both bind:value and on:change for parent value retrieval
function setValue(newValue: number) {
if (value !== newValue) {
value = newValue;
valueTmp = newValue;
dispatch("change", { value });
}
function setValue(newValue: number, dispatchEvent = true) {
value = newValue;
if (dispatchEvent) dispatch("change", { value });
}
function onTrackEvent(e: Event) {
@ -41,21 +40,15 @@
onDragStart();
}
function onScroll(e: WheelEvent) {
if (e.deltaY > 0) {
setValue(Math.min(value + 5, max));
} else if (e.deltaY < 0) {
setValue(Math.max(value - 5, min));
}
}
function onDragStart() {
holding = true;
}
function onDragEnd() {
holding = false;
setValue(valueTmp);
if (holding) {
setValue(valueTmp);
holding = false;
}
}
// Accessible keypress handling
@ -74,7 +67,7 @@
}
if (e.key === "ArrowDown" || e.key === "ArrowLeft") {
if (value - throttled < min || value <= min) {
valueTmp = min;
setValue(min);
} else {
setValue(value - throttled);
}
@ -100,6 +93,10 @@
// Limit value min -> max
valueTmp = (percent * (max - min)) / 100 + min;
if (liveUpdate) {
setValue(valueTmp, false);
}
}
// Handles both dragging of touch/mouse as well as simple one-off click/touches
@ -116,7 +113,7 @@
? (e as TouchEvent).touches[0]
: (e as MouseEvent);
if (elementRect !== null) {
if (elementRect !== null && !liveUpdate) {
const dist = Math.abs(elementRect.y - coords.clientY);
if (dist > window.innerHeight * 0.4) {
valueTmp = value;
@ -130,7 +127,9 @@
// React to left position of element relative to window
$: if (element) elementRect = element.getBoundingClientRect();
$: percent = ((Math.min(Math.max(valueTmp, min), max) - min) * 100) / (max - min);
$: percent =
((Math.min(Math.max(holding ? valueTmp : value, min), max) - min) * 100) /
(max - min);
</script>
<svelte:window
@ -154,7 +153,6 @@
aria-valuenow={value}
on:mousedown={onTrackEvent}
on:touchstart={onTrackEvent}
on:wheel={onScroll}
style={`--slider-transform:${percent}%;`}
>
<div class="slider-bg slider-shape">

View file

@ -0,0 +1,30 @@
import type { ClientSettings } from "$lib/util/types";
import { persisted } from "svelte-local-storage-store";
import type { Writable } from "svelte/store";
export const clientSettings: Writable<ClientSettings> = persisted("settings", {
currentCoverLarge: false,
mute: false,
volume: 100,
});
export function toggleCurrentCoverLarge() {
clientSettings.update((v: ClientSettings) => {
v.currentCoverLarge = !v.currentCoverLarge;
return v;
});
}
export function toggleMute() {
clientSettings.update((v: ClientSettings) => {
v.mute = !v.mute;
return v;
});
}
export function setVolume(volume: number) {
clientSettings.update((v: ClientSettings) => {
v.volume = volume;
return v;
});
}

View file

@ -1,14 +0,0 @@
import { persisted } from "svelte-local-storage-store";
import type { SettingsLayout } from "$lib/util/types";
import type { Writable } from "svelte/store";
export const layout: Writable<SettingsLayout> = persisted("settings_layout", {
currentCoverLarge: false,
});
export function toggleCurrentCoverLarge() {
layout.update((v: SettingsLayout) => {
v.currentCoverLarge = !v.currentCoverLarge;
return v;
});
}

View file

@ -7,6 +7,8 @@ export type Link = {
title: string;
};
export type SettingsLayout = {
export type ClientSettings = {
currentCoverLarge: boolean;
volume: number;
mute: boolean;
};