Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | import React, { useEffect, useLayoutEffect, useRef } from "react"; import API from "renderer/lib/api"; import { useAppSelector } from "store/hooks"; import { TRACKER_CELL_HEIGHT, TRACKER_HEADER_HEIGHT } from "./helpers"; import { TRACKER_PATTERN_LENGTH } from "consts"; interface SongTrackerPlaybackControllerProps { scrollRef: React.RefObject<HTMLDivElement | null>; sequenceLength: number; } const getPlaybackRowCellId = (sequenceId: number, rowId: number) => `tracker_playhead_${sequenceId}_${rowId}`; export const SongTrackerPlaybackController = ({ scrollRef, sequenceLength, }: SongTrackerPlaybackControllerProps) => { const playing = useAppSelector((state) => state.tracker.playing); const playbackSequence = useAppSelector( (state) => state.tracker.playbackSequence, ); const playbackRow = useAppSelector((state) => state.tracker.playbackRow); const previousPlaybackCellRef = useRef<HTMLElement | null>(null); const scrollRafRef = useRef<number | undefined>(undefined); useEffect(() => { return () => { previousPlaybackCellRef.current?.removeAttribute("data-playing"); Iif (scrollRafRef.current !== undefined) { cancelAnimationFrame(scrollRafRef.current); } }; }, []); useEffect(() => { Iif (playbackSequence >= sequenceLength) { API.music.sendToMusicWindow({ action: "position", position: { sequence: 0, row: 0 }, }); } }, [playbackSequence, sequenceLength]); useLayoutEffect(() => { previousPlaybackCellRef.current?.removeAttribute("data-playing"); const nextPlaybackCell = document.getElementById( getPlaybackRowCellId(playbackSequence, playbackRow), ); if (nextPlaybackCell instanceof HTMLElement) { nextPlaybackCell.setAttribute("data-playing", "true"); previousPlaybackCellRef.current = nextPlaybackCell; } else { previousPlaybackCellRef.current = null; } }, [playbackRow, playbackSequence]); useLayoutEffect(() => { Iif (!playing) { return; } const scrollEl = scrollRef.current; Iif (!scrollEl) { return; } const patternHeight = TRACKER_HEADER_HEIGHT + TRACKER_CELL_HEIGHT * TRACKER_PATTERN_LENGTH; const playheadTop = playbackSequence * patternHeight + TRACKER_HEADER_HEIGHT + playbackRow * TRACKER_CELL_HEIGHT; const viewportHeight = scrollEl.clientHeight; const scrollHeight = sequenceLength * patternHeight + TRACKER_HEADER_HEIGHT; const maxScrollTop = Math.max(0, scrollHeight - viewportHeight); const nextScrollTop = Math.max( 0, Math.min(playheadTop - viewportHeight * 0.5, maxScrollTop), ); Iif (scrollRafRef.current !== undefined) { cancelAnimationFrame(scrollRafRef.current); } scrollRafRef.current = requestAnimationFrame(() => { scrollEl.scrollTop = nextScrollTop; }); }, [playbackRow, playbackSequence, playing, scrollRef, sequenceLength]); return null; }; |