From 308a1bcd3ff2b39e4d95be98792465282025b4c0 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sat, 18 Nov 2017 13:42:04 -0800 Subject: [PATCH] Reverse, randomize, sort --- js/actionCreators.js | 31 +++++++++++++++++++-- js/actionTypes.js | 3 ++ js/components/PlaylistWindow/MiscMenu.js | 35 +++++++++++++++++------- js/hotkeys.js | 6 +++- js/reducers.js | 20 +++++++++++++- js/utils.js | 32 ++++++++++++++++++++++ 6 files changed, 113 insertions(+), 14 deletions(-) diff --git a/js/actionCreators.js b/js/actionCreators.js index 387a78d0..6f900e9e 100644 --- a/js/actionCreators.js +++ b/js/actionCreators.js @@ -4,7 +4,13 @@ import skinParser from "./skinParser"; import { BANDS } from "./constants"; import { getEqfData } from "./selectors"; -import { clamp, base64FromArrayBuffer, downloadURI, normalize } from "./utils"; +import { + clamp, + base64FromArrayBuffer, + downloadURI, + normalize, + sort +} from "./utils"; import { CLOSE_WINAMP, LOAD_AUDIO_FILE, @@ -25,7 +31,10 @@ import { CLOSE_EQUALIZER_WINDOW, REMOVE_TRACKS, PLAY, - PAUSE + PAUSE, + REVERSE_LIST, + RANDOMIZE_LIST, + SET_TRACK_ORDER } from "./actionTypes"; export function play() { @@ -247,3 +256,21 @@ export function removeSelectedTracks() { }); }; } + +export function reverseList() { + return { type: REVERSE_LIST }; +} + +export function randomizeList() { + return { type: RANDOMIZE_LIST }; +} + +export function sortListByTitle() { + return (dispatch, getState) => { + const state = getState(); + const trackOrder = sort(state.playlist.trackOrder, i => + state.tracks[i].title.toLowerCase() + ); + return dispatch({ type: SET_TRACK_ORDER, trackOrder }); + }; +} diff --git a/js/actionTypes.js b/js/actionTypes.js index af48a6b5..b83e01e5 100644 --- a/js/actionTypes.js +++ b/js/actionTypes.js @@ -52,3 +52,6 @@ export const CROP_TRACKS = "CROP_TRACKS"; export const FILE_INFO = "FILE_INFO"; export const REMOVE_TRACKS = "REMOVE_TRACKS"; export const SET_AVALIABLE_SKINS = "SET_AVALIABLE_SKINS"; +export const REVERSE_LIST = "REVERSE_LIST"; +export const RANDOMIZE_LIST = "RANDOMIZE_LIST"; +export const SET_TRACK_ORDER = "SET_TRACK_ORDER"; diff --git a/js/components/PlaylistWindow/MiscMenu.js b/js/components/PlaylistWindow/MiscMenu.js index b1a0621d..3f11ccd4 100644 --- a/js/components/PlaylistWindow/MiscMenu.js +++ b/js/components/PlaylistWindow/MiscMenu.js @@ -1,17 +1,35 @@ import React from "react"; import { connect } from "react-redux"; -import { FILE_INFO } from "../../actionTypes"; +import { + reverseList, + randomizeList, + sortListByTitle +} from "../../actionCreators"; import PlaylistMenu from "./PlaylistMenu"; +import { ContextMenu, Hr, Node } from "../ContextMenu"; /* eslint-disable no-alert */ +/* TODO: This should really be kitty-corner to the upper right hand corner of the MiscMenu */ +const SortContextMenu = props => ( + }> + +
+ + +
+); + +const ConnectedSortcontextMenu = connect(null, { + reverseList, + randomizeList, + sortListByTitle +})(SortContextMenu); const MiscMenu = () => ( -
alert("Not supported in Winamp2-js")} - /> - {/* onClick={props.fileInfo} */} +
e.stopPropagation()}> + +
alert("Not supported in Winamp2-js")} @@ -23,7 +41,4 @@ const MiscMenu = () => ( ); -const mapDispatchToProps = { - fileInfo: () => ({ type: FILE_INFO }) -}; -export default connect(null, mapDispatchToProps)(MiscMenu); +export default MiscMenu; diff --git a/js/hotkeys.js b/js/hotkeys.js index 50444402..4b678a29 100644 --- a/js/hotkeys.js +++ b/js/hotkeys.js @@ -7,7 +7,8 @@ import { toggleShuffle, openFileDialog, seekForward, - seekBackward + seekBackward, + reverseList } from "./actionCreators"; import { @@ -40,6 +41,9 @@ export default function(fileInput, { dispatch }) { break; case 76: // CTRL+L FIXME break; + case 82: // CTRL+R + dispatch(reverseList()); + break; case 84: // CTRL+T dispatch({ type: TOGGLE_TIME_MODE }); break; diff --git a/js/reducers.js b/js/reducers.js index 80ce365b..27dc2f8f 100644 --- a/js/reducers.js +++ b/js/reducers.js @@ -45,9 +45,13 @@ import { REMOVE_TRACKS, SET_AVALIABLE_SKINS, LOAD_AUDIO_FILE, - LOAD_AUDIO_URL + LOAD_AUDIO_URL, + REVERSE_LIST, + RANDOMIZE_LIST, + SET_TRACK_ORDER } from "./actionTypes"; import { playlistEnabled } from "./config"; +import { shuffle } from "./utils"; const mapObject = (obj, iteratee) => // TODO: Could return the original reference if no values change @@ -317,6 +321,20 @@ const playlist = (state = defaultPlaylistState, action) => { ...state, trackOrder: state.trackOrder.filter(id => !action.ids.includes(id)) }; + case REVERSE_LIST: + return { + ...state, + trackOrder: [...state.trackOrder].reverse() + }; + case RANDOMIZE_LIST: + return { + ...state, + trackOrder: shuffle(state.trackOrder) + }; + case SET_TRACK_ORDER: + const { trackOrder } = action; + return { ...state, trackOrder }; + default: return state; } diff --git a/js/utils.js b/js/utils.js index f05a92f1..e73949c7 100644 --- a/js/utils.js +++ b/js/utils.js @@ -114,3 +114,35 @@ export const segment = (min, max, value, newValues) => { export const arraysAreEqual = (a, b) => a.length === b.length && a.every((value, i) => value === b[i]); + +// https://bost.ocks.org/mike/shuffle/ +// Shuffle an array in O(n) +export const shuffle = array => { + const sorted = [...array]; + let m = sorted.length; + + // While there remain elements to shuffle… + while (m) { + // Pick a remaining element… + const i = Math.floor(Math.random() * m--); + + // And swap it with the current element. + const val = sorted[m]; + sorted[m] = sorted[i]; + sorted[i] = val; + } + + return sorted; +}; + +export const sort = (array, iteratee) => + array.sort((a, b) => { + const aKey = iteratee(a); + const bKey = iteratee(b); + if (aKey < bKey) { + return -1; + } else if (aKey > bKey) { + return 1; + } + return 0; + });