Skip to content

Commit c2dcd92

Browse files
committed
feat: audio controls
1 parent edd1550 commit c2dcd92

File tree

11 files changed

+229
-20
lines changed

11 files changed

+229
-20
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": "^4.0.7"
4142
},
4243
"type": "module",
4344
"dependencies": {

pnpm-lock.yaml

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

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: 14 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,18 @@
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+
<VolumeControl />
2229
{#if weather}
23-
<div class="truncate flex items-center mr-2">
30+
<div class="truncate flex items-center pr-2">
2431
<span class="text-2xl">
2532
{#if weather.status === "clear_day"}
2633
<i class="nf nf-weather-day_sunny"></i>
@@ -53,10 +60,13 @@
5360
{/if}
5461
{#if !isOnPrimaryMonitor() || !config.taskbarIntegration.enabled}
5562
<PointFilled class="mr-2" />
56-
<SmoothDiv outerClass="flex h-full overflow-hidden">
57-
<button onclick={() => (fullDate = !fullDate)}>
63+
<SmoothDiv outerClass="flex items-stretch h-full overflow-hidden">
64+
<button
65+
class="hover:text-zb-accent"
66+
onclick={() => (fullDate = !fullDate)}
67+
>
5868
<p class="whitespace-nowrap">
59-
{date?.formatted}
69+
{date?.new.toLocaleTimeString(undefined, timeOptions)}
6070
{#if fullDate}
6171
-
6272
{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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<script lang="ts">
2+
import { providers } from "$lib/providers.svelte";
3+
import { config } from "$lib/config.svelte";
4+
import VolumeX from "@lucide/svelte/icons/volume-x";
5+
import Volume from "@lucide/svelte/icons/volume";
6+
import Volume1 from "@lucide/svelte/icons/volume-1";
7+
import Volume2 from "@lucide/svelte/icons/volume-2";
8+
import VolumeOff from "@lucide/svelte/icons/volume-off";
9+
import ChevronLeft from "@lucide/svelte/icons/chevron-left";
10+
import RangeSlider from "svelte-range-slider-pips";
11+
import SmoothDiv from "../SmoothDiv.svelte";
12+
import "./VolumeControl.css";
13+
import { fly } from "svelte/transition";
14+
15+
let audio = $derived(providers.audio);
16+
let device = $derived(audio?.defaultPlaybackDevice);
17+
let volume = $derived(device?.volume ?? 0);
18+
let muted = $derived(device?.isMuted ?? false);
19+
let sliderOpen = $state(false);
20+
let values = $derived([volume]);
21+
22+
const onMouseWheel = (e: WheelEvent) => {
23+
if (!device) return;
24+
e.preventDefault();
25+
const increment = 5;
26+
let newVolume = volume + (e.deltaY < 0 ? increment : -increment);
27+
newVolume = Math.max(0, Math.min(100, newVolume));
28+
audio?.setVolume(newVolume);
29+
};
30+
</script>
31+
32+
{#if device}
33+
<div class="flex items-stretch h-full mr-1" onwheel={onMouseWheel}>
34+
<div class="h-full overflow-hidden flex items-center">
35+
<SmoothDiv outerClass="flex justify-end">
36+
{#if sliderOpen}
37+
<RangeSlider
38+
id="VolumeSlider"
39+
class="w-24 mr-2"
40+
float
41+
min={0}
42+
max={100}
43+
range="min"
44+
step={1}
45+
pips
46+
pipstep={10}
47+
bind:values
48+
on:change={(e) => audio?.setVolume(e.detail.value)}
49+
/>
50+
{/if}
51+
</SmoothDiv>
52+
</div>
53+
<button
54+
class="transition px-1 text-lg hover:text-zb-accent hover:scale-125 {sliderOpen
55+
? 'rotate-180'
56+
: ''}"
57+
aria-label="Open volume slider"
58+
onclick={() => (sliderOpen = !sliderOpen)}><ChevronLeft /></button
59+
>
60+
<button
61+
class="transition px-1 mr-1 text-lg stroke-2 hover:text-zb-accent hover:scale-125"
62+
aria-label="Mute"
63+
onclick={() => audio?.setMute(!muted)}
64+
>
65+
{#if device}
66+
{#if muted || volume === 0}
67+
<VolumeX />
68+
{:else if volume <= 33}
69+
<Volume />
70+
{:else if volume > 33 && volume <= 66}
71+
<Volume1 />
72+
{:else}
73+
<Volume2 />
74+
{/if}
75+
{:else}
76+
<VolumeOff />
77+
{/if}
78+
</button>
79+
</div>
80+
{/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() {

src/routes/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
<Group
123123
rightCurve={!config.attachSides}
124124
outerClass="h-full justify-self-end"
125-
innerClass="h-full px-4 flex items-center {isOnPrimaryMonitor()
125+
innerClass="h-full pl-2 pr-4 flex items-center {isOnPrimaryMonitor()
126126
? 'pr-zrby'
127127
: ''}"
128128
>

static/config.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
--not-playing: var(--tau-love);
5454
--network: var(--tau-text);
5555
--weather: var(--tau-text);
56+
--volume-start: var(--tau-strong);
57+
--volume-end: var(--tau-accent);
58+
--volume-inner: var(--tau-accent);
5659
--bg-focused: var(--tau-highlight-high) / 0.4;
5760
--bg-unfocused: var(--tau-overlay) / 0.5;
5861
}

0 commit comments

Comments
 (0)