Refactor how tracks are listed so we can support ellipsis

This commit is contained in:
Jordan Eldredge 2017-11-19 14:06:30 -08:00
parent a5c7fbc90a
commit 943cf47caa
6 changed files with 115 additions and 81 deletions

View file

@ -50,52 +50,32 @@
#winamp2-js .playlist-middle-center {
flex-grow: 1;
padding: 3px 0;
min-width: 0; /* Not sure why this is needed */
}
#winamp2-js .playlist-tracks {
overflow: hidden;
height: 100%;
}
#winamp2-js .playlist-tracks > div {
width: 100%;
display: table;
}
#winamp2-js .playlist-track {
display: flex;
flex: 1 0 auto;
font-size: 9px;
display: table-row;
line-height: 13px;
letter-spacing: 0.5px;
user-select: none;
}
#winamp2-js .playlist-track-number {
text-align: right;
#winamp2-js .playlist-track-numbers > div,
#winamp2-js .playlist-track-durations > div {
padding-right: 3px;
display: table-cell;
width: 1%;
white-space: nowrap;
}
#winamp2-js .playlist-track-title {
flex-grow: 1;
display: table-cell;
width: auto;
#winamp2-js .playlist-track-titles {
flex: 1 1 auto;
overflow: hidden;
}
#winamp2-js .playlist-track-title > span {
#winamp2-js .playlist-track-titles > div {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
display: block;
text-overflow: ellipsis;
position: relative;
}
#winamp2-js .playlist-track-duration {
padding-right: 3px;
display: table-cell;
width: 1%;
white-space: nowrap;
}
#winamp2-js .playlist-middle-right {

View file

@ -1,24 +1,21 @@
import React from "react";
import { connect } from "react-redux";
import classnames from "classnames";
import { getTimeStr } from "../../utils";
import {
CLICKED_TRACK,
CTRL_CLICKED_TRACK,
PLAY_TRACK
} from "../../actionTypes";
const Track = props => {
const TrackCell = props => {
const {
skinPlaylistStyle,
selected,
current,
title,
number,
duration,
clickTrack,
ctrlClickTrack,
playTrack
playTrack,
children
} = props;
const style = {
backgroundColor: selected ? skinPlaylistStyle.selectedbg : null,
@ -26,17 +23,13 @@ const Track = props => {
};
return (
<div
className={classnames("playlist-track", { selected, current })}
className={classnames({ selected, current })}
style={style}
onClick={clickTrack}
onDoubleClick={playTrack}
onContextMenu={ctrlClickTrack}
>
<div className="playlist-track-number">{number}.</div>
<div className="playlist-track-title">
<span>{title}</span>
</div>
<div className="playlist-track-duration">{getTimeStr(duration)}</div>
{children}
</div>
);
};
@ -51,8 +44,6 @@ const mapStateToProps = (state, ownProps) => {
return {
skinPlaylistStyle,
selected: track.selected,
title: track.title,
duration: track.duration,
current: currentTrack === ownProps.id
};
};
@ -70,4 +61,4 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
playTrack: () => dispatch({ type: PLAY_TRACK, id: ownProps.id })
});
export default connect(mapStateToProps, mapDispatchToProps)(Track);
export default connect(mapStateToProps, mapDispatchToProps)(TrackCell);

View file

@ -0,0 +1,44 @@
import React from "react";
import { connect } from "react-redux";
import { getTimeStr } from "../../utils";
import { getVisibleTrackIds } from "../../selectors";
import TrackCell from "./TrackCell";
const TrackList = props => {
const { trackIds, tracks } = props;
return (
<div className="playlist-tracks">
<div className="playlist-track-numbers">
{trackIds.map((id, i) => (
<TrackCell key={id} id={id}>
{`${i + 1}.`}
</TrackCell>
))}
</div>
<div className="playlist-track-titles">
{trackIds.map(id => (
<TrackCell key={id} id={id}>
{tracks[id].title}
</TrackCell>
))}
</div>
<div className="playlist-track-durations">
{trackIds.map(id => (
<TrackCell key={id} id={id}>
{getTimeStr(tracks[id].duration)}
</TrackCell>
))}
</div>
</div>
);
};
const mapDispatchToProps = () => ({});
const mapStateToProps = state => ({
trackIds: getVisibleTrackIds(state),
tracks: state.tracks
});
export default connect(mapStateToProps, mapDispatchToProps)(TrackList);

View file

@ -52,7 +52,15 @@ exports[`PlaylistWindow renders to snapshot 1`] = `
<div
className="playlist-tracks"
>
<div />
<div
className="playlist-track-numbers"
/>
<div
className="playlist-track-titles"
/>
<div
className="playlist-track-durations"
/>
</div>
</div>
<div

View file

@ -6,7 +6,6 @@ import Slider from "rc-slider/lib/Slider";
import DropTarget from "../DropTarget";
import MiniTime from "../MiniTime";
import PlaylistShade from "./PlaylistShade";
import Track from "./Track";
import AddMenu from "./AddMenu";
import RemoveMenu from "./RemoveMenu";
import SelectionMenu from "./SelectionMenu";
@ -14,7 +13,7 @@ import MiscMenu from "./MiscMenu";
import ListMenu from "./ListMenu";
import ResizeTarget from "./ResizeTarget";
import RunningTimeDisplay from "./RunningTimeDisplay";
import { percentToIndex } from "../../utils";
import TrackList from "./TrackList";
import {
WINDOWS,
PLAYLIST_RESIZE_SEGMENT_WIDTH,
@ -27,7 +26,7 @@ import {
SET_FOCUSED_WINDOW,
SET_PLAYLIST_SCROLL_POSITION
} from "../../actionTypes";
import { getOrderedTracks } from "../../selectors";
import { getVisibleTrackIds } from "../../selectors";
import {
play,
pause,
@ -38,7 +37,6 @@ import {
import "../../../css/playlist-window.css";
const TRACK_HEIGHT = 13;
const MIN_WINDOW_HEIGHT = 116;
const Handle = () => <div className="playlist-scrollbar-handle" />;
@ -50,12 +48,13 @@ const PlaylistWindow = props => {
focused,
playlistScrollPosition,
setPlaylistScrollPosition,
trackOrder,
playlistSize,
playlistShade,
close,
toggleShade
toggleShade,
allTracksAreVisible
} = props;
console.log({ allTracksAreVisible });
if (playlistShade) {
return <PlaylistShade />;
}
@ -75,22 +74,6 @@ const PlaylistWindow = props => {
wide: playlistSize[0] > 2
});
const BASE_WINDOW_HEIGHT = 52;
const numberOfVisibleTracks = Math.floor(
(BASE_WINDOW_HEIGHT + PLAYLIST_RESIZE_SEGMENT_HEIGHT * playlistSize[1]) /
TRACK_HEIGHT
);
const overflowTracks = Math.max(0, trackOrder.length - numberOfVisibleTracks);
const offset = percentToIndex(
playlistScrollPosition / 100,
overflowTracks + 1
);
// Ugh. By not rendering some tracks, we can end up in a situation where
// scrolling causes the number of digits in the tracks to go up, thus causing
// a horizontal jump.
const tracks = trackOrder.slice(offset, offset + numberOfVisibleTracks);
return (
<DropTarget
id="playlist-window"
@ -109,13 +92,7 @@ const PlaylistWindow = props => {
<div className="playlist-middle draggable">
<div className="playlist-middle-left draggable" />
<div className="playlist-middle-center">
<div className="playlist-tracks">
<div>
{tracks.map((id, i) => (
<Track number={i + 1 + offset} id={id} key={id} />
))}
</div>
</div>
<TrackList />
</div>
<div className="playlist-middle-right draggable">
<Slider
@ -128,7 +105,7 @@ const PlaylistWindow = props => {
onChange={setPlaylistScrollPosition}
vertical
handle={Handle}
disabled={overflowTracks === 0}
disabled={allTracksAreVisible}
/>
</div>
</div>
@ -177,6 +154,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
stop: () => dispatch(stop()),
openFileDialog: () => dispatch(openFileDialog(ownProps.fileInput)),
setPlaylistScrollPosition: position =>
// TODO: Move this to an action creator so we can see if this is actually changing
dispatch({ type: SET_PLAYLIST_SCROLL_POSITION, position: 100 - position }),
close: () => dispatch({ type: TOGGLE_PLAYLIST_WINDOW }),
toggleShade: () => dispatch({ type: TOGGLE_PLAYLIST_SHADE_MODE }),
@ -192,16 +170,18 @@ const mapStateToProps = state => {
playlistSize,
playlistShade
},
media: { duration }
media: { duration },
playlist: { trackOrder }
} = state;
return {
focused,
skinPlaylistStyle,
playlistScrollPosition,
playlistSize,
playlistShade,
trackOrder: getOrderedTracks(state),
duration
duration,
allTracksAreVisible: getVisibleTrackIds(state).length === trackOrder.length
};
};

View file

@ -1,5 +1,5 @@
import { denormalize, getTimeStr, clamp } from "./utils";
import { BANDS } from "./constants";
import { denormalize, getTimeStr, clamp, percentToIndex } from "./utils";
import { BANDS, PLAYLIST_RESIZE_SEGMENT_HEIGHT } from "./constants";
import { createSelector } from "reselect";
export const getEqfData = state => {
@ -87,3 +87,34 @@ export const nextTrack = (state, n = 1) => {
nextIndex = clamp(nextIndex, 0, trackOrder.length - 1);
return trackOrder[nextIndex];
};
export const getPlaylistScrollPosition = state =>
state.display.playlistScrollPosition;
const TRACK_HEIGHT = 13;
const BASE_WINDOW_HEIGHT = 52;
export const getNumberOfVisibleTracks = state => {
const { playlistSize } = state.display;
return Math.floor(
(BASE_WINDOW_HEIGHT + PLAYLIST_RESIZE_SEGMENT_HEIGHT * playlistSize[1]) /
TRACK_HEIGHT
);
};
export const getOverflowTrackCount = createSelector(
getTrackOrder,
getNumberOfVisibleTracks,
(trackOrder, numberOfVisibleTracks) =>
Math.max(0, trackOrder.length - numberOfVisibleTracks)
);
export const getVisibleTrackIds = createSelector(
getPlaylistScrollPosition,
getTrackOrder,
getNumberOfVisibleTracks,
(playlistScrollPosition, trackOrder, numberOfVisibleTracks) => {
const overflow = Math.max(0, trackOrder.length - numberOfVisibleTracks);
const offset = percentToIndex(playlistScrollPosition / 100, overflow + 1);
return trackOrder.slice(offset, offset + numberOfVisibleTracks);
}
);