Remove LibraryTable

This commit is contained in:
Jordan Eldredge 2019-12-05 23:29:00 -08:00
parent 230980bec0
commit 9b3cedafc2
9 changed files with 3 additions and 351 deletions

View file

@ -1,17 +0,0 @@
import * as React from "react";
import LibraryTable from "./LibraryTable";
const AlbumsTable = React.memo(() => {
return (
<LibraryTable
headings={["Album", "Tracks"]}
rows={[
["All (1 album)", "1"],
["Ben Mason", "1"],
]}
widths={[50, 200]}
/>
);
});
export default AlbumsTable;

View file

@ -1,19 +0,0 @@
import * as React from "react";
import LibraryTable from "./LibraryTable";
interface Props {}
export default class ArtistsTable extends React.Component<Props> {
render() {
return (
<LibraryTable
headings={["Album", "Tracks", "Other"]}
rows={[
["All (1 album)", "1", "1"],
["Ben Mason", "1", "1"],
]}
widths={[100, 150, 200]}
/>
);
}
}

View file

@ -1,72 +0,0 @@
import React, { useState } from "react";
import classnames from "classnames";
interface Props {
headings: Array<string>;
rows: Array<Array<any>>;
widths: Array<number>;
}
export default function LibraryTable(props: Props) {
const [selectedRow, setSelectedRow] = useState<number | null>(null);
const rowStyle = {
display: "grid",
gridTemplateColumns: props.widths.map(width => `${width}px`).join(" "),
};
return (
<div
className="webamp-media-library-item"
style={{ height: "100%", position: "relative" }}
>
<div
className="webamp-media-library-table"
style={{
overflow: "scroll",
height: "100%",
}}
>
<div style={rowStyle} className="library-table-heading">
{props.headings.map((heading, i) => (
<div key={`heading-${i}-${heading}`} style={{ paddingLeft: 5 }}>
{heading}
</div>
))}
</div>
{props.rows.map((row, i) => (
<div
style={{
...rowStyle,
boxSizing: "border-box",
}}
className={classnames("library-table-row", {
selected: i === selectedRow,
})}
onClick={() => setSelectedRow(i)}
key={`row-${i}`}
>
{row.map((text, j) => (
<div
style={{ overflow: "hidden", paddingLeft: 6 }}
key={`cell-${j}`}
>
{text}
</div>
))}
</div>
))}
<div
style={{
zIndex: 99999,
color: "white",
width: 1,
position: "absolute",
left: 50,
top: 0,
height: "100%",
}}
/>
</div>
</div>
);
}

View file

@ -1,93 +0,0 @@
import * as React from "react";
import { connect } from "react-redux";
import * as Selectors from "../../selectors";
import { AppState, PlaylistTrack } from "../../types";
import * as Utils from "../../utils";
import * as FileUtils from "../../fileUtils";
import LibraryTable from "./LibraryTable";
interface StateProps {
tracks: PlaylistTrack[];
filterTracks: (query: string) => PlaylistTrack[];
}
interface State {
filter: string;
}
class TracksTable extends React.Component<StateProps, State> {
constructor(props: StateProps) {
super(props);
this.state = { filter: "" };
}
render() {
return (
<div
style={{
display: "flex",
flexDirection: "column",
height: "100%",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
height: 23,
}}
>
<span>Search:</span>
<input
style={{ marginLeft: 12, flexGrow: 1 }}
type="text"
className="webamp-media-library-item"
onChange={e => this.setState({ filter: e.target.value })}
/>
</div>
<LibraryTable
headings={[
"Artist",
"Title",
"Album",
"Length",
"Track #",
"Genere",
"Year",
"Filename",
]}
rows={this.props.filterTracks(this.state.filter).map(track => {
return [
track.artist,
track.title,
track.album,
Utils.getTimeStr(track.duration),
1,
"Primus",
2001,
track.url == null
? track.defaultName
: FileUtils.filenameFromUrl(track.url),
];
})}
widths={[100, 100, 100, 100, 100, 100, 100, 100]}
/>
<div style={{ marginTop: 2 }}>
<span id="webamp-media-library-track-summary-duration">
1 item [3:25]
</span>
</div>
</div>
);
}
}
const mapStateToProps = (state: AppState): StateProps => {
return {
tracks: Object.values(Selectors.getTracks(state)),
filterTracks: Selectors.getTracksMatchingFilter(state),
};
};
export default connect(mapStateToProps)(TracksTable);

View file

@ -1,7 +1,4 @@
import * as React from "react";
import ArtistsTable from "./ArtistsTable";
import AlbumsTable from "./AlbumsTable";
import TracksTable from "./TracksTable";
import LibraryLayout from "./LibraryLayout";
export default class MediaLibraryWindow extends React.Component<{}> {
@ -9,9 +6,9 @@ export default class MediaLibraryWindow extends React.Component<{}> {
return (
<LibraryLayout
sidebar={null}
artists={<ArtistsTable />}
albums={<AlbumsTable />}
tracks={<TracksTable />}
artists={null}
albums={null}
tracks={null}
/>
);
}

View file

@ -30,7 +30,6 @@ import * as fromDisplay from "./reducers/display";
import * as fromEqualizer from "./reducers/equalizer";
import * as fromMedia from "./reducers/media";
import * as fromWindows from "./reducers/windows";
import * as TrackUtils from "./trackUtils";
import * as MarqueeUtils from "./marqueeUtils";
import { generateGraph } from "./resizeUtils";
import { SerializedStateV1 } from "./serializedStates/v1Types";
@ -53,17 +52,6 @@ export const getEqfData = createSelector(getSliders, sliders => {
});
export const getTracks = (state: AppState) => state.tracks;
export const getTracksMatchingFilter = createSelector(getTracks, tracks => {
const tracksArray = Object.values(tracks);
const filter = Utils.makeCachingFilterFunction(tracksArray, (track, query) =>
TrackUtils.trackFilterContents(track).includes(query)
);
return (filterString: string): PlaylistTrack[] => {
return filter(filterString.toLowerCase());
};
});
export const getTrackUrl = (state: AppState) => {
return (id: number): string | null => {
return state.tracks[id]?.url;

View file

@ -35,12 +35,3 @@ export const trackFilename = Utils.weakMapMemoize(
return "???";
}
);
export const trackFilterContents = Utils.weakMapMemoize(
(track: PlaylistTrack): string => {
return [track.artist, track.title, track.defaultName]
.filter(Boolean)
.join("|")
.toLowerCase();
}
);

View file

@ -11,7 +11,6 @@ import {
segment,
moveSelected,
spliceIn,
makeCachingFilterFunction,
replaceAtIndex,
} from "./utils";
@ -381,92 +380,6 @@ describe("spliceIn", () => {
});
});
describe("makeCachingFilterFunction", () => {
test("caches exact queries", () => {
const values = ["abc", "b", "c"];
const includes = jest.fn((v, query) => v.includes(query));
const filter = makeCachingFilterFunction(values, includes);
expect(filter("c")).toEqual(["abc", "c"]);
expect(includes.mock.calls.length).toBe(3);
expect(filter("c")).toEqual(["abc", "c"]);
expect(includes.mock.calls.length).toBe(3);
});
test("caches sub queries", () => {
const values = ["a--", "ab-", "abc"];
const includes = jest.fn((v, query) => v.includes(query));
let comparisons = 0;
const newComparisons = () => {
const recent = includes.mock.calls.length - comparisons;
comparisons += recent;
return recent;
};
const filter = makeCachingFilterFunction(values, includes);
// Intial search
expect(filter("ab")).toEqual(["ab-", "abc"]);
expect(newComparisons()).toBe(3); // Looks at all elements
// Second search where original search is a prefix
expect(filter("abc")).toEqual(["abc"]);
expect(newComparisons()).toBe(2); // Only reconsiders the previous matches
// Unique search
expect(filter("b")).toEqual(["ab-", "abc"]); // Looks at all elements
expect(newComparisons()).toBe(3); // Reconsiders all elements
expect(filter("bc")).toEqual(["abc"]); // Only reconsidres the matches that already include `b`
expect(newComparisons()).toBe(2);
// Go back to the initial serach
expect(filter("ab")).toEqual(["ab-", "abc"]);
expect(newComparisons()).toBe(0); // Result is cached
// A variation on the second search
expect(filter("abcd")).toEqual([]);
expect(newComparisons()).toBe(1); // Only recondsiders the results of `abc`
});
test("big data", () => {
const values = [...Array(10000)].map((val, i) => String(i));
const includes = jest.fn((v, query) => v.includes(query));
let comparisons = 0;
const newComparisons = () => {
const recent = includes.mock.calls.length - comparisons;
comparisons += recent;
return recent;
};
const filter = makeCachingFilterFunction(values, includes);
// Intial search
expect(filter("").length).toEqual(10000);
expect(newComparisons()).toBe(0); // Looks at zero
expect(filter("1").length).toEqual(3439);
expect(newComparisons()).toBe(10000); // Looks at all elements
expect(filter("12").length).toEqual(299);
expect(newComparisons()).toBe(3439);
expect(filter("123").length).toEqual(20);
expect(newComparisons()).toBe(299);
expect(filter("1234").length).toEqual(1);
expect(newComparisons()).toBe(20);
expect(filter("12345").length).toEqual(0);
expect(newComparisons()).toBe(1);
// A variation on the initial non-empty query
expect(filter("11").length).toEqual(280);
expect(newComparisons()).toBe(3439);
expect(filter("111").length).toEqual(19);
expect(newComparisons()).toBe(280);
expect(filter("1111").length).toEqual(1);
expect(newComparisons()).toBe(19);
});
});
describe("replaceAtIndex", () => {
test("can replace", () => {
expect(replaceAtIndex([1, 2, 3, 4], 2, 0)).toEqual([1, 2, 0, 4]);

View file

@ -403,39 +403,3 @@ export function weakMapMemoize<T extends object, R>(
return cache.get(value);
};
}
interface Cache<V> {
results?: V[];
subCaches: { [char: string]: Cache<V> };
}
// Is this a premature optimizaiton? Probably. But it's my side-project so I can
// do what I like. :P
export function makeCachingFilterFunction<V>(
values: V[],
includes: (v: V, query: string) => boolean
) {
const cache: Cache<V> = {
results: values,
subCaches: {},
};
return (query: string): V[] => {
let queryCache: Cache<V> = cache;
let lastResults: V[] = values;
for (const char of query) {
let letterCaches = queryCache.subCaches[char];
if (!letterCaches) {
letterCaches = queryCache.subCaches[char] = { subCaches: {} };
} else if (letterCaches.results) {
lastResults = letterCaches.results;
}
queryCache = letterCaches;
}
if (!queryCache.results) {
queryCache.results = lastResults.filter(v => includes(v, query));
}
return queryCache.results;
};
}