mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-22 17:47:16 +00:00
Remove LibraryTable
This commit is contained in:
parent
230980bec0
commit
9b3cedafc2
9 changed files with 3 additions and 351 deletions
|
|
@ -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;
|
||||
|
|
@ -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]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
36
js/utils.ts
36
js/utils.ts
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue