[READ-ONLY] Mirror of https://github.com/probablykasper/ferrum. Music library app for Mac, Linux and Windows ferrum.kasper.space
electron linux macos music music-library music-player napi windows
0

Configure Feed

Select the types of activity you want to include in your feed.

Custom slider design

Kasper (Sep 12, 2024, 2:08 AM +0200) e057f8dc 069989c8

+111 -73
+1
CHANGELOG.md
··· 6 6 - Add playback history to queue panel 7 7 - Redesign queue panel 8 8 - Add queue panel slide-out transition 9 + - Make time & volume sliders look ok 9 10 - Fix dragging tracks to up next/autoplay queue boundary 10 11 - Fix button edge not clickable due to zoom 11 12 - Fix rearranging queue items not working when dragging the cover artwork
+9 -73
src/components/Player.svelte
··· 19 19 import { showTrackMenu } from '@/lib/menus' 20 20 import { dragged } from '@/lib/drag-drop' 21 21 import * as dragGhost from './DragGhost.svelte' 22 + import Slider from './Slider.svelte' 22 23 23 - let sliderBeingDragged = false 24 - const sliderSteps = 400 25 - let sliderValue = 0 26 - $: { 27 - if (!sliderBeingDragged && $duration > 0) { 28 - sliderValue = ($currentTime / $duration) * sliderSteps 29 - } 30 - } 31 - function sliderMousedown() { 32 - sliderBeingDragged = true 33 - } 34 - function sliderMouseup() { 35 - sliderBeingDragged = false 36 - seek((sliderValue / sliderSteps) * $duration || 0) 37 - } 38 24 async function playingContextMenu() { 39 25 const playing = queue.getCurrent() 40 26 if (playing) { ··· 192 178 </div> 193 179 <div class="time-bar"> 194 180 <small class="current-time">{getDuration($currentTime)}</small> 195 - <input 196 - type="range" 197 - tabindex="-1" 198 - class="time border-none" 199 - min="0" 200 - max={sliderSteps} 201 - bind:value={sliderValue} 202 - on:focus={(e) => { 203 - if (e.relatedTarget instanceof HTMLElement) { 204 - e.relatedTarget.focus() 205 - } else { 206 - e.currentTarget.blur() 207 - } 181 + <Slider 182 + class="w-full" 183 + value={$currentTime / $duration || 0} 184 + max={1} 185 + step={0.0001} 186 + on_apply={(value) => { 187 + seek(value * $duration) 208 188 }} 209 - on:mousedown={sliderMousedown} 210 - on:mouseup={sliderMouseup} 211 189 /> 212 190 <small class="duration">{getDuration($duration)}</small> 213 191 </div> ··· 255 233 </svg> 256 234 {/if} 257 235 </button> 258 - <input 259 - type="range" 260 - tabindex="-1" 261 - class="volume border-none" 262 - min="0" 263 - max="1" 264 - step="0.01" 265 - bind:value={$volume} 266 - on:focus={(e) => { 267 - if (e.relatedTarget instanceof HTMLElement) { 268 - e.relatedTarget.focus() 269 - } else { 270 - e.currentTarget.blur() 271 - } 272 - }} 273 - /> 236 + <Slider class="mr-1 w-[100px]" bind:value={$volume} min={0} max={1} step={0.01} /> 274 237 <button 275 238 tabindex="-1" 276 239 on:mousedown|preventDefault ··· 372 335 opacity: 0.5 373 336 min-width: 40px 374 337 font-family: 'Open Sans' // for monospace digits 375 - input 376 - -webkit-appearance: none 377 - background: transparent 378 - padding: 5px 4px 379 - margin: 0px 4px 380 - &:focus 381 - outline: none 382 - &::-webkit-slider-thumb 383 - -webkit-appearance: none 384 - margin-top: 2px 385 - transform: translate(0px, -50%) 386 - height: 10px 387 - width: 10px 388 - border-radius: 100px 389 - background: #ffffff 390 - box-shadow: 0px 0px 5px 1px rgba(0,0,0,0.5) 391 - &::-webkit-slider-runnable-track 392 - width: 100% 393 - height: 4px 394 - background: #5e5e5e 395 - border-radius: 100px 396 - position: relative 397 - input.time 398 - width: 100% 399 338 .on svg 400 339 fill: var(--icon-highlight-color) 401 340 button.volume-icon ··· 406 345 translate: 4px 407 346 .low 408 347 translate: 2px 409 - input.volume 410 - width: 100px 411 - margin-left: 0px 412 348 </style>
+101
src/components/Slider.svelte
··· 1 + <script lang="ts"> 2 + import type { HTMLBaseAttributes } from 'svelte/elements' 3 + export let value: number 4 + export let min = 0 5 + export let max = 100 6 + export let step: number | undefined = undefined 7 + export let update_on_drag = true 8 + export let on_apply: (value: number) => void = () => {} 9 + export let klass = '' 10 + export { klass as class } 11 + 12 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 13 + interface $$Props extends HTMLBaseAttributes { 14 + value: number 15 + min?: number 16 + max?: number 17 + step?: number 18 + class?: string 19 + on_apply?: (value: number) => void 20 + } 21 + 22 + let element: HTMLInputElement 23 + let dragging = false 24 + 25 + let internal_value = value 26 + $: if (element && (update_on_drag || !dragging)) { 27 + internal_value = value 28 + element.value = String(value) 29 + } 30 + </script> 31 + 32 + <div class="slider{` ${klass}`.trimEnd()}" {...$$restProps}> 33 + <div 34 + class="group relative flex h-5 w-full items-center justify-center py-2 px-1" 35 + style:--slider-value="{((min + internal_value) / max) * 100}%" 36 + > 37 + <input 38 + bind:this={element} 39 + type="range" 40 + value={internal_value} 41 + {min} 42 + {max} 43 + {step} 44 + class="px-inherit absolute h-5 w-full appearance-none rounded-full border-none bg-transparent px-[inherit]" 45 + on:focus={(e) => { 46 + if (e.relatedTarget instanceof HTMLElement) { 47 + e.relatedTarget.focus() 48 + } else { 49 + e.currentTarget.blur() 50 + } 51 + }} 52 + on:input={(e) => { 53 + internal_value = Number(e.currentTarget.value) 54 + if (update_on_drag) { 55 + value = internal_value 56 + } 57 + }} 58 + on:mousedown={() => (dragging = true)} 59 + on:mouseup={(e) => { 60 + dragging = false 61 + value = Number(e.currentTarget.value) 62 + on_apply(value) 63 + }} 64 + /> 65 + <div class="pointer-events-none relative w-full"> 66 + <div class="w-full overflow-hidden rounded-full"> 67 + <div 68 + class="relative -left-full h-1 w-full rounded-full rounded-full bg-gray-300 transition duration-100 will-change-transform group-hover:bg-[hsl(217,100%,60%)]" 69 + style:translate="var(--slider-value)" 70 + ></div> 71 + </div> 72 + <div class="absolute top-0 flex size-full items-center" style:translate="var(--slider-value)"> 73 + <div 74 + class="thumb size-2.5 -translate-x-[50%] scale-[0.4] rounded-full bg-gray-300 opacity-0 shadow-md transition duration-75 group-hover:scale-100 group-hover:opacity-100" 75 + ></div> 76 + </div> 77 + </div> 78 + </div> 79 + </div> 80 + 81 + <style> 82 + @layer base { 83 + .slider { 84 + width: 129px; 85 + } 86 + } 87 + .thumb { 88 + box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.5); 89 + } 90 + ::-webkit-slider-runnable-track { 91 + background-color: var(--color-gray-700); 92 + border-radius: 100px; 93 + height: 4px; 94 + } 95 + input::-webkit-slider-thumb { 96 + appearance: none; 97 + opacity: 0; 98 + width: 0px; 99 + height: 0px; 100 + } 101 + </style>