Typing lessons like duolingo & musicmouse cleanup
This commit is contained in:
@@ -234,7 +234,6 @@ export function Ambience({ album, state, tunables, manual, onDebugFrame }: Props
|
||||
const baseRef = useRef(ambienceBaseFor(album, trackAnalysis));
|
||||
useEffect(() => {
|
||||
baseRef.current = ambienceBaseFor(album, trackAnalysis);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [album, trackAnalysis]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* the whole page. */
|
||||
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import type { MouseEvent as ReactMouseEvent, ReactNode } from "react";
|
||||
|
||||
import type { Album } from "../api/types";
|
||||
import {
|
||||
@@ -546,7 +547,7 @@ function ShelfRow({
|
||||
children,
|
||||
focusedIndex,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
/** Which tile in this row the keyboard is on, or `null` while some other row has
|
||||
* focus - keeps a Ctrl+h/l selection that's scrolled off the edge on screen, the
|
||||
* horizontal equivalent of the outer scroller's own keep-in-view effect above. */
|
||||
@@ -580,7 +581,7 @@ function ShelfRow({
|
||||
return () => element.removeEventListener("wheel", onWheel);
|
||||
}, []);
|
||||
|
||||
const onMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
const onMouseDown = (event: ReactMouseEvent<HTMLDivElement>) => {
|
||||
const element = ref.current;
|
||||
if (event.button !== 0 || !element) return;
|
||||
event.preventDefault(); // no text-selection/ghost-drag while panning
|
||||
@@ -632,7 +633,7 @@ function CategoryTile({
|
||||
entry: Category;
|
||||
navIndex?: number;
|
||||
selected?: boolean;
|
||||
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onClick: (event: ReactMouseEvent<HTMLButtonElement>) => void;
|
||||
}) {
|
||||
const books = entry.albums.filter(isBook).length;
|
||||
const allBooks = books === entry.albums.length;
|
||||
@@ -728,7 +729,7 @@ function SectionTitle({
|
||||
children,
|
||||
centered,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
centered?: boolean;
|
||||
}) {
|
||||
return (
|
||||
@@ -750,7 +751,7 @@ function SectionTitle({
|
||||
);
|
||||
}
|
||||
|
||||
function Muted({ children }: { children: React.ReactNode }) {
|
||||
function Muted({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<span style={{ fontSize: 13, fontWeight: 700, color: "oklch(88% 0.02 210 / .7)" }}>
|
||||
{children}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* taller than wide for an audiobook. Callers pick which axis is fixed, never the ratio.
|
||||
*/
|
||||
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
import { coverUrl } from "../api/client";
|
||||
import type { Album } from "../api/types";
|
||||
import { aspectOf, coverBackground, isBook } from "../lib/covers";
|
||||
@@ -21,7 +23,7 @@ interface Props {
|
||||
fit?: "width" | "height";
|
||||
radius?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
style?: CSSProperties;
|
||||
/** Print the title over generated art. Off for thumbnails, where it would not fit. */
|
||||
label?: boolean;
|
||||
}
|
||||
@@ -29,7 +31,7 @@ interface Props {
|
||||
export function Cover({ album, size, fit = "width", radius, className, style, label }: Props) {
|
||||
const book = isBook(album);
|
||||
const defaultRadius = book ? "6px 18px 18px 6px" : "16px";
|
||||
const box: React.CSSProperties =
|
||||
const box: CSSProperties =
|
||||
fit === "height"
|
||||
? { height: "100%", width: "auto", aspectRatio: aspectOf(album) }
|
||||
: { width: "100%", aspectRatio: aspectOf(album) };
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { Album, PlayerState } from "../api/types";
|
||||
import { usePlaybackClock } from "../hooks/usePlaybackClock";
|
||||
import { albumLine, isBook } from "../lib/covers";
|
||||
import { isBook, nowPlayingText } from "../lib/covers";
|
||||
import { clock, remainingInAlbum } from "../lib/format";
|
||||
import { groupOf } from "../lib/search";
|
||||
import { SHOW_DECORATIVE_ANIMATIONS } from "../lib/theme";
|
||||
@@ -41,6 +41,7 @@ export function PlayView({
|
||||
assignPending,
|
||||
}: Props) {
|
||||
const position = usePlaybackClock(state.position, state.playing);
|
||||
const now = nowPlayingText(state, album);
|
||||
const book = album ? isBook(album) : false;
|
||||
const podcast = album ? groupOf(album) === "podcasts" : false;
|
||||
const remaining = album
|
||||
@@ -138,7 +139,7 @@ export function PlayView({
|
||||
textWrap: "pretty",
|
||||
}}
|
||||
>
|
||||
{state.track_title ?? "Wähl ein Album!"}
|
||||
{now.title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
@@ -148,7 +149,7 @@ export function PlayView({
|
||||
marginTop: 8,
|
||||
}}
|
||||
>
|
||||
{album ? albumLine(album) : "Tippen oder klicken"}
|
||||
{now.subtitle}
|
||||
</div>
|
||||
{album && (
|
||||
<div
|
||||
@@ -159,7 +160,7 @@ export function PlayView({
|
||||
marginTop: 6,
|
||||
}}
|
||||
>
|
||||
{book ? "Kapitel" : "Song"} {state.track_index + 1} von {state.track_count}
|
||||
{now.progress}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -169,7 +170,7 @@ export function PlayView({
|
||||
position={position}
|
||||
duration={state.duration}
|
||||
onSeek={onSeek}
|
||||
resetKey={`${state.album_id ?? ""}:${state.track_index}`}
|
||||
resetKey={now.resetKey}
|
||||
height={14}
|
||||
interactive
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { Album, PlayerState } from "../api/types";
|
||||
import { usePlaybackClock } from "../hooks/usePlaybackClock";
|
||||
import { albumLine, isBook } from "../lib/covers";
|
||||
import { nowPlayingText } from "../lib/covers";
|
||||
import { SHOW_DECORATIVE_ANIMATIONS, SHOW_GLASS_BLUR } from "../lib/theme";
|
||||
import { Cover } from "./Cover";
|
||||
import { ProgressBar } from "./ProgressBar";
|
||||
@@ -33,6 +33,7 @@ export function PlayerBar({
|
||||
onOpenPlayView,
|
||||
}: Props) {
|
||||
const position = usePlaybackClock(state.position, state.playing);
|
||||
const now = nowPlayingText(state, album);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -100,7 +101,7 @@ export function PlayerBar({
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{state.track_title ?? "Wähl ein Album!"}
|
||||
{now.title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
@@ -112,7 +113,7 @@ export function PlayerBar({
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{album ? albumLine(album) : "Tippen oder klicken"}
|
||||
{now.subtitle}
|
||||
</div>
|
||||
{album && (
|
||||
<div
|
||||
@@ -123,8 +124,7 @@ export function PlayerBar({
|
||||
marginTop: 1,
|
||||
}}
|
||||
>
|
||||
{isBook(album) ? "Kapitel" : "Song"} {state.track_index + 1} von{" "}
|
||||
{state.track_count}
|
||||
{now.progress}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -134,7 +134,7 @@ export function PlayerBar({
|
||||
position={position}
|
||||
duration={state.duration}
|
||||
onSeek={onSeek}
|
||||
resetKey={`${state.album_id ?? ""}:${state.track_index}`}
|
||||
resetKey={now.resetKey}
|
||||
height={10}
|
||||
interactive
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** The progress bar, and the one thing the mockup could not do: scrubbing. */
|
||||
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import type { PointerEvent } from "react";
|
||||
|
||||
interface Props {
|
||||
position: number;
|
||||
@@ -45,7 +46,7 @@ export function ProgressBar({
|
||||
const shown = dragging ?? position;
|
||||
const percent = duration > 0 ? Math.min(100, (shown / duration) * 100) : 0;
|
||||
|
||||
const onPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
const onPointerDown = (event: PointerEvent<HTMLDivElement>) => {
|
||||
if (!interactive || !duration) return;
|
||||
event.currentTarget.setPointerCapture(event.pointerId);
|
||||
setDragging(positionAt(event.clientX));
|
||||
|
||||
Reference in New Issue
Block a user