mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-23 01:57:29 +00:00
169 lines
4.6 KiB
TypeScript
169 lines
4.6 KiB
TypeScript
import { Action } from "../types";
|
|
import {
|
|
CLICKED_TRACK,
|
|
CTRL_CLICKED_TRACK,
|
|
SHIFT_CLICKED_TRACK,
|
|
SELECT_ALL,
|
|
SELECT_ZERO,
|
|
INVERT_SELECTION,
|
|
REMOVE_ALL_TRACKS,
|
|
REMOVE_TRACKS,
|
|
ADD_TRACK_FROM_URL,
|
|
REVERSE_LIST,
|
|
RANDOMIZE_LIST,
|
|
SET_TRACK_ORDER,
|
|
PLAY_TRACK,
|
|
BUFFER_TRACK,
|
|
DRAG_SELECTED,
|
|
} from "../actionTypes";
|
|
import { shuffle, moveSelected } from "../utils";
|
|
|
|
export interface PlaylistState {
|
|
trackOrder: number[];
|
|
lastSelectedIndex: number | null;
|
|
currentTrack: number | null;
|
|
selectedTracks: Set<number>;
|
|
}
|
|
|
|
const defaultPlaylistState: PlaylistState = {
|
|
trackOrder: [],
|
|
currentTrack: null,
|
|
lastSelectedIndex: null,
|
|
selectedTracks: new Set(),
|
|
};
|
|
|
|
function toggleSetMembership<T>(set: Set<T>, value: T): void {
|
|
if (set.has(value)) {
|
|
set.delete(value);
|
|
} else {
|
|
set.add(value);
|
|
}
|
|
}
|
|
|
|
const playlist = (
|
|
state: PlaylistState = defaultPlaylistState,
|
|
action: Action
|
|
): PlaylistState => {
|
|
switch (action.type) {
|
|
case CLICKED_TRACK:
|
|
return {
|
|
...state,
|
|
selectedTracks: new Set([state.trackOrder[action.index]]),
|
|
lastSelectedIndex: action.index,
|
|
};
|
|
case CTRL_CLICKED_TRACK: {
|
|
const id = state.trackOrder[action.index];
|
|
const newSelectedTracks = new Set(state.selectedTracks);
|
|
toggleSetMembership(newSelectedTracks, id);
|
|
return {
|
|
...state,
|
|
selectedTracks: newSelectedTracks,
|
|
// Using this as the lastClickedIndex is kinda funny, since you
|
|
// may have just _un_selected the track. However, this is what
|
|
// Winamp 2 does, so we'll copy it.
|
|
lastSelectedIndex: action.index,
|
|
};
|
|
}
|
|
case SHIFT_CLICKED_TRACK:
|
|
if (state.lastSelectedIndex == null) {
|
|
return state;
|
|
}
|
|
const clickedIndex = action.index;
|
|
const start = Math.min(clickedIndex, state.lastSelectedIndex);
|
|
const end = Math.max(clickedIndex, state.lastSelectedIndex);
|
|
const selectedTracks = new Set(state.trackOrder.slice(start, end + 1));
|
|
return {
|
|
...state,
|
|
selectedTracks,
|
|
};
|
|
case SELECT_ALL:
|
|
return {
|
|
...state,
|
|
selectedTracks: new Set(state.trackOrder),
|
|
};
|
|
case SELECT_ZERO:
|
|
return {
|
|
...state,
|
|
selectedTracks: new Set(),
|
|
};
|
|
case INVERT_SELECTION:
|
|
return {
|
|
...state,
|
|
selectedTracks: new Set(
|
|
state.trackOrder.filter(id => !state.selectedTracks.has(id))
|
|
),
|
|
};
|
|
case REMOVE_ALL_TRACKS:
|
|
// TODO: Consider disposing of ObjectUrls
|
|
return {
|
|
...state,
|
|
trackOrder: [],
|
|
currentTrack: null,
|
|
selectedTracks: new Set(),
|
|
lastSelectedIndex: null,
|
|
};
|
|
case REMOVE_TRACKS:
|
|
// TODO: Consider disposing of ObjectUrls
|
|
const actionIds = new Set(action.ids.map(Number));
|
|
const { currentTrack } = state;
|
|
return {
|
|
...state,
|
|
trackOrder: state.trackOrder.filter(trackId => !actionIds.has(trackId)),
|
|
currentTrack: actionIds.has(Number(currentTrack)) ? null : currentTrack,
|
|
selectedTracks: new Set(
|
|
Array.from(state.selectedTracks).filter(id => actionIds.has(id))
|
|
),
|
|
// TODO: This could probably be made to work, but we clear it just to be safe.
|
|
lastSelectedIndex: null,
|
|
};
|
|
case REVERSE_LIST:
|
|
return {
|
|
...state,
|
|
trackOrder: [...state.trackOrder].reverse(),
|
|
// TODO: This could probably be made to work, but we clear it just to be safe.
|
|
lastSelectedIndex: null,
|
|
};
|
|
case RANDOMIZE_LIST:
|
|
return {
|
|
...state,
|
|
trackOrder: shuffle(state.trackOrder),
|
|
};
|
|
case SET_TRACK_ORDER:
|
|
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.slice(0, atIndex),
|
|
Number(action.id),
|
|
...state.trackOrder.slice(atIndex),
|
|
],
|
|
// TODO: This could probably be made to work, but we clear it just to be safe.
|
|
lastSelectedIndex: null,
|
|
};
|
|
case PLAY_TRACK:
|
|
case BUFFER_TRACK:
|
|
return {
|
|
...state,
|
|
currentTrack: action.id,
|
|
};
|
|
case DRAG_SELECTED:
|
|
return {
|
|
...state,
|
|
trackOrder: moveSelected(
|
|
state.trackOrder,
|
|
i => state.selectedTracks.has(state.trackOrder[i]),
|
|
action.offset
|
|
),
|
|
// TODO: This could probably be made to work, but we clear it just to be safe.
|
|
lastSelectedIndex: null,
|
|
};
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
|
|
export default playlist;
|