allow hiding playercontrols for more vertical space

AuthorKonata <konata@posteo.jp>
Date
Commit58709779acfd33db994c3c961347ea31be1fd595
Parent02a821a
3 files changed, 133 insertions(+), 96 deletions(-)
▾Mwebclient/src/components/PlayerControls.css
@@ -4,6 +4,31 @@
display: flex;
flex-direction: column;
gap: 0.5rem;
& > .track-info-toggle-container {
display: flex;
justify-content: space-between;
gap: 0.5rem;
& > .track-info {
display: flex;
gap: 1rem;
margin-bottom: 0.5rem;
margin-left: auto;
margin-right: auto;
text-align: center;
font-weight: bold;
overflow: hidden;
& > .short-name {
overflow: hidden;
text-overflow: ellipsis;
text-wrap: nowrap;
}
& > .loading-spinner {
width: 24px !important;
height: 24px !important;
}
}
}
}
.control-buttons {
@@ -21,6 +46,29 @@
display: flex;
align-items: center;
gap: 0.5rem;
& > .progress-bar {
position: relative;
flex: 1;
height: 8px;
background-color: #ddd;
cursor: pointer;
& > .buffered-fill,
& > .progress-fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
cursor: pointer;
background-color: #0cc069;
}
& > .progress-fill {
background-color: #4a6da7;
z-index: 1;
}
}
}
.time-container {
@@ -28,41 +76,6 @@
justify-content: center;
gap: 0.5rem;
}
.track-info {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 0.5rem;
text-align: center;
font-weight: bold;
.loading-spinner {
width: 24px !important;
height: 24px !important;
}
}
.progress-bar {
position: relative;
flex: 1;
height: 8px;
background-color: #ddd;
cursor: pointer;
}
.buffered-fill,
.progress-fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
cursor: pointer;
background-color: #0cc069;
}
.progress-fill {
background-color: #4a6da7;
z-index: 1;
}
.volume-control {
display: flex;
▾Mwebclient/src/components/PlayerControls.tsx
@@ -1,8 +1,18 @@
import "./PlayerControls.css";
import { type AppOptions, getItemName, type PlayerState } from "../types";
import { type Accessor, createEffect, createSignal, For, type Setter } from "solid-js";
import { NextTrackSVG, PauseSVG, PlaySVG, PreviousTrackSVG, RepeatSVG, ShuffleSVG, VolumeSVG } from "./SVG";
import { formatTime } from "../utils";
import { type Accessor, createEffect, createSignal, For, onMount, type Setter, Show } from "solid-js";
import {
DownSVG,
NextTrackSVG,
PauseSVG,
PlaySVG,
PreviousTrackSVG,
RepeatSVG,
ShuffleSVG,
UpSVG,
VolumeSVG,
} from "./SVG";
import { formatTime, LocalStorageValues } from "../utils";
import type { SetStoreFunction } from "solid-js/store";
import { LoadingSpinner } from "./LoadingScreen";
@@ -36,10 +46,6 @@ export default function PlayerControls(props: PlayerControlsProps) {
}
}
function isLocalPlaying() {
return props.audioElement.src.startsWith("blob:");
}
// Seek to position
function seekTo(e: MouseEvent) {
const playerState = props.playerState;
@@ -55,17 +61,32 @@ export default function PlayerControls(props: PlayerControlsProps) {
props.setPlayerState("currentTime", pos * duration);
}
const [showPlayerControls, setShowPlayerControls] = createSignal(true);
onMount(() => {
const savePlayerControls = localStorage.getItem(LocalStorageValues.playerControls);
if (savePlayerControls !== null) setShowPlayerControls(savePlayerControls === "true");
createEffect(() => localStorage.setItem(LocalStorageValues.playerControls, showPlayerControls().toString()));
});
return (
<div class="player-controls">
<div class="track-info">
{getItemName(props.playerState.currentTrack) || "No track selected"}
{props.playerState.waitingForStart ? (
<div>
<LoadingSpinner center={true} />
</div>
) : (
""
)}
<div class="track-info-toggle-container">
<button title="Toggle player controls" onclick={() => setShowPlayerControls(!showPlayerControls())}>
{showPlayerControls() ? <UpSVG /> : <DownSVG />}
</button>
<div class="track-info">
<span classList={{ "short-name": !showPlayerControls() }}>
{getItemName(props.playerState.currentTrack) || "No track selected"}
</span>
{props.playerState.waitingForStart ? (
<div>
<LoadingSpinner center={true} />
</div>
) : (
""
)}
</div>
</div>
<div class="progress-container" onClick={seekTo}>
<div class="progress-bar" style={controlsDisabled() ? { cursor: "default" } : {}}>
@@ -99,56 +120,58 @@ export default function PlayerControls(props: PlayerControlsProps) {
/>
</div>
</div>
<div class="time-container">
<span>{formatTime(props.playerState.currentTime)}</span>/
<span>{formatTime(props.playerState.currentTrack?.metadata.duration || 0)}</span>
</div>
<Show when={showPlayerControls()}>
<div class="time-container">
<span>{formatTime(props.playerState.currentTime)}</span>/
<span>{formatTime(props.playerState.currentTrack?.metadata.duration || 0)}</span>
</div>
<div class="control-buttons">
<button title="Previous track" disabled={controlsDisabled()} onClick={props.playPrevious}>
<PreviousTrackSVG disabled={controlsDisabled} />
</button>
<button title="Toggle play/pause" disabled={controlsDisabled()} onClick={props.togglePlay}>
{props.playerState.isPlaying ? (
<PauseSVG disabled={controlsDisabled} />
) : (
<PlaySVG disabled={controlsDisabled} />
)}
</button>
<button title="Next track" disabled={controlsDisabled()} onClick={props.playNext}>
<NextTrackSVG disabled={controlsDisabled} />
</button>
<div class="control-buttons">
<button title="Previous track" disabled={controlsDisabled()} onClick={props.playPrevious}>
<PreviousTrackSVG disabled={controlsDisabled} />
</button>
<button title="Toggle play/pause" disabled={controlsDisabled()} onClick={props.togglePlay}>
{props.playerState.isPlaying ? (
<PauseSVG disabled={controlsDisabled} />
) : (
<PlaySVG disabled={controlsDisabled} />
)}
</button>
<button title="Next track" disabled={controlsDisabled()} onClick={props.playNext}>
<NextTrackSVG disabled={controlsDisabled} />
</button>
<button title="Repeat Playlist" onClick={() => props.setRepeated(!props.isRepeated())}>
<RepeatSVG disabled={() => !props.isRepeated()} />
</button>
<button title="Repeat Playlist" onClick={() => props.setRepeated(!props.isRepeated())}>
<RepeatSVG disabled={() => !props.isRepeated()} />
</button>
<button title="Shuffle" onClick={() => props.setShuffled(!props.isShuffled())}>
<ShuffleSVG disabled={() => !props.isShuffled()} />
</button>
<button title="Shuffle" onClick={() => props.setShuffled(!props.isShuffled())}>
<ShuffleSVG disabled={() => !props.isShuffled()} />
</button>
<div class="volume-control" style={props.options.hideVolume ? { display: "none" } : {}}>
<VolumeSVG />
<input type="range" min="0" max="1" step="0.01" value={props.playerState.volume} onInput={setVolume} />
</div>
<div>
<select
value={props.playerState.rate}
onchange={(e) => props.setPlayerState("rate", Number.parseFloat(e.target.value))}
>
<option value={0.25}>0.25x</option>
<option value={0.5}>0.5x</option>
<option value={0.75}>0.75x</option>
<option value={1}>1x</option>
<option value={1.25}>1.25x</option>
<option value={1.5}>1.5x</option>
<option value={1.75}>1.75x</option>
<option value={2}>2x</option>
<option value={2.5}>2.5x</option>
<option value={3}>3x</option>
</select>
<div class="volume-control" style={props.options.hideVolume ? { display: "none" } : {}}>
<VolumeSVG />
<input type="range" min="0" max="1" step="0.01" value={props.playerState.volume} onInput={setVolume} />
</div>
<div>
<select
value={props.playerState.rate}
onchange={(e) => props.setPlayerState("rate", Number.parseFloat(e.target.value))}
>
<option value={0.25}>0.25x</option>
<option value={0.5}>0.5x</option>
<option value={0.75}>0.75x</option>
<option value={1}>1x</option>
<option value={1.25}>1.25x</option>
<option value={1.5}>1.5x</option>
<option value={1.75}>1.75x</option>
<option value={2}>2x</option>
<option value={2.5}>2.5x</option>
<option value={3}>3x</option>
</select>
</div>
</div>
</div>
</Show>
</div>
);
}
▾Mwebclient/src/utils.ts
@@ -26,6 +26,7 @@ export const LocalStorageValues = {
path: "Path",
fileBrowserButtons: "FileBrowserButtons",
playlistButtons: "PlaylistButtons",
playerControls: "PlayerControls",
};
export function generateUUID() {