Typing lessons like duolingo & musicmouse cleanup
This commit is contained in:
@@ -154,6 +154,33 @@ export function App() {
|
||||
void (state.playing ? api.pause() : api.resume());
|
||||
}, [connection, library.albums, play, results.albums, state]);
|
||||
|
||||
const onMute = useCallback(
|
||||
() => setVolume(state && state.volume > 0 ? 0 : UNMUTE_PERCENT),
|
||||
[setVolume, state],
|
||||
);
|
||||
|
||||
/** The one seek primitive: an optimistic position patch plus the backend call.
|
||||
* Shared by dragging the progress bar (an absolute target) and Shift+Arrow /
|
||||
* podcast skip (a target computed from the current position). */
|
||||
const seekTo = useCallback(
|
||||
(target: number) => {
|
||||
connection.optimistic({ position: target });
|
||||
void api.seek(target);
|
||||
},
|
||||
[connection],
|
||||
);
|
||||
|
||||
const skipPodcast = useCallback(
|
||||
(direction: 1 | -1) => {
|
||||
const target =
|
||||
direction > 0
|
||||
? Math.min(state?.duration ?? 0, (state?.position ?? 0) + PODCAST_SKIP_SECONDS)
|
||||
: Math.max(0, (state?.position ?? 0) - PODCAST_SKIP_SECONDS);
|
||||
seekTo(target);
|
||||
},
|
||||
[seekTo, state],
|
||||
);
|
||||
|
||||
/** The one place a keyboard action, a click or a tap all end up. */
|
||||
// Read-modify-write the whole mapping, like ParentPanel does for settings: the
|
||||
// backend takes a full replacement, not a per-slot patch.
|
||||
@@ -218,12 +245,7 @@ export function App() {
|
||||
case "next":
|
||||
playPop(260);
|
||||
if (currentAlbum && groupOf(currentAlbum) === "podcasts") {
|
||||
const target = Math.min(
|
||||
state?.duration ?? 0,
|
||||
(state?.position ?? 0) + PODCAST_SKIP_SECONDS,
|
||||
);
|
||||
connection.optimistic({ position: target });
|
||||
void api.seek(target);
|
||||
skipPodcast(1);
|
||||
} else {
|
||||
connection.optimistic({ position: 0 });
|
||||
void api.next();
|
||||
@@ -232,9 +254,7 @@ export function App() {
|
||||
case "previous":
|
||||
playPop(260);
|
||||
if (currentAlbum && groupOf(currentAlbum) === "podcasts") {
|
||||
const target = Math.max(0, (state?.position ?? 0) - PODCAST_SKIP_SECONDS);
|
||||
connection.optimistic({ position: target });
|
||||
void api.seek(target);
|
||||
skipPodcast(-1);
|
||||
} else {
|
||||
connection.optimistic({ position: 0 });
|
||||
void api.previous();
|
||||
@@ -249,8 +269,7 @@ export function App() {
|
||||
0,
|
||||
Math.min(state.duration, (state?.position ?? 0) + action.delta),
|
||||
);
|
||||
connection.optimistic({ position: target });
|
||||
void api.seek(target);
|
||||
seekTo(target);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -258,12 +277,23 @@ export function App() {
|
||||
playPop(action.freq);
|
||||
break;
|
||||
case "mute":
|
||||
setVolume(state && state.volume > 0 ? 0 : UNMUTE_PERCENT);
|
||||
onMute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
[assignCurrentTo, connection, currentAlbum, play, setVolume, state, toggle],
|
||||
[
|
||||
assignCurrentTo,
|
||||
connection,
|
||||
currentAlbum,
|
||||
onMute,
|
||||
play,
|
||||
seekTo,
|
||||
setVolume,
|
||||
skipPodcast,
|
||||
state,
|
||||
toggle,
|
||||
],
|
||||
);
|
||||
|
||||
// Held in a ref so the listener is installed once rather than on every state change.
|
||||
@@ -356,11 +386,8 @@ export function App() {
|
||||
};
|
||||
|
||||
const onPlaySong = (hit: SongHit) => play(hit.album.id, hit.index);
|
||||
const onSeek = (target: number) => {
|
||||
connection.optimistic({ position: target });
|
||||
void api.seek(target);
|
||||
};
|
||||
const onMute = () => setVolume(state && state.volume > 0 ? 0 : UNMUTE_PERCENT);
|
||||
const onNext = useCallback(() => run([{ type: "next" }]), [run]);
|
||||
const onPrevious = useCallback(() => run([{ type: "previous" }]), [run]);
|
||||
|
||||
// The single top-left "back" button, shared by every level of the music page -
|
||||
// search, categories, play view - so it always means "one step out" no matter what
|
||||
@@ -425,9 +452,9 @@ export function App() {
|
||||
state={state}
|
||||
album={currentAlbum}
|
||||
onToggle={toggle}
|
||||
onNext={() => run([{ type: "next" }])}
|
||||
onPrevious={() => run([{ type: "previous" }])}
|
||||
onSeek={onSeek}
|
||||
onNext={onNext}
|
||||
onPrevious={onPrevious}
|
||||
onSeek={seekTo}
|
||||
onVolume={setVolume}
|
||||
onMute={onMute}
|
||||
onOpenAlbum={onOpenCurrentAlbum}
|
||||
@@ -514,9 +541,9 @@ export function App() {
|
||||
state={state}
|
||||
album={currentAlbum}
|
||||
onToggle={toggle}
|
||||
onNext={() => run([{ type: "next" }])}
|
||||
onPrevious={() => run([{ type: "previous" }])}
|
||||
onSeek={onSeek}
|
||||
onNext={onNext}
|
||||
onPrevious={onPrevious}
|
||||
onSeek={seekTo}
|
||||
onVolume={setVolume}
|
||||
onMute={onMute}
|
||||
onOpenPlayView={() => setUi((previous) => ({ ...previous, view: "play" }))}
|
||||
|
||||
Reference in New Issue
Block a user