add drag handle to progressbar

AuthorKonata <konata@posteo.jp>
Date
Commit2326fa1d6fcdbcad2c753c9519d69b7e052f73c4
Parentad002a5
3 files changed, 79 insertions(+), 3 deletions(-)
▾Mwebclient/src/App.tsx
@@ -67,7 +67,7 @@ const App: Component = () => {
filebrowserDisplay: FileDisplay.TRACKNO_TITLE,
playlistDisplay: FileDisplay.TRACKNO_TITLE,
controlsDisplay: FileDisplay.TITLE,
progressBarHeight: 8,
progressBarHeight: 10,
rememberDir: false,
duplicateWarning: true,
▾Mwebclient/src/components/PlayerControls.css
@@ -78,6 +78,18 @@
background-color: #4a6da7;
z-index: 1;
}
& > .progress-handle {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
background-color: #ddd;
border: 1px solid slategray;
touch-action: none; /*needed to disable pull-to-refresh on mobile*/
z-index: 2;
cursor: pointer;
pointer-events: auto;
}
}
}
▾Mwebclient/src/components/PlayerControls.tsx
@@ -1,6 +1,6 @@
import "./PlayerControls.css";
import type { AppOptions, PlayerState } from "../types";
import { type Accessor, createEffect, createSignal, For, onMount, type Setter, Show } from "solid-js";
import { type Accessor, createEffect, createSignal, For, onCleanup, onMount, type Setter, Show } from "solid-js";
import {
ClearSVG,
DownSVG,
@@ -63,6 +63,60 @@ export default function PlayerControls(props: PlayerControlsProps) {
props.setPlayerState("currentTime", pos * duration);
}
const [isDragging, setIsDragging] = createSignal(false);
const [dragPos, setDragPos] = createSignal(0);
createEffect(() => {
if (isDragging()) return;
setDragPos(
props.playerState.currentTrack
? Math.min(
100,
(props.playerState.currentTime /
(props.playerState.currentTrack?.metadata.duration ?? Number.POSITIVE_INFINITY)) *
100,
)
: 0,
);
});
let progressContainerRef!: HTMLDivElement;
let handleRef!: HTMLDivElement;
//pointer instead drag events needed, because for some reason e.clientX is always 0 on firefox during the drag
function handlePointerDown(e: PointerEvent) {
if (e.button !== 0) return;
e.preventDefault();
handleRef.setPointerCapture(e.pointerId); //needed for mobile drag to work
setIsDragging(true);
handlePointerMove(e);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
window.addEventListener("pointercancel", handlePointerUp); //just for safety
}
function handlePointerMove(e: PointerEvent) {
if (!isDragging() || !props.playerState.currentTrack) return;
e.preventDefault();
const rect = progressContainerRef.getBoundingClientRect();
const pos = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
setDragPos(pos * 100);
}
function handlePointerUp(e: PointerEvent) {
if (!isDragging() || !props.playerState.currentTrack) return;
setIsDragging(false);
if (e.type !== "pointercancel") {
const rect = progressContainerRef.getBoundingClientRect();
const pos = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
const duration = props.playerState.currentTrack.metadata.duration;
props.setPlayerState("waitingForStart", true);
props.audioElement.currentTime = pos * duration;
}
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
window.removeEventListener("pointercancel", handlePointerUp);
handleRef.releasePointerCapture(e.pointerId);
}
const [showPlayerControls, setShowPlayerControls] = createSignal(true);
onMount(() => {
const savePlayerControls = localStorage.getItem(LocalStorageValues.playerControls);
@@ -136,7 +190,7 @@ export default function PlayerControls(props: PlayerControlsProps) {
)}
</div>
</div>
<div class="progress-container" onClick={seekTo}>
<div class="progress-container" onClick={seekTo} ref={progressContainerRef}>
<div
class="progress-bar"
style={{
@@ -172,6 +226,16 @@ export default function PlayerControls(props: PlayerControlsProps) {
}%`,
}}
/>
<div
class="progress-handle"
style={{
left: `${dragPos()}%`,
height: `${props.options.progressBarHeight * 2}px`,
width: `${props.options.progressBarHeight * 2}px`,
}}
onPointerDown={handlePointerDown}
ref={handleRef}
/>
</div>
</div>
<Show when={showPlayerControls()}>