Skip to content

Commit 246b449

Browse files
committed
feat: audio controls
1 parent edd1550 commit 246b449

File tree

12 files changed

+235
-21
lines changed

12 files changed

+235
-21
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ In addition to colors, `config.css` provides variables that control other style
163163
--not-playing: var(--tau-love);
164164
--network: var(--tau-text);
165165
--weather: var(--tau-text);
166+
--volume-start: var(--tau-strong);
167+
--volume-end: var(--tau-accent);
168+
--volume-inner: var(--tau-accent);
166169
--bg-focused: var(--tau-highlight-high) / 0.4;
167170
--bg-unfocused: var(--tau-overlay) / 0.5;
168171
```
@@ -202,6 +205,9 @@ In addition to colors, `config.css` provides variables that control other style
202205
--tiling-direction: var(--rp-rose);
203206
--network: var(--rp-text);
204207
--weather: var(--rp-text);
208+
--volume-start: var(--rp-love);
209+
--volume-end: var(--rp-foam);
210+
--volume-inner: var(--rp-foam);
205211
--bg-focused: var(--rp-subtle) / 0.4;
206212
--bg-unfocused: var(--rp-base) / 0.5;
207213
```
@@ -234,6 +240,9 @@ In addition to colors, `config.css` provides variables that control other style
234240
--tiling-direction: var(--rp-moon-rose);
235241
--network: var(--rp-moon-text);
236242
--weather: var(--rp-moon-text);
243+
--volume-start: var(--rp-moon-love);
244+
--volume-end: var(--rp-moon-foam);
245+
--volume-inner: var(--rp-moon-foam);
237246
--bg-focused: var(--rp-moon-subtle) / 0.4;
238247
--bg-unfocused: var(--rp-moon-base) / 0.5;
239248
```
@@ -266,6 +275,9 @@ In addition to colors, `config.css` provides variables that control other style
266275
--tiling-direction: var(--rp-dawn-rose);
267276
--network: var(--rp-dawn-text);
268277
--weather: var(--rp-dawn-text);
278+
--volume-start: var(--rp-dawn-love);
279+
--volume-end: var(--rp-dawn-foam);
280+
--volume-inner: var(--rp-dawn-foam);
269281
--bg-focused: var(--rp-dawn-overlay) / 0.4;
270282
--bg-unfocused: var(--rp-dawn-text) / 0.5;
271283
```

package.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,33 @@
1212
"format": "prettier --write ."
1313
},
1414
"devDependencies": {
15+
"@eslint/js": "^9.34.0",
16+
"@lucide/svelte": "^0.541.0",
1517
"@sveltejs/adapter-auto": "^6.1.0",
1618
"@sveltejs/adapter-static": "^3.0.9",
1719
"@sveltejs/kit": "^2.31.1",
1820
"@sveltejs/vite-plugin-svelte": "^6.1.2",
21+
"@tabler/icons-svelte": "^3.34.1",
22+
"@tauri-apps/api": "^2.7.0",
1923
"@types/eslint": "^9.6.1",
2024
"autoprefixer": "^10.4.21",
2125
"eslint": "^9.34.0",
22-
"@eslint/js": "^9.34.0",
2326
"eslint-config-prettier": "^10.1.8",
2427
"eslint-plugin-svelte": "^3.11.0",
28+
"fs-extra": "^11.3.1",
2529
"globals": "^16.3.0",
30+
"json5": "^2.2.3",
31+
"jsonschema": "^1.5.0",
2632
"postcss": "^8.5.6",
2733
"prettier": "^3.6.2",
2834
"prettier-plugin-svelte": "^3.4.0",
2935
"svelte": "^5.38.6",
30-
"@lucide/svelte": "^0.541.0",
31-
"@tabler/icons-svelte": "^3.34.1",
3236
"svelte-check": "^4.3.1",
3337
"tailwindcss": "^3.4.17",
3438
"typescript": "^5.9.2",
3539
"typescript-eslint": "^8.41.0",
3640
"vite": "^7.1.3",
37-
"json5": "^2.2.3",
38-
"jsonschema": "^1.5.0",
39-
"@tauri-apps/api": "^2.7.0",
40-
"fs-extra": "^11.3.1"
41+
"svelte-range-slider-pips": "https://github.com/blaiyz/svelte-range-slider-pips/tarball/main"
4142
},
4243
"type": "module",
4344
"dependencies": {

pnpm-lock.yaml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
@tailwind components;
1212
@tailwind utilities;
1313

14-
@layer base {
14+
@layer custom-base {
1515
body {
1616
overflow: hidden;
1717
font-size: var(--font-size);

src/components/NowPlaying.svelte

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,17 @@
5353
{#if isOnPrimaryMonitor() && media && shownSession}
5454
<div
5555
transition:fly={{ y: 20, duration: config.transitionDuration }}
56-
class="flex items-center gap-3 mr-2"
56+
class="flex items-stretch"
5757
>
5858
<button
59-
class="transition hover:text-zb-accent hover:scale-125"
59+
class="transition px-2 hover:text-zb-accent hover:scale-125"
6060
aria-label="Previous"
6161
onclick={() => media.previous()}
6262
>
6363
<SkipBack />
6464
</button>
6565
<button
66-
class="relative gap-x-2 overflow-hidden inline-flex items-center justify-center group transition hover:text-zb-accent"
66+
class="relative px-1 gap-x-2 overflow-hidden inline-flex items-center justify-center group transition hover:text-zb-accent"
6767
aria-label="Toggle"
6868
onclick={() => media.togglePlayPause()}
6969
>
@@ -102,7 +102,7 @@
102102
</SmoothDiv>
103103
</button>
104104
<button
105-
class="transition hover:text-zb-accent hover:scale-125"
105+
class="transition px-2 hover:text-zb-accent hover:scale-125"
106106
aria-label="Next"
107107
onclick={() => media.next()}
108108
>

src/components/RightGroup.svelte

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import PointFilled from "@tabler/icons-svelte/icons/point-filled";
66
import NowPlaying from "./NowPlaying.svelte";
77
import SmoothDiv from "./SmoothDiv.svelte";
8+
import VolumeControl from "./VolumeControl/VolumeControl.svelte";
89
910
let date = $derived(providers.date);
1011
let weather = $derived(providers.weather);
@@ -15,12 +16,24 @@
1516
const shortDay = days[date.getDay()];
1617
return shortDay + " " + date.toLocaleDateString();
1718
};
19+
20+
const timeOptions: Intl.DateTimeFormatOptions = {
21+
hour: "numeric",
22+
minute: "2-digit"
23+
};
1824
</script>
1925

2026
<div class="flex flex-row items-center h-full">
2127
<NowPlaying />
28+
{#if isOnPrimaryMonitor()}
29+
<VolumeControl />
30+
{/if}
2231
{#if weather}
23-
<div class="truncate flex items-center mr-2">
32+
<div
33+
class="truncate flex items-center pr-2 {!isOnPrimaryMonitor()
34+
? 'pl-2'
35+
: ''}"
36+
>
2437
<span class="text-2xl">
2538
{#if weather.status === "clear_day"}
2639
<i class="nf nf-weather-day_sunny"></i>
@@ -53,10 +66,13 @@
5366
{/if}
5467
{#if !isOnPrimaryMonitor() || !config.taskbarIntegration.enabled}
5568
<PointFilled class="mr-2" />
56-
<SmoothDiv outerClass="flex h-full overflow-hidden">
57-
<button onclick={() => (fullDate = !fullDate)}>
69+
<SmoothDiv outerClass="flex items-stretch h-full overflow-hidden">
70+
<button
71+
class="hover:text-zb-accent"
72+
onclick={() => (fullDate = !fullDate)}
73+
>
5874
<p class="whitespace-nowrap">
59-
{date?.formatted}
75+
{date?.new.toLocaleTimeString(undefined, timeOptions)}
6076
{#if fullDate}
6177
-
6278
{date && getDate(date?.new)}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#VolumeSlider {
2+
@apply transition-colors;
3+
--pip-height: 0.4rem;
4+
--track-width: 0.4rem;
5+
--handle-rotate: 0deg;
6+
7+
--range-slider: hsl(var(--border));
8+
--range-inactive: hsl(var(--volume-end));
9+
--range-range: hsl(var(--volume-end));
10+
--range-low: hsl(var(--volume-start));
11+
12+
--range-handle-inactive: hsl(var(--volume-inner));
13+
--range-handle: hsl(var(--volume-inner));
14+
--range-handle-focus: hsl(var(--volume-inner));
15+
--range-handle-border: transparent;
16+
17+
--range-pip: hsl(var(--text));
18+
--range-pip-in-range: hsl(var(--volume-end));
19+
--range-pip-active: hsl(var(--volume-end));
20+
--range-pip-hover: hsl(var(--volume-end));
21+
}
22+
23+
#VolumeSlider.rangeSlider.rsPips {
24+
margin-bottom: 0;
25+
}
26+
27+
#VolumeSlider.rangeSlider {
28+
margin: 0;
29+
margin-inline: 0.5rem;
30+
@apply transition-[margin];
31+
}
32+
33+
#VolumeSlider.rangeSlider:hover,
34+
#VolumeSlider.rangeSlider:active {
35+
margin-inline: 1.2rem;
36+
}
37+
38+
#VolumeSlider .rangeBar {
39+
@apply bg-gradient-to-r from-[var(--range-low)] to-[var(--range)];
40+
}
41+
42+
#VolumeSlider .rangePips .rsPip {
43+
height: var(--pip-height);
44+
width: 0.1rem;
45+
}
46+
47+
#VolumeSlider.rangeSlider .rangeHandle {
48+
width: 0;
49+
height: 0;
50+
background: none;
51+
display: grid;
52+
grid-template: 1fr / 1fr;
53+
align-items: center;
54+
overflow: hidden;
55+
@apply transition-[width,height];
56+
}
57+
58+
#VolumeSlider.rangeSlider:hover .rangeHandle,
59+
#VolumeSlider.rangeSlider:active .rangeHandle {
60+
width: 2.2rem;
61+
height: 1.2rem;
62+
}
63+
64+
#VolumeSlider.rangeSlider .rangeFloat {
65+
opacity: 0;
66+
background: transparent;
67+
translate: none;
68+
grid-area: 1 / 1;
69+
position: static;
70+
padding: 0;
71+
}
72+
73+
#VolumeSlider.rangeSlider:hover .rangeFloat,
74+
#VolumeSlider.rangeSlider:active .rangeFloat {
75+
opacity: 1;
76+
}
77+
78+
#VolumeSlider.rangeSlider .rangeNub {
79+
transform: none;
80+
border-width: 0.2rem;
81+
color: var(--range-handle);
82+
border-color: var(--range-handle);
83+
background-color: color-mix(in srgb, currentColor 50%, hsl(var(--bg)));
84+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<script lang="ts">
2+
import { providers } from "$lib/providers.svelte";
3+
import VolumeX from "@lucide/svelte/icons/volume-x";
4+
import Volume from "@lucide/svelte/icons/volume";
5+
import Volume1 from "@lucide/svelte/icons/volume-1";
6+
import Volume2 from "@lucide/svelte/icons/volume-2";
7+
import VolumeOff from "@lucide/svelte/icons/volume-off";
8+
import ChevronLeft from "@lucide/svelte/icons/chevron-left";
9+
import RangeSlider from "svelte-range-slider-pips";
10+
import SmoothDiv from "../SmoothDiv.svelte";
11+
import "./VolumeControl.css";
12+
13+
let audio = $derived(providers.audio);
14+
let device = $derived(audio?.defaultPlaybackDevice);
15+
let volume = $derived(device?.volume ?? 0);
16+
let muted = $derived(device?.isMuted ?? false);
17+
let sliderOpen = $state(false);
18+
let values = $derived([volume]);
19+
20+
const onMouseWheel = (e: WheelEvent) => {
21+
if (!device) return;
22+
e.preventDefault();
23+
const increment = 5;
24+
let newVolume = volume + (e.deltaY < 0 ? increment : -increment);
25+
newVolume = Math.max(0, Math.min(100, newVolume));
26+
audio?.setVolume(newVolume);
27+
};
28+
</script>
29+
30+
{#if device}
31+
<div class="flex items-stretch h-full mr-1" onwheel={onMouseWheel}>
32+
<div class="h-full overflow-hidden flex items-center">
33+
<SmoothDiv outerClass="flex justify-end">
34+
{#if sliderOpen}
35+
<RangeSlider
36+
id="VolumeSlider"
37+
class="w-24 mr-2"
38+
float
39+
min={0}
40+
max={100}
41+
range="min"
42+
step={1}
43+
pips
44+
pipstep={10}
45+
bind:values
46+
on:change={(e) => audio?.setVolume(e.detail.value)}
47+
/>
48+
{/if}
49+
</SmoothDiv>
50+
</div>
51+
<button
52+
class="transition px-1 text-lg hover:text-zb-accent hover:scale-125 {sliderOpen
53+
? 'rotate-180'
54+
: ''}"
55+
aria-label="Open volume slider"
56+
onclick={() => (sliderOpen = !sliderOpen)}><ChevronLeft /></button
57+
>
58+
<button
59+
class="transition px-1 mr-1 text-lg stroke-2 hover:text-zb-accent hover:scale-125"
60+
aria-label="Mute"
61+
onclick={() => audio?.setMute(!muted)}
62+
>
63+
{#if device}
64+
{#if muted || volume === 0}
65+
<VolumeX />
66+
{:else if volume <= 33}
67+
<Volume />
68+
{:else if volume > 33 && volume <= 66}
69+
<Volume1 />
70+
{:else}
71+
<Volume2 />
72+
{/if}
73+
{:else}
74+
<VolumeOff />
75+
{/if}
76+
</button>
77+
</div>
78+
{/if}

src/lib/providers.svelte.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import { configLoaded, type Config } from "./config.svelte";
88
const providerConfig = {
99
battery: { type: "battery", refreshInterval: 1000 * 60 * 1 }, // 1 minute
1010
cpu: { type: "cpu", refreshInterval: 2000 },
11-
date: { type: "date", formatting: "HH:mm" },
11+
date: { type: "date" },
1212
glazewm: { type: "glazewm" },
1313
memory: { type: "memory", refreshInterval: 5000 },
1414
network: { type: "network", refreshInterval: 2000 },
1515
weather: { type: "weather", refreshInterval: 1000 * 60 * 15 }, // 15 minutes
16-
media: { type: "media" }
16+
media: { type: "media" },
17+
audio: { type: "audio" }
1718
} satisfies ProviderGroupConfig;
1819

1920
const providers: ProviderGroup<typeof providerConfig>["outputMap"] = $state({
@@ -24,7 +25,8 @@ const providers: ProviderGroup<typeof providerConfig>["outputMap"] = $state({
2425
memory: null,
2526
network: null,
2627
weather: null,
27-
media: null
28+
media: null,
29+
audio: null
2830
});
2931

3032
async function initProviders() {

0 commit comments

Comments
 (0)