chore: add volume slider

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
HorizonCodeandCopilot committed 2026-05-02 22:15:39 +02:00
1 parent adf607e24c
commit 3900b5c6cf
7 files changed
+183 -24

No files matched your search

+115 -15
View File
@@ -1,14 +1,13 @@
<script lang="ts">
import Minimize from 'lucide-svelte/icons/minus';
import Close from 'lucide-svelte/icons/x';
import Logo from '$assets/logo.png';
import Volume2 from 'lucide-svelte/icons/volume-2';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { onMount } from 'svelte';
import { launcherVersion } from '@/global';
import Badge from '../badge/badge.svelte';
import { reduceAnimations } from '@/userSettings';
import { custom_theme_audio_playing, custom_theme_volume } from '@/global';
import { reduceAnimations, userSettings } from '@/userSettings';
import { Volume, Volume1, VolumeOff } from 'lucide-svelte';
onMount(() => {
const appWindow = getCurrentWindow();
@@ -21,14 +20,67 @@
</script>
<div data-tauri-drag-region class="titlebar z-[100]">
{#if $custom_theme_audio_playing}
{#if $custom_theme_audio_playing}
<div class="relative flex items-center justify-center group">
<div class="relative flex items-center justify-center p-1 cursor-pointer">
<div
class="absolute inset-0 bg-background rounded
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}"
></div>
{#if $custom_theme_volume > 0.7}
<Volume2 class="z-10 pointer-events-none" size={14} />
{:else if $custom_theme_volume >= 0.15}
<Volume1 class="z-10 pointer-events-none" size={14} />
{:else if $custom_theme_volume > 0}
<Volume class="z-10 pointer-events-none" size={14} />
{:else}
<VolumeOff class="z-10 pointer-events-none" size={14} />
{/if}
</div>
<div
class="absolute top-full right-0 translate-x-[7px]
opacity-0 invisible pointer-events-none -translate-y-2
group-hover:opacity-100 group-hover:visible group-hover:pointer-events-auto group-hover:translate-y-0
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
z-50"
>
<div class="h-2 w-full"></div>
<div
class="flex items-center justify-center h-[120px] w-[35px]
bg-black/20 backdrop-blur-sm border border-white/10 rounded-xl"
>
<input
type="range"
min="0"
max="1"
step="0.01"
bind:value={$custom_theme_volume}
on:input={() => {
$userSettings.value('volume').set($custom_theme_volume);
$userSettings.save();
}}
class="volume-slider"
/>
</div>
</div>
</div>
{/if}
{/if}
<div class="relative flex items-center justify-center p-1 group" id="titlebar-minimize">
<div
class="absolute inset-0 bg-background rounded
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
></div>
<Minimize class="z-10 pointer-events-none" size={14} />
@@ -36,11 +88,11 @@
<div class="relative flex items-center justify-center p-1 group" id="titlebar-close">
<div
class="absolute inset-0 bg-red-500 rounded
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
scale-80 opacity-0
group-hover:opacity-100
group-hover:scale-100
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
origin-center"
></div>
<Close class="z-10 pointer-events-none" size={14} />
@@ -63,4 +115,52 @@
padding-right: 5px;
pointer-events: auto;
}
.volume-slider {
appearance: none;
-webkit-appearance: none;
width: 100px; /* becomes vertical */
height: 6px;
background: transparent;
cursor: pointer;
transform: rotate(-90deg);
}
/* track */
.volume-slider::-webkit-slider-runnable-track {
height: 6px;
background: rgba(255, 255, 255, 0.25);
border-radius: 999px;
}
/* thumb */
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
border: none;
margin-top: -4px; /* center thumb */
}
/* Firefox */
.volume-slider::-moz-range-track {
height: 6px;
background: rgba(255, 255, 255, 0.25);
border-radius: 999px;
}
.volume-slider::-moz-range-thumb {
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
border: none;
}
</style>
+9
View File
@@ -4,6 +4,8 @@ import type { Component } from 'svelte';
import Loading from '../screens/Loading.svelte';
import type { Release } from './types';
import type { Theme } from './themes';
import { browser } from '$app/environment';
import { setGlobalVolume } from './utils';
export const currentView = writable<Component>(Loading);
export const platform = writable<string>('');
@@ -27,6 +29,13 @@ export const launching = writable<boolean>(false);
export const custom_themes = writable<Theme[]>([]);
export const custom_theme_container = writable<HTMLElement | undefined>(undefined);
export const active_custom_theme = writable<Theme | undefined>(undefined);
export const custom_theme_audio_playing = writable<boolean>(false);
export const custom_theme_volume = writable<number>(0.15);
custom_theme_volume.subscribe((val) => {
if (browser) {
setGlobalVolume(val);
}
});
export const serverPing = writable<number | undefined>(undefined);
export const serverConnectionFails = writable(0);
+2 -2
View File
@@ -269,7 +269,7 @@ export const downloadTheme = async (theme: Theme, force = false): Promise<boolea
return true;
};
export const loadTheme = async (theme: Theme, themeContainer: HTMLElement) => {
export const loadTheme = async (theme: Theme, themeContainer: HTMLElement, volume = 0.15) => {
active_custom_theme.set(theme);
themeContainer.innerHTML = '';
if (theme.scriptUrl.length > 0) {
@@ -277,7 +277,7 @@ export const loadTheme = async (theme: Theme, themeContainer: HTMLElement) => {
/* @vite-ignore */
themeScript.mountTheme(themeContainer, { assets: theme.assets });
}
setGlobalVolume(0.15);
setGlobalVolume(volume);
};
export const deleteTheme = async (themeToUninstall: Theme) => {
+45
View File
@@ -1,6 +1,7 @@
import { invoke } from '@tauri-apps/api/core';
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { custom_theme_audio_playing } from './global';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -144,3 +145,47 @@ export function fadeGlobalVolume(volumeFrom: number, volumeTo: number, duration:
animate();
}
export function trackMediaContainer(node: HTMLElement) {
const media = new Set<HTMLMediaElement>();
function update() {
custom_theme_audio_playing.set(Array.from(media).some((el) => !el.paused));
}
function register(el: HTMLMediaElement) {
media.add(el);
const handler = () => update();
el.addEventListener('play', handler);
el.addEventListener('pause', handler);
return () => {
media.delete(el);
el.removeEventListener('play', handler);
el.removeEventListener('pause', handler);
};
}
// find existing media inside the div
node.querySelectorAll<HTMLMediaElement>('video, audio').forEach(register);
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
m.addedNodes.forEach((n) => {
if (n instanceof HTMLMediaElement) register(n);
if (n instanceof HTMLElement) {
n.querySelectorAll<HTMLMediaElement>('video, audio').forEach(register);
}
});
}
});
observer.observe(node, { childList: true, subtree: true });
return {
destroy() {
observer.disconnect();
},
};
}