diff --git a/seiri-client-internals/public/index.html b/seiri-client-internals/public/index.html index 895bfad..c11bb93 100644 --- a/seiri-client-internals/public/index.html +++ b/seiri-client-internals/public/index.html @@ -21,7 +21,7 @@ --> 聲理 — seiri - + diff --git a/seiri-client-internals/src/TrackTable.tsx b/seiri-client-internals/src/TrackTable.tsx index 875ee8a..c6babdf 100644 --- a/seiri-client-internals/src/TrackTable.tsx +++ b/seiri-client-internals/src/TrackTable.tsx @@ -1,12 +1,11 @@ import React from "react"; -import { orderBy as _, range } from "lodash"; +import { orderBy as _, range, debounce } from "lodash"; import Mousetrap from "mousetrap"; import 'mousetrap-global-bind'; import { DraggableData, DraggableCore } from "react-draggable"; import { Dispatch } from "redux"; import { - Column, RowMouseEventHandlerParams, SortDirection, @@ -15,12 +14,14 @@ import { Table, TableCellRenderer, TableHeaderProps, - WindowScroller + WindowScroller, + WindowScrollerChildProps } from "react-virtualized"; import "react-virtualized/styles.css"; // only needs to be imported once import { updateSelectedCount, updateTracksTick } from "./actions"; import "./Table.css"; import { Track, TrackFileType } from "./types"; +import { fileTypeString, msToTime } from "./utility"; const toLodashDirection = (direction: SortDirectionType) => { switch (direction) { @@ -46,21 +47,22 @@ interface TrackTableState { selected: boolean[] | undefined; cursor: number | undefined; pivot: number | undefined; - prevTrackLength?: number, - prevQuery?: string, + prevTrackLength?: number; + prevQuery?: string; + scrollToRow?: number } const TOTAL_WIDTH = 3000; // tslint:disable:jsx-no-lambda class TrackTable extends React.Component { - tableRef: React.RefObject; + tableRef: React.RefObject; constructor(props: TrackTableProps) { super(props); const sortBy = "updated"; const sortDirection = SortDirection.DESC; - this.tableRef = React.createRef
(); + this.tableRef = React.createRef(); this.state = { // tslint:disable:object-literal-sort-keys widths: { @@ -88,8 +90,41 @@ class TrackTable extends React.Component { cursor: undefined, pivot: undefined, }; - window.addEventListener("keydown", event => { + const keyDown = debounce(this.keyDownEventHandler).bind(this) + window.addEventListener("keydown", (event) => { + if (event.key === "ArrowUp" || event.key === "ArrowDown") { + event.preventDefault(); + keyDown(event); + } + }); + Mousetrap.bindGlobal(['command+r', 'ctrl+r'], () => { + const tracksToRefresh = this.state.sortedList.filter( + (_track, index) => this.state.selected?.[index] === true + ).map(track => track.filePath); + + window.seiri.refreshTracks(tracksToRefresh) + // tslint:disable-next-line:no-console + // console.log("REFRESHED!"); + // tslint:disable-next-line:no-console + // console.log(tracksToRefresh); + this.setState(this.asSelected([], undefined, undefined)); + this.props.dispatch?.(updateTracksTick.action({})); + return false; + }); + this.rowClassName = this.rowClassName.bind(this); + this.handleClick = this.handleClick.bind(this); + this.handleDoubleClick = this.handleDoubleClick.bind(this); + this.sort = this.sort.bind(this); + this.rowGetter = this.rowGetter.bind(this); + this.albumArtistCellRenderer = this.albumArtistCellRenderer.bind(this); + this.durationCellRenderer = this.durationCellRenderer.bind(this); + this.fileTypeCellRenderer = this.fileTypeCellRenderer.bind(this); + this.hasCoverArtCellRenderer = this.hasCoverArtCellRenderer.bind(this); + } + + keyDownEventHandler(event: KeyboardEvent) { if (event.key === "ArrowUp" || event.key === "ArrowDown") { + event.preventDefault(); let newSelected = this.state.cursor if (newSelected === undefined) { newSelected = 0 @@ -101,7 +136,6 @@ class TrackTable extends React.Component { if (newSelected >= this.props.tracks.length) newSelected = this.props.tracks.length - 1; } - if (event.shiftKey) { // everything between the cursor and the pivot is selected. let newSelectionKeys = []; @@ -118,71 +152,60 @@ class TrackTable extends React.Component { selected[key] = true; } - this.setState(this.asSelected(selected, newSelected, this.state.pivot ?? newSelected)); + this.setState({ + scrollToRow: newSelected, + ...this.asSelected(selected, newSelected, this.state.pivot ?? newSelected) + }); return; } else if (event.ctrlKey) { - this.setState({ cursor: newSelected }); - this.tableRef?.current?.scrollToPosition(newSelected); + this.setState({ scrollToRow: newSelected, cursor: newSelected }); + } else { const clearState = []; clearState[newSelected] = true; - this.setState(this.asSelected(clearState, newSelected, newSelected)); - this.tableRef?.current?.scrollToPosition(newSelected); + this.setState({scrollToRow: newSelected, ...this.asSelected(clearState, newSelected, newSelected)}); } } - - }); - Mousetrap.bindGlobal(['command+r', 'ctrl+r'], () => { - const tracksToRefresh = this.state.sortedList.filter( - (_track, index) => this.state.selected?.[index] === true - ).map(track => track.filePath); - - window.seiri.refreshTracks(tracksToRefresh) - // tslint:disable-next-line:no-console - // console.log("REFRESHED!"); - // tslint:disable-next-line:no-console - // console.log(tracksToRefresh); - this.setState(this.asSelected([], undefined, undefined)); - this.props.dispatch?.(updateTracksTick.action({})); - return false; - }); - this.rowClassName = this.rowClassName.bind(this); - this.handleClick = this.handleClick.bind(this); - this.handleDoubleClick = this.handleDoubleClick.bind(this); - this.sort = this.sort.bind(this); - this.rowGetter = this.rowGetter.bind(this); - this.albumArtistCellRenderer = this.albumArtistCellRenderer.bind(this); - this.durationCellRenderer = this.durationCellRenderer.bind(this); - this.fileTypeCellRenderer = this.fileTypeCellRenderer.bind(this); - this.hasCoverArtCellRenderer = this.hasCoverArtCellRenderer.bind(this); } - asSelected(selected: boolean[], cursor: number | undefined, pivot: number | undefined) { - this.props.dispatch?.(updateSelectedCount({ count: selected.filter(s => s).length ?? 0 })); return { selected, cursor, pivot } } - static getDerivedStateFromProps(newProps: TrackTableProps, prevState: TrackTableState) - { - const { sortBy, sortDirection, prevQuery, prevTrackLength } = prevState; - - // Need this so setting selected stuff actually works - if (newProps.query !== prevQuery || newProps.tracks.length !== prevTrackLength) { - newProps.dispatch?.(updateSelectedCount({ count: 0 })) - return { + UNSAFE_componentWillReceiveProps(newProps: TrackTableProps) { + const { sortBy, sortDirection } = this.state; + if (newProps.query !== this.props.query || newProps.tracks.length !== this.props.tracks.length) { + this.setState({ sortedList: TrackTable.sortList({ list: newProps.tracks, sortBy, sortDirection }), selected: [], - cursor: undefined, + cursor: undefined, pivot: undefined, - prevQuery: newProps.query, - prevTrackLength: newProps.tracks.length - } + }); } else { - return { sortedList: TrackTable.sortList({ list: newProps.tracks, sortBy, sortDirection }) } + this.setState({ sortedList: TrackTable.sortList({ list: newProps.tracks, sortBy, sortDirection }) }); } } + // static getDerivedStateFromProps(newProps: TrackTableProps, prevState: TrackTableState) + // { + // const { sortBy, sortDirection, prevQuery, prevTrackLength } = prevState; + + // // Need this so setting selected stuff actually works + // if (newProps.query !== prevQuery || newProps.tracks.length !== prevTrackLength) { + // newProps.dispatch?.(updateSelectedCount({ count: 0 })) + // return { + // sortedList: TrackTable.sortList({ list: newProps.tracks, sortBy, sortDirection }), + // selected: [], + // cursor: undefined, + // pivot: undefined, + // prevQuery: newProps.query, + // prevTrackLength: newProps.tracks.length + // } + // } else { + // return { sortedList: TrackTable.sortList({ list: newProps.tracks, sortBy, sortDirection }) } + // } + // } + rowClassName({ index }: { index: number }) { if (index < 0) { return "table-row table-header"; @@ -204,6 +227,10 @@ class TrackTable extends React.Component { } return tableRowClass; } + + UNSAFE_componentWillUpdate(nextProps: TrackTableProps, nextState: TrackTableState) { + this.props.dispatch?.(updateSelectedCount({ count: nextState.selected?.filter(s => s).length ?? 0 })); + } rowGetter = ({ index }: { index: number }) => this.getDatum(this.state.sortedList, index); @@ -302,67 +329,6 @@ class TrackTable extends React.Component { }); }; - msToTime(ms: number) { - const minutes = Math.floor(ms / 60000); - const seconds = ((ms % 60000) / 1000).toFixed(0); - return minutes + ":" + (Number(seconds) < 10 ? "0" : "") + seconds; - } - - fileTypeString(fileType: TrackFileType) { - switch (fileType) { - case TrackFileType.FLAC: - return "FLAC"; - case TrackFileType.FLAC4: - return "FLAC (4-bit)"; - case TrackFileType.FLAC8: - return "FLAC (8-bit)"; - case TrackFileType.FLAC16: - return "FLAC (16-bit)"; - case TrackFileType.FLAC24: - return "FLAC (24-bit Hi-Res)"; - case TrackFileType.FLAC32: - return "FLAC (32-bit Integral)"; - case TrackFileType.MP3CBR: - return "MP3 (Constant Bitrate)"; - case TrackFileType.MP3VBR: - return "MP3 (Variable Bitrate)"; - case TrackFileType.AAC: - return "AAC (M4A Audio)"; - case TrackFileType.ALAC: - return "Apple Lossless"; - case TrackFileType.ALAC16: - return "Apple Lossless (16-bit)"; - case TrackFileType.ALAC24: - return "Apple Lossless (24-bit Hi-Res)"; - case TrackFileType.AIFF: - return "AIFF (PCM Audio)"; - case TrackFileType.AIFF4: - return "AIFF (4-bit PCM)" - case TrackFileType.AIFF8: - return "AIFF (8-bit PCM)" - case TrackFileType.AIFF16: - return "AIFF (16-bit PCM)" - case TrackFileType.AIFF24: - return "AIFF (24-bit PCM)" - case TrackFileType.AIFF32: - return "AIFF (32-bit PCM)" - case TrackFileType.Opus: - return "Opus"; - case TrackFileType.Vorbis: - return "Vorbis"; - case TrackFileType.MonkeysAudio: - return "Monkey's Audio"; - case TrackFileType.MonkeysAudio16: - return "Monkey's Audio (16-bit)"; - case TrackFileType.MonkeysAudio24: - return "Monkey's Audio (24-bit)"; - case TrackFileType.Unknown: - return "Unknown"; - default: - return ""; - } - } - handleDoubleClick(event: RowMouseEventHandlerParams) { const track: Track = event.rowData; window.seiri.openTrackFolder(track); @@ -411,21 +377,21 @@ class TrackTable extends React.Component { } albumArtistCellRenderer: TableCellRenderer = ({ cellData }: { cellData?: string[] }) => (cellData || []).join(";") - durationCellRenderer: TableCellRenderer = ({ cellData }: { cellData?: number }) => this.msToTime(cellData ?? 0) - fileTypeCellRenderer: TableCellRenderer = ({ cellData }: { cellData?: TrackFileType }) => this.fileTypeString(cellData ?? TrackFileType.Unknown) + durationCellRenderer: TableCellRenderer = ({ cellData }: { cellData?: number }) => msToTime(cellData ?? 0) + fileTypeCellRenderer: TableCellRenderer = ({ cellData }: { cellData?: TrackFileType }) => fileTypeString(cellData ?? TrackFileType.Unknown) hasCoverArtCellRenderer: TableCellRenderer = ({ cellData }: { cellData?: boolean }) => (cellData ? "Yes" : "No") // tslint:disable-next-line:member-ordering render() { return (
- - {({ height, isScrolling, scrollTop }: { height: number, isScrolling: boolean, scrollTop: number }) => ( + + {({ height, isScrolling, scrollTop, onChildScroll }: Pick) => (
{ render() { return ( -
+