From b053a83e9eac9e30c6ecfed41db0fb18df66c2de Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 1 Jan 2018 14:27:24 -0800 Subject: [PATCH] Support dragging files in at a given location in the playlist --- js/actionCreators.js | 12 ++-- js/components/PlaylistWindow/index.js | 8 +-- js/reducers/playlist.js | 8 ++- js/reducers/playlist.test.js | 89 ++++++++++++++++++++++++++- 4 files changed, 105 insertions(+), 12 deletions(-) diff --git a/js/actionCreators.js b/js/actionCreators.js index 865a6f65..e9699374 100644 --- a/js/actionCreators.js +++ b/js/actionCreators.js @@ -171,13 +171,13 @@ function setEqFromFile(file) { }; } -function addTracksFromReferences(fileReferences) { +function addTracksFromReferences(fileReferences, atIndex) { return dispatch => { Array.from(fileReferences).forEach((file, i) => { const priority = i === 0 ? "PLAY" : "NONE"; const id = uniqueId(); const url = URL.createObjectURL(file); - dispatch(_addTrackFromUrl(url, file.name, id, priority)); + dispatch(_addTrackFromUrl(url, file.name, id, priority, atIndex + i)); dispatch(fetchMediaTags(file, id)); }); }; @@ -185,7 +185,7 @@ function addTracksFromReferences(fileReferences) { const SKIN_FILENAME_MATCHER = new RegExp("(wsz|zip)$", "i"); const EQF_FILENAME_MATCHER = new RegExp("eqf$", "i"); -export function loadFilesFromReferences(fileReferences) { +export function loadFilesFromReferences(fileReferences, atIndex = null) { return dispatch => { if (fileReferences.length < 1) { return; @@ -201,7 +201,7 @@ export function loadFilesFromReferences(fileReferences) { return; } } - dispatch(addTracksFromReferences(fileReferences)); + dispatch(addTracksFromReferences(fileReferences, atIndex)); }; } @@ -225,9 +225,9 @@ function uniqueId() { return counter++; } -export function _addTrackFromUrl(url, name, id, priority) { +function _addTrackFromUrl(url, name, id, priority, atIndex) { return dispatch => { - dispatch({ type: ADD_TRACK_FROM_URL, url, name, id }); + dispatch({ type: ADD_TRACK_FROM_URL, url, name, id, atIndex }); switch (priority) { case "BUFFER": dispatch({ type: BUFFER_TRACK, name, id }); diff --git a/js/components/PlaylistWindow/index.js b/js/components/PlaylistWindow/index.js index dfbe23c4..0725c28b 100644 --- a/js/components/PlaylistWindow/index.js +++ b/js/components/PlaylistWindow/index.js @@ -51,12 +51,12 @@ class PlaylistWindow extends React.Component { _handleDrop(e, targetCoords) { const top = e.clientY - targetCoords.y; - /*const startIndex = */ clamp( - (top - 23) % TRACK_HEIGHT, + const atIndex = clamp( + Math.round((top - 23) / TRACK_HEIGHT), 0, - this.props.maxTrackIndex + this.props.maxTrackIndex + 1 ); - this.props.loadFilesFromReferences(e /*, startIndex*/); + this.props.loadFilesFromReferences(e, atIndex); } render() { diff --git a/js/reducers/playlist.js b/js/reducers/playlist.js index 89815e07..badd20ce 100644 --- a/js/reducers/playlist.js +++ b/js/reducers/playlist.js @@ -132,9 +132,15 @@ const playlist = (state = defaultPlaylistState, action) => { const { trackOrder } = action; return { ...state, trackOrder }; case ADD_TRACK_FROM_URL: + const atIndex = + action.atIndex == null ? state.trackOrder.length : action.atIndex; return { ...state, - trackOrder: [...state.trackOrder, Number(action.id)], + trackOrder: [ + ...state.trackOrder.slice(0, atIndex), + Number(action.id), + ...state.trackOrder.slice(atIndex) + ], tracks: { ...state.tracks, [action.id]: { diff --git a/js/reducers/playlist.test.js b/js/reducers/playlist.test.js index f5387580..a7a7a981 100644 --- a/js/reducers/playlist.test.js +++ b/js/reducers/playlist.test.js @@ -1,11 +1,98 @@ import { SHIFT_CLICKED_TRACK, CLICKED_TRACK, - CTRL_CLICKED_TRACK + CTRL_CLICKED_TRACK, + ADD_TRACK_FROM_URL } from "../actionTypes"; import reducer from "./playlist"; describe("playlist reducer", () => { + it("can handle adding a track", () => { + const initialState = { + tracks: {}, + trackOrder: [], + lastSelectedIndex: null + }; + const nextState = reducer(initialState, { + type: ADD_TRACK_FROM_URL, + id: 100, + name: "My Track Name", + url: "url://some-url" + }); + expect(nextState).toEqual({ + tracks: { + 100: { + selected: false, + duration: null, + title: "My Track Name", + url: "url://some-url" + } + }, + trackOrder: [100], + lastSelectedIndex: null + }); + }); + it("defaults to adding new tracks to the end of the list", () => { + const initialState = { + tracks: { + 2: { selected: false }, + 3: { selected: false } + }, + trackOrder: [3, 2], + lastSelectedIndex: 0 + }; + const nextState = reducer(initialState, { + type: ADD_TRACK_FROM_URL, + id: 100, + name: "My Track Name", + url: "url://some-url" + }); + expect(nextState).toEqual({ + tracks: { + 2: { selected: false }, + 3: { selected: false }, + 100: { + selected: false, + duration: null, + title: "My Track Name", + url: "url://some-url" + } + }, + trackOrder: [3, 2, 100], + lastSelectedIndex: null + }); + }); + it("can handle adding a track at a given index", () => { + const initialState = { + tracks: { + 2: { selected: false }, + 3: { selected: false } + }, + trackOrder: [3, 2], + lastSelectedIndex: 0 + }; + const nextState = reducer(initialState, { + type: ADD_TRACK_FROM_URL, + id: 100, + name: "My Track Name", + url: "url://some-url", + atIndex: 1 + }); + expect(nextState).toEqual({ + tracks: { + 2: { selected: false }, + 3: { selected: false }, + 100: { + selected: false, + duration: null, + title: "My Track Name", + url: "url://some-url" + } + }, + trackOrder: [3, 100, 2], + lastSelectedIndex: null + }); + }); it("can handle clicking a track", () => { const initialState = { tracks: {