Use CSS Grid for laying out the media library

This commit is contained in:
Jordan Eldredge 2019-03-23 17:37:37 -07:00
parent b5f1ad522e
commit d57e3b3671
3 changed files with 187 additions and 137 deletions

View file

@ -0,0 +1,178 @@
import React, { ReactNode } from "react";
import { connect } from "react-redux";
import GenWindow from "../GenWindow";
import { WINDOWS } from "../../constants";
import { AppState, SkinGenExColors } from "../../types";
import * as Selectors from "../../selectors";
interface StateProps {
skinGenExColors: SkinGenExColors;
}
interface OwnProps {
sidebar: ReactNode;
artists: ReactNode;
albums: ReactNode;
tracks: ReactNode;
}
type Props = StateProps & OwnProps;
interface State {
sidebarWidth: number;
topPlaylistSectionHeight: number;
artistsPanelWidth: number;
}
const DIVIDER_WIDTH = 9;
// TODO: Tune these
const SIDEBAR_MIN = 25;
const SIDEBAR_MAX = 200;
class LibraryLayout extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
sidebarWidth: 100, // Pixels
topPlaylistSectionHeight: 0.25, // Percent
artistsPanelWidth: 0.5, // Percent
};
}
_onMouseMove(cb: (e: MouseEvent) => void) {
const handleMouseUp = () => {
document.removeEventListener("mousemove", cb);
document.removeEventListener("mouseup", handleMouseUp);
};
// TODO: Technically there's a leak here since the component could unmount while we are moving
document.addEventListener("mousemove", cb);
document.addEventListener("mouseup", handleMouseUp);
}
_handleSidebarMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
const { pageX: startX } = e;
const initialWidth = this.state.sidebarWidth;
this._onMouseMove((moveEvent: MouseEvent) => {
let sidebarWidth = initialWidth + moveEvent.pageX - startX;
if (sidebarWidth < SIDEBAR_MIN) {
sidebarWidth = 0;
}
sidebarWidth = Math.min(sidebarWidth, SIDEBAR_MAX);
this.setState({ sidebarWidth });
});
};
_handlePlaylistResizeMouseDown = (
e: React.MouseEvent<HTMLDivElement>,
windowHeight: number
) => {
const { pageY: startY } = e;
const avaliableHeight = windowHeight - DIVIDER_WIDTH;
const initialHeight = avaliableHeight * this.state.topPlaylistSectionHeight;
this._onMouseMove((moveEvent: MouseEvent) => {
const deltaY = moveEvent.pageY - startY;
const topPlaylistSectionPixelHeight = initialHeight + deltaY;
this.setState({
topPlaylistSectionHeight:
topPlaylistSectionPixelHeight / avaliableHeight,
});
});
};
_handleArtistsResizeMouseDown = (
e: React.MouseEvent<HTMLDivElement>,
windowWidth: number
) => {
const { pageX: startX } = e;
const avaliableWidth =
windowWidth - DIVIDER_WIDTH - this.state.sidebarWidth;
const initialWidth = avaliableWidth * this.state.artistsPanelWidth;
this._onMouseMove((moveEvent: MouseEvent) => {
const deltaX = moveEvent.pageX - startX;
const artistsPanelPixelWidth = initialWidth + deltaX;
this.setState({
artistsPanelWidth: artistsPanelPixelWidth / avaliableWidth,
});
});
};
render() {
return (
<GenWindow title={"Winamp Library"} windowId={WINDOWS.MEDIA_LIBRARY}>
{({ width, height }) => (
<div
id="webamp-media-library"
style={{
// TODO: There's probably a better way to fill all avalaible space.
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
display: "grid",
gridTemplateColumns: `${
this.state.sidebarWidth
}px ${DIVIDER_WIDTH}px auto`,
}}
>
{this.state.sidebarWidth === 0 ? <div /> : this.props.sidebar}
<div
className="webamp-media-library-vertical-divider"
onMouseDown={this._handleSidebarMouseDown}
>
<div className="webamp-media-library-vertical-divider-line" />
</div>
<div
style={{
display: "grid",
gridTemplateRows: `${
this.state.topPlaylistSectionHeight
}fr ${DIVIDER_WIDTH}px ${1 -
this.state.topPlaylistSectionHeight}fr`,
}}
>
<div
style={{
display: "grid",
gridTemplateColumns: `${
this.state.artistsPanelWidth
}fr ${DIVIDER_WIDTH}px ${1 - this.state.artistsPanelWidth}fr`,
}}
>
{this.props.artists}
<div
className="webamp-media-library-vertical-divider"
onMouseDown={(e: React.MouseEvent<HTMLDivElement>) =>
this._handleArtistsResizeMouseDown(e, width)
}
>
<div className="webamp-media-library-vertical-divider-line" />
</div>
{this.props.albums}
</div>
<div
className="webamp-media-library-horizontal-divider"
onMouseDown={(e: React.MouseEvent<HTMLDivElement>) =>
this._handlePlaylistResizeMouseDown(e, height)
}
>
<div className="webamp-media-library-horizontal-divider-line" />
</div>
<div style={{ overflow: "hidden" }}>{this.props.tracks}</div>
</div>
</div>
)}
</GenWindow>
);
}
}
const mapStateToProps = (state: AppState): StateProps => {
return {
skinGenExColors: Selectors.getSkinGenExColors(state),
};
};
export default connect(mapStateToProps)(LibraryLayout);

View file

@ -30,6 +30,7 @@ class TracksTable extends React.Component<StateProps, State> {
display: "flex",
flexDirection: "column",
flexGrow: 2,
height: "100%",
}}
>
<div

View file

@ -1,149 +1,20 @@
import * as React from "react";
import { connect } from "react-redux";
import GenWindow from "../GenWindow";
import { WINDOWS } from "../../constants";
import "../../../css/media-library-window.css";
import { AppState, SkinGenExColors } from "../../types";
import * as Selectors from "../../selectors";
import Sidebar from "./Sidebar";
import ArtistsTable from "./ArtistsTable";
import AlbumsTable from "./AlbumsTable";
import TracksTable from "./TracksTable";
import LibraryLayout from "./LibraryLayout";
interface Props {
skinGenExColors: SkinGenExColors;
}
interface State {
sidebarWidth: number;
topPlaylistSectionHeight: number;
artistsPanelWidth: number;
}
class MediaLibraryWindow extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
sidebarWidth: 100,
topPlaylistSectionHeight: 200,
artistsPanelWidth: 150,
};
}
_onMouseMove(cb: (e: MouseEvent) => void) {
const handleMouseUp = () => {
document.removeEventListener("mousemove", cb);
document.removeEventListener("mouseup", handleMouseUp);
};
document.addEventListener("mousemove", cb);
document.addEventListener("mouseup", handleMouseUp);
}
_handleSidebarMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
const { pageX: startX } = e;
const initialWidth = this.state.sidebarWidth;
this._onMouseMove((moveEvent: MouseEvent) => {
this.setState({
sidebarWidth: initialWidth + moveEvent.pageX - startX,
});
});
};
_handlePlaylistResizeMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
const { pageY: startY } = e;
const initialHeight = this.state.topPlaylistSectionHeight;
this._onMouseMove((moveEvent: MouseEvent) => {
this.setState({
topPlaylistSectionHeight: initialHeight + moveEvent.pageY - startY,
});
});
};
_handleArtistsResizeMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
const { pageX: startX } = e;
const initialWidth = this.state.artistsPanelWidth;
this._onMouseMove((moveEvent: MouseEvent) => {
this.setState({
artistsPanelWidth: initialWidth + moveEvent.pageX - startX,
});
});
};
export default class MediaLibraryWindow extends React.Component<{}> {
render() {
return (
<GenWindow title={"Winamp Library"} windowId={WINDOWS.MEDIA_LIBRARY}>
{() => (
<div
id="webamp-media-library"
style={{
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
display: "grid",
gridTemplateColumns: `[sidebar] ${
this.state.sidebarWidth
}px [vert-divider] 9px [media-area] auto`,
overflow: "hidden",
}}
>
<div>
<Sidebar />
</div>
<div
className="webamp-media-library-vertical-divider"
onMouseDown={this._handleSidebarMouseDown}
>
<div className="webamp-media-library-vertical-divider-line" />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
flexShrink: 1,
flexGrow: 1,
// https://stackoverflow.com/a/35609992/1263117
overflow: "hidden",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
flexShrink: 0,
height: this.state.topPlaylistSectionHeight,
}}
>
<div style={{ width: this.state.artistsPanelWidth }}>
<ArtistsTable />
</div>
<div
className="webamp-media-library-vertical-divider"
onMouseDown={this._handleArtistsResizeMouseDown}
>
<div className="webamp-media-library-vertical-divider-line" />
</div>
<AlbumsTable />
</div>
<div
className="webamp-media-library-horizontal-divider"
onMouseDown={this._handlePlaylistResizeMouseDown}
>
<div className="webamp-media-library-horizontal-divider-line" />
</div>
<TracksTable />
</div>
</div>
)}
</GenWindow>
<LibraryLayout
sidebar={<Sidebar />}
artists={<ArtistsTable />}
albums={<AlbumsTable />}
tracks={<TracksTable />}
/>
);
}
}
const mapStateToProps = (state: AppState) => {
return {
skinGenExColors: Selectors.getSkinGenExColors(state),
};
};
export default connect(mapStateToProps)(MediaLibraryWindow);