Finish migration to inline action types

This commit is contained in:
Jordan Eldredge 2025-07-06 10:32:33 -07:00
parent c6d0c2717c
commit 274abd9090
19 changed files with 247 additions and 425 deletions

View file

@ -7,13 +7,6 @@ import { getWebampConfig } from "./webampConfig";
import * as SoundCloud from "./SoundCloud";
import WebampLazy from "../../js/webampLazy";
import {
DISABLE_MARQUEE,
SET_DUMMY_VIZ_DATA,
SET_EQ_AUTO,
TOGGLE_REPEAT,
TOGGLE_SHUFFLE,
} from "../../js/actionTypes";
import { disableMarquee, skinUrl as configSkinUrl } from "./config";
import DemoDesktop from "./DemoDesktop";
@ -95,15 +88,15 @@ async function main() {
const webamp = new WebampLazy(config);
if (disableMarquee || screenshot) {
webamp.store.dispatch({ type: DISABLE_MARQUEE });
webamp.store.dispatch({ type: "DISABLE_MARQUEE" });
}
if (screenshot) {
window.document.body.style.backgroundColor = "#000";
webamp.store.dispatch({ type: TOGGLE_REPEAT });
webamp.store.dispatch({ type: TOGGLE_SHUFFLE });
webamp.store.dispatch({ type: SET_EQ_AUTO, value: true });
webamp.store.dispatch({ type: "TOGGLE_REPEAT" });
webamp.store.dispatch({ type: "TOGGLE_SHUFFLE" });
webamp.store.dispatch({ type: "SET_EQ_AUTO", value: true });
webamp.store.dispatch({
type: SET_DUMMY_VIZ_DATA,
type: "SET_DUMMY_VIZ_DATA",
data: {
0: 11.75,
8: 11.0625,

View file

@ -8,15 +8,6 @@ import * as SoundCloud from "./SoundCloud";
import { Action, Options, AppState, WindowLayout } from "../../js/types";
import {
STEP_MARQUEE,
UPDATE_TIME_ELAPSED,
UPDATE_WINDOW_POSITIONS,
SET_VOLUME,
SET_BALANCE,
SET_BAND_VALUE,
} from "../../js/actionTypes";
import { getButterchurnOptions } from "./butterchurnOptions";
import dropboxFilePicker from "./dropboxFilePicker";
import availableSkins from "./availableSkins";
@ -26,12 +17,12 @@ import screenshotInitialState from "./screenshotInitialState";
import { InjectableDependencies, PrivateOptions } from "../../js/webampLazy";
const NOISY_ACTION_TYPES = new Set([
STEP_MARQUEE,
UPDATE_TIME_ELAPSED,
UPDATE_WINDOW_POSITIONS,
SET_VOLUME,
SET_BALANCE,
SET_BAND_VALUE,
"STEP_MARQUEE",
"UPDATE_TIME_ELAPSED",
"UPDATE_WINDOW_POSITIONS",
"SET_VOLUME",
"SET_BALANCE",
"SET_BAND_VALUE",
]);
const MIN_MILKDROP_WIDTH = 725;

View file

@ -1,13 +1,5 @@
// TODO: Split these out into individual files.
import { BANDS } from "../constants";
import {
STOP,
SET_VOLUME,
SET_BALANCE,
TOGGLE_REPEAT,
TOGGLE_SHUFFLE,
SET_BAND_VALUE,
} from "../actionTypes";
import {
stop,
@ -23,21 +15,21 @@ import {
} from "./";
test("stop", () => {
const expectedAction = { type: STOP };
const expectedAction = { type: "STOP" };
expect(stop()).toEqual(expectedAction);
});
describe("setVolume", () => {
it("enforces a mimimum value", () => {
const expectedAction = {
type: SET_VOLUME,
type: "SET_VOLUME",
volume: 0,
};
expect(setVolume(-10)).toEqual(expectedAction);
});
it("enforces a maximum value", () => {
const expectedAction = {
type: SET_VOLUME,
type: "SET_VOLUME",
volume: 100,
};
expect(setVolume(110)).toEqual(expectedAction);
@ -47,28 +39,28 @@ describe("setVolume", () => {
describe("setBalance", () => {
it("enforces a mimimum value", () => {
const expectedAction = {
type: SET_BALANCE,
type: "SET_BALANCE",
balance: -100,
};
expect(setBalance(-110)).toEqual(expectedAction);
});
it("enforces a maximum value", () => {
const expectedAction = {
type: SET_BALANCE,
type: "SET_BALANCE",
balance: 100,
};
expect(setBalance(110)).toEqual(expectedAction);
});
it("snaps to zero for positive values close to zero", () => {
const expectedAction = {
type: SET_BALANCE,
type: "SET_BALANCE",
balance: 0,
};
expect(setBalance(24)).toEqual(expectedAction);
});
it("snaps to zero for negative values close to zero", () => {
const expectedAction = {
type: SET_BALANCE,
type: "SET_BALANCE",
balance: 0,
};
expect(setBalance(-24)).toEqual(expectedAction);
@ -76,22 +68,22 @@ describe("setBalance", () => {
});
test("toggleRepeat", () => {
const expectedAction = { type: TOGGLE_REPEAT };
const expectedAction = { type: "TOGGLE_REPEAT" };
expect(toggleRepeat()).toEqual(expectedAction);
});
test("toggleShuffle", () => {
const expectedAction = { type: TOGGLE_SHUFFLE };
const expectedAction = { type: "TOGGLE_SHUFFLE" };
expect(toggleShuffle()).toEqual(expectedAction);
});
test("setPreamp", () => {
const expectedAction = { type: SET_BAND_VALUE, band: "preamp", value: 100 };
const expectedAction = { type: "SET_BAND_VALUE", band: "preamp", value: 100 };
expect(setPreamp(100)).toEqual(expectedAction);
});
test("setEqBand", () => {
const expectedAction = { type: SET_BAND_VALUE, band: 3, value: 100 };
const expectedAction = { type: "SET_BAND_VALUE", band: 3, value: 100 };
expect(setEqBand(3, 100)).toEqual(expectedAction);
});
@ -100,7 +92,7 @@ test("setEqToMax", () => {
const dispatcher = setEqToMax();
dispatcher(mockDispatch);
const expectedCalls = BANDS.map((band) => [
{ type: SET_BAND_VALUE, band, value: 100 },
{ type: "SET_BAND_VALUE", band, value: 100 },
]);
expect(mockDispatch.mock.calls).toEqual(expectedCalls);
});
@ -110,7 +102,7 @@ test("setEqToMin", () => {
const dispatcher = setEqToMin();
dispatcher(mockDispatch);
const expectedCalls = BANDS.map((band) => [
{ type: SET_BAND_VALUE, band, value: 0 },
{ type: "SET_BAND_VALUE", band, value: 0 },
]);
expect(mockDispatch.mock.calls).toEqual(expectedCalls);
});
@ -120,7 +112,7 @@ test("setEqToMid", () => {
const dispatcher = setEqToMid();
dispatcher(mockDispatch);
const expectedCalls = BANDS.map((band) => [
{ type: SET_BAND_VALUE, band, value: 50 },
{ type: "SET_BAND_VALUE", band, value: 50 },
]);
expect(mockDispatch.mock.calls).toEqual(expectedCalls);
});

View file

@ -1,11 +1,4 @@
import { FormEvent, memo, useCallback } from "react";
import {
SEEK_TO_PERCENT_COMPLETE,
SET_FOCUS,
UNSET_FOCUS,
SET_SCRUB_POSITION,
} from "../../actionTypes";
import * as Selectors from "../../selectors";
import { useTypedSelector, useTypedDispatch } from "../../hooks";
@ -29,19 +22,19 @@ const Position = memo(() => {
const seekToPercentComplete = useCallback(
(e: FormEvent) => {
dispatch({
type: SEEK_TO_PERCENT_COMPLETE,
type: "SEEK_TO_PERCENT_COMPLETE",
percent: Number((e.target as HTMLInputElement).value),
});
dispatch({ type: UNSET_FOCUS });
dispatch({ type: "UNSET_FOCUS" });
},
[dispatch]
);
const setPosition = useCallback(
(e: FormEvent) => {
dispatch({ type: SET_FOCUS, input: "position" });
dispatch({ type: "SET_FOCUS", input: "position" });
dispatch({
type: SET_SCRUB_POSITION,
type: "SET_SCRUB_POSITION",
position: Number((e.target as HTMLInputElement).value),
});
},

View file

@ -1,10 +1,5 @@
import { useCallback, ReactNode, TouchEvent } from "react";
import classnames from "classnames";
import {
CLICKED_TRACK,
CTRL_CLICKED_TRACK,
SHIFT_CLICKED_TRACK,
} from "../../actionTypes";
import * as Selectors from "../../selectors";
import * as Actions from "../../actionCreators";
import {
@ -36,16 +31,16 @@ function TrackCell({ children, handleMoveClick, index, id }: Props) {
(e: React.MouseEvent<HTMLDivElement>) => {
if (e.shiftKey) {
e.preventDefault();
dispatch({ type: SHIFT_CLICKED_TRACK, index });
dispatch({ type: "SHIFT_CLICKED_TRACK", index });
return;
} else if (e.metaKey || e.ctrlKey) {
e.preventDefault();
dispatch({ type: CTRL_CLICKED_TRACK, index });
dispatch({ type: "CTRL_CLICKED_TRACK", index });
return;
}
if (!selected) {
dispatch({ type: CLICKED_TRACK, index });
dispatch({ type: "CLICKED_TRACK", index });
}
handleMoveClick(e);
@ -56,7 +51,7 @@ function TrackCell({ children, handleMoveClick, index, id }: Props) {
const handleTouchStart = useCallback(
(e: TouchEvent<HTMLDivElement>) => {
if (!selected) {
dispatch({ type: CLICKED_TRACK, index });
dispatch({ type: "CLICKED_TRACK", index });
}
handleMoveClick(e);

View file

@ -1,23 +1,4 @@
import { IMedia } from "./media";
import {
IS_PLAYING,
PAUSE,
PLAY,
SEEK_TO_PERCENT_COMPLETE,
SET_BAND_VALUE,
SET_BALANCE,
SET_MEDIA,
SET_VOLUME,
START_WORKING,
STOP,
STOP_WORKING,
UPDATE_TIME_ELAPSED,
SET_EQ_OFF,
SET_EQ_ON,
PLAY_TRACK,
BUFFER_TRACK,
LOAD_SERIALIZED_STATE,
} from "./actionTypes";
import { next as nextTrack } from "./actionCreators";
import * as Selectors from "./selectors";
import { MiddlewareStore, Action, Dispatch } from "./types";
@ -37,7 +18,7 @@ export default (media: IMedia) => (store: MiddlewareStore) => {
media.on("timeupdate", () => {
store.dispatch({
type: UPDATE_TIME_ELAPSED,
type: "UPDATE_TIME_ELAPSED",
elapsed: media.timeElapsed(),
});
});
@ -47,15 +28,15 @@ export default (media: IMedia) => (store: MiddlewareStore) => {
});
media.on("playing", () => {
store.dispatch({ type: IS_PLAYING });
store.dispatch({ type: "IS_PLAYING" });
});
media.on("waiting", () => {
store.dispatch({ type: START_WORKING });
store.dispatch({ type: "START_WORKING" });
});
media.on("stopWaiting", () => {
store.dispatch({ type: STOP_WORKING });
store.dispatch({ type: "STOP_WORKING" });
});
media.on("fileLoaded", () => {
@ -67,7 +48,7 @@ export default (media: IMedia) => (store: MiddlewareStore) => {
}
store.dispatch({
id,
type: SET_MEDIA,
type: "SET_MEDIA",
kbps: "128",
khz: "44",
channels: 2,
@ -79,52 +60,52 @@ export default (media: IMedia) => (store: MiddlewareStore) => {
const returnValue = next(action);
const state = store.getState();
switch (action.type) {
case PLAY:
case "PLAY":
media.play();
break;
case PAUSE:
case "PAUSE":
media.pause();
break;
case STOP:
case "STOP":
media.stop();
break;
case SET_VOLUME:
case "SET_VOLUME":
media.setVolume(Selectors.getVolume(state));
break;
case SET_BALANCE:
case "SET_BALANCE":
media.setBalance(Selectors.getBalance(state));
break;
case SEEK_TO_PERCENT_COMPLETE:
media.seekToPercentComplete(action.percent);
case "SEEK_TO_PERCENT_COMPLETE":
media.seekToPercentComplete((action as any).percent);
break;
case PLAY_TRACK: {
const url = Selectors.getTrackUrl(store.getState())(action.id);
case "PLAY_TRACK": {
const url = Selectors.getTrackUrl(store.getState())((action as any).id);
if (url != null) {
media.loadFromUrl(url, true);
}
break;
}
case BUFFER_TRACK: {
const url = Selectors.getTrackUrl(store.getState())(action.id);
case "BUFFER_TRACK": {
const url = Selectors.getTrackUrl(store.getState())((action as any).id);
if (url != null) {
media.loadFromUrl(url, false);
}
break;
}
case SET_BAND_VALUE:
if (action.band === "preamp") {
media.setPreamp(action.value);
case "SET_BAND_VALUE":
if ((action as any).band === "preamp") {
media.setPreamp((action as any).value);
} else {
media.setEqBand(action.band, action.value);
media.setEqBand((action as any).band, (action as any).value);
}
break;
case SET_EQ_OFF:
case "SET_EQ_OFF":
media.disableEq();
break;
case SET_EQ_ON:
case "SET_EQ_ON":
media.enableEq();
break;
case LOAD_SERIALIZED_STATE: {
case "LOAD_SERIALIZED_STATE": {
// Set ALL THE THINGS!
if (Selectors.getEqualizerEnabled(state)) {
media.enableEq();

View file

@ -10,26 +10,6 @@ import {
} from "../types";
import * as Utils from "../utils";
import { createSelector } from "reselect";
import {
CLOSE_WINAMP,
OPEN_WINAMP,
SET_SKIN_DATA,
START_WORKING,
STEP_MARQUEE,
STOP_WORKING,
TOGGLE_DOUBLESIZE_MODE,
TOGGLE_LLAMA_MODE,
TOGGLE_VISUALIZER_STYLE,
SET_PLAYLIST_SCROLL_POSITION,
LOADED,
SET_Z_INDEX,
DISABLE_MARQUEE,
SET_DUMMY_VIZ_DATA,
LOADING,
LOAD_SERIALIZED_STATE,
LOAD_DEFAULT_SKIN,
} from "../actionTypes";
import { DEFAULT_SKIN, VISUALIZER_ORDER } from "../constants";
import { DisplaySerializedStateV1 } from "../serializedStates/v1Types";
@ -106,7 +86,7 @@ const display = (
action: Action
): DisplayState => {
switch (action.type) {
case LOAD_DEFAULT_SKIN: {
case "LOAD_DEFAULT_SKIN": {
const {
skinImages,
skinColors,
@ -127,30 +107,30 @@ const display = (
skinGenExColors,
};
}
case TOGGLE_DOUBLESIZE_MODE:
case "TOGGLE_DOUBLESIZE_MODE":
return { ...state, doubled: !state.doubled };
case TOGGLE_LLAMA_MODE:
case "TOGGLE_LLAMA_MODE":
return { ...state, llama: !state.llama };
case STEP_MARQUEE:
case "STEP_MARQUEE":
return state.disableMarquee
? state
: { ...state, marqueeStep: state.marqueeStep + 1 };
case DISABLE_MARQUEE:
case "DISABLE_MARQUEE":
return { ...state, disableMarquee: true };
case STOP_WORKING:
case "STOP_WORKING":
return { ...state, working: false };
case START_WORKING:
case "START_WORKING":
return { ...state, working: true };
case CLOSE_WINAMP:
case "CLOSE_WINAMP":
return { ...state, closed: true };
case OPEN_WINAMP:
case "OPEN_WINAMP":
return { ...state, closed: false };
case LOADING:
case "LOADING":
return { ...state, loading: true };
case LOADED:
case "LOADED":
return { ...state, loading: false };
case SET_SKIN_DATA:
const { data } = action;
case "SET_SKIN_DATA":
const { data } = action as any;
return {
...state,
loading: false,
@ -162,19 +142,19 @@ const display = (
skinGenLetterWidths: data.skinGenLetterWidths,
skinGenExColors: data.skinGenExColors || defaultSkinGenExColors,
};
case TOGGLE_VISUALIZER_STYLE:
case "TOGGLE_VISUALIZER_STYLE":
return {
...state,
visualizerStyle: (state.visualizerStyle + 1) % VISUALIZER_ORDER.length,
};
case SET_PLAYLIST_SCROLL_POSITION:
return { ...state, playlistScrollPosition: action.position };
case SET_Z_INDEX:
return { ...state, zIndex: action.zIndex };
case SET_DUMMY_VIZ_DATA:
return { ...state, dummyVizData: action.data };
case LOAD_SERIALIZED_STATE: {
const { skinCursors, ...rest } = action.serializedState.display;
case "SET_PLAYLIST_SCROLL_POSITION":
return { ...state, playlistScrollPosition: (action as any).position };
case "SET_Z_INDEX":
return { ...state, zIndex: (action as any).zIndex };
case "SET_DUMMY_VIZ_DATA":
return { ...state, dummyVizData: (action as any).data };
case "LOAD_SERIALIZED_STATE": {
const { skinCursors, ...rest } = (action as any).serializedState.display;
const upgrade = (url: string) => ({ type: "cur", url } as const);
const newSkinCursors =
skinCursors == null ? null : Utils.objectMap(skinCursors, upgrade);

View file

@ -1,12 +1,4 @@
import { Slider, Action } from "./../types";
import {
SET_BAND_VALUE,
SET_EQ_AUTO,
SET_EQ_ON,
SET_EQ_OFF,
LOAD_SERIALIZED_STATE,
} from "../actionTypes";
import { EqualizerSerializedStateV1 } from "../serializedStates/v1Types";
export interface EqualizerState {
@ -38,17 +30,17 @@ const equalizer = (
action: Action
): EqualizerState => {
switch (action.type) {
case SET_BAND_VALUE:
const newSliders = { ...state.sliders, [action.band]: action.value };
case "SET_BAND_VALUE":
const newSliders = { ...state.sliders, [(action as any).band]: (action as any).value };
return { ...state, sliders: newSliders };
case SET_EQ_ON:
case "SET_EQ_ON":
return { ...state, on: true };
case SET_EQ_OFF:
case "SET_EQ_OFF":
return { ...state, on: false };
case SET_EQ_AUTO:
return { ...state, auto: action.value };
case LOAD_SERIALIZED_STATE:
return action.serializedState.equalizer || state;
case "SET_EQ_AUTO":
return { ...state, auto: (action as any).value };
case "LOAD_SERIALIZED_STATE":
return (action as any).serializedState.equalizer || state;
default:
return state;
}

View file

@ -1,21 +1,4 @@
import { Action, PlayerMediaStatus, TimeMode } from "../types";
import {
PLAY,
STOP,
PAUSE,
IS_STOPPED,
IS_PLAYING,
SET_VOLUME,
SET_BALANCE,
SET_MEDIA,
TOGGLE_REPEAT,
TOGGLE_SHUFFLE,
TOGGLE_TIME_MODE,
UPDATE_TIME_ELAPSED,
LOAD_SERIALIZED_STATE,
CLOSE_WINAMP,
OPEN_WINAMP,
} from "../actionTypes";
import { TIME_MODE, PLAYER_MEDIA_STATUS } from "../constants";
import { MediaSerializedStateV1 } from "../serializedStates/v1Types";
@ -50,41 +33,41 @@ const media = (
): MediaState => {
switch (action.type) {
// TODO: Make these constants
case PLAY:
case IS_PLAYING:
case "PLAY":
case "IS_PLAYING":
return { ...state, status: PLAYER_MEDIA_STATUS.PLAYING };
case PAUSE:
case "PAUSE":
return { ...state, status: PLAYER_MEDIA_STATUS.PAUSED };
case STOP:
case "STOP":
return { ...state, status: PLAYER_MEDIA_STATUS.STOPPED };
case IS_STOPPED:
case "IS_STOPPED":
return { ...state, status: PLAYER_MEDIA_STATUS.ENDED };
case OPEN_WINAMP:
case "OPEN_WINAMP":
return { ...state, status: PLAYER_MEDIA_STATUS.STOPPED };
case CLOSE_WINAMP:
case "CLOSE_WINAMP":
return { ...state, status: PLAYER_MEDIA_STATUS.CLOSED };
case TOGGLE_TIME_MODE:
case "TOGGLE_TIME_MODE":
const newMode =
state.timeMode === TIME_MODE.REMAINING
? TIME_MODE.ELAPSED
: TIME_MODE.REMAINING;
return { ...state, timeMode: newMode };
case UPDATE_TIME_ELAPSED:
return { ...state, timeElapsed: action.elapsed };
case SET_MEDIA:
case "UPDATE_TIME_ELAPSED":
return { ...state, timeElapsed: (action as any).elapsed };
case "SET_MEDIA":
return {
...state,
};
case SET_VOLUME:
return { ...state, volume: action.volume };
case SET_BALANCE:
return { ...state, balance: action.balance };
case TOGGLE_REPEAT:
case "SET_VOLUME":
return { ...state, volume: (action as any).volume };
case "SET_BALANCE":
return { ...state, balance: (action as any).balance };
case "TOGGLE_REPEAT":
return { ...state, repeat: !state.repeat };
case TOGGLE_SHUFFLE:
case "TOGGLE_SHUFFLE":
return { ...state, shuffle: !state.shuffle };
case LOAD_SERIALIZED_STATE:
return { ...state, ...action.serializedState.media };
case "LOAD_SERIALIZED_STATE":
return { ...state, ...(action as any).serializedState.media };
default:
return state;
}

View file

@ -1,17 +1,4 @@
import { Action, StatePreset, TransitionType, MilkdropMessage } from "../types";
import {
SET_MILKDROP_DESKTOP,
SET_MILKDROP_FULLSCREEN,
GOT_BUTTERCHURN_PRESETS,
GOT_BUTTERCHURN,
RESOLVE_PRESET_AT_INDEX,
SELECT_PRESET_AT_INDEX,
TOGGLE_PRESET_OVERLAY,
PRESET_REQUESTED,
TOGGLE_RANDOMIZE_PRESETS,
TOGGLE_PRESET_CYCLING,
SCHEDULE_MILKDROP_MESSAGE,
} from "../actionTypes";
import * as Utils from "../utils";
export interface MilkdropState {
@ -47,55 +34,55 @@ export const milkdrop = (
action: Action
): MilkdropState => {
switch (action.type) {
case SET_MILKDROP_DESKTOP:
return { ...state, display: action.enabled ? "DESKTOP" : "WINDOW" };
case SET_MILKDROP_FULLSCREEN:
return { ...state, display: action.enabled ? "FULLSCREEN" : "WINDOW" };
case GOT_BUTTERCHURN:
return { ...state, butterchurn: action.butterchurn };
case GOT_BUTTERCHURN_PRESETS:
case "SET_MILKDROP_DESKTOP":
return { ...state, display: (action as any).enabled ? "DESKTOP" : "WINDOW" };
case "SET_MILKDROP_FULLSCREEN":
return { ...state, display: (action as any).enabled ? "FULLSCREEN" : "WINDOW" };
case "GOT_BUTTERCHURN":
return { ...state, butterchurn: (action as any).butterchurn };
case "GOT_BUTTERCHURN_PRESETS":
return {
...state,
presets: state.presets.concat(action.presets),
presets: state.presets.concat((action as any).presets),
};
case PRESET_REQUESTED:
if (action.addToHistory) {
case "PRESET_REQUESTED":
if ((action as any).addToHistory) {
return {
...state,
presetHistory: [...state.presetHistory, action.index],
presetHistory: [...state.presetHistory, (action as any).index],
};
}
return {
...state,
presetHistory: state.presetHistory.slice(0, -1),
};
case RESOLVE_PRESET_AT_INDEX:
const preset = state.presets[action.index];
case "RESOLVE_PRESET_AT_INDEX":
const preset = state.presets[(action as any).index];
return {
...state,
presets: Utils.replaceAtIndex(state.presets, action.index, {
presets: Utils.replaceAtIndex(state.presets, (action as any).index, {
type: "RESOLVED",
name: preset.name,
preset: action.json,
preset: (action as any).json,
}),
};
case SELECT_PRESET_AT_INDEX:
case "SELECT_PRESET_AT_INDEX":
return {
...state,
currentPresetIndex: action.index,
transitionType: action.transitionType,
currentPresetIndex: (action as any).index,
transitionType: (action as any).transitionType,
};
case TOGGLE_PRESET_OVERLAY:
case "TOGGLE_PRESET_OVERLAY":
return { ...state, overlay: !state.overlay };
case TOGGLE_RANDOMIZE_PRESETS:
case "TOGGLE_RANDOMIZE_PRESETS":
return { ...state, randomize: !state.randomize };
case TOGGLE_PRESET_CYCLING:
case "TOGGLE_PRESET_CYCLING":
return { ...state, cycling: !state.cycling };
case SCHEDULE_MILKDROP_MESSAGE:
case "SCHEDULE_MILKDROP_MESSAGE":
return {
...state,
message: {
text: action.message,
text: (action as any).message,
time: Date.now(),
},
};

View file

@ -1,5 +1,4 @@
import { Action } from "../types";
import { NETWORK_CONNECTED, NETWORK_DISCONNECTED } from "../actionTypes";
export interface NetworkState {
connected: boolean;
@ -10,9 +9,9 @@ const network = (
action: Action
): NetworkState => {
switch (action.type) {
case NETWORK_CONNECTED:
case "NETWORK_CONNECTED":
return { ...state, connected: true };
case NETWORK_DISCONNECTED:
case "NETWORK_DISCONNECTED":
return { ...state, connected: false };
default:
return state;

View file

@ -1,9 +1,3 @@
import {
SHIFT_CLICKED_TRACK,
CLICKED_TRACK,
CTRL_CLICKED_TRACK,
ADD_TRACK_FROM_URL,
} from "../actionTypes";
import reducer from "./playlist";
describe("playlist reducer", () => {
@ -14,7 +8,7 @@ describe("playlist reducer", () => {
lastSelectedIndex: null,
};
const nextState = reducer(initialState, {
type: ADD_TRACK_FROM_URL,
type: "ADD_TRACK_FROM_URL",
id: 100,
defaultName: "My Track Name",
url: "url://some-url",
@ -32,7 +26,7 @@ describe("playlist reducer", () => {
lastSelectedIndex: 0,
};
const nextState = reducer(initialState, {
type: ADD_TRACK_FROM_URL,
type: "ADD_TRACK_FROM_URL",
id: 100,
defaultName: "My Track Name",
url: "url://some-url",
@ -50,7 +44,7 @@ describe("playlist reducer", () => {
lastSelectedIndex: 0,
};
const nextState = reducer(initialState, {
type: ADD_TRACK_FROM_URL,
type: "ADD_TRACK_FROM_URL",
id: 100,
defaultName: "My Track Name",
url: "url://some-url",
@ -70,7 +64,7 @@ describe("playlist reducer", () => {
};
const nextState = reducer(initialState, {
type: CLICKED_TRACK,
type: "CLICKED_TRACK",
index: 1,
});
expect(nextState).toEqual({
@ -87,7 +81,7 @@ describe("playlist reducer", () => {
};
const nextState = reducer(initialState, {
type: CTRL_CLICKED_TRACK,
type: "CTRL_CLICKED_TRACK",
index: 0,
});
expect(nextState).toEqual({
@ -104,7 +98,7 @@ describe("playlist reducer", () => {
};
const nextState = reducer(initialState, {
type: SHIFT_CLICKED_TRACK,
type: "SHIFT_CLICKED_TRACK",
index: 3,
});
expect(nextState).toEqual({

View file

@ -1,21 +1,4 @@
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 {
@ -45,14 +28,14 @@ const playlist = (
action: Action
): PlaylistState => {
switch (action.type) {
case CLICKED_TRACK:
case "CLICKED_TRACK":
return {
...state,
selectedTracks: new Set([state.trackOrder[action.index]]),
lastSelectedIndex: action.index,
selectedTracks: new Set([state.trackOrder[(action as any).index]]),
lastSelectedIndex: (action as any).index,
};
case CTRL_CLICKED_TRACK: {
const id = state.trackOrder[action.index];
case "CTRL_CLICKED_TRACK": {
const id = state.trackOrder[(action as any).index];
const newSelectedTracks = new Set(state.selectedTracks);
toggleSetMembership(newSelectedTracks, id);
return {
@ -61,14 +44,14 @@ const playlist = (
// 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,
lastSelectedIndex: (action as any).index,
};
}
case SHIFT_CLICKED_TRACK:
case "SHIFT_CLICKED_TRACK":
if (state.lastSelectedIndex == null) {
return state;
}
const clickedIndex = action.index;
const clickedIndex = (action as any).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));
@ -76,24 +59,24 @@ const playlist = (
...state,
selectedTracks,
};
case SELECT_ALL:
case "SELECT_ALL":
return {
...state,
selectedTracks: new Set(state.trackOrder),
};
case SELECT_ZERO:
case "SELECT_ZERO":
return {
...state,
selectedTracks: new Set(),
};
case INVERT_SELECTION:
case "INVERT_SELECTION":
return {
...state,
selectedTracks: new Set(
state.trackOrder.filter((id) => !state.selectedTracks.has(id))
),
};
case REMOVE_ALL_TRACKS:
case "REMOVE_ALL_TRACKS":
// TODO: Consider disposing of ObjectUrls
return {
...state,
@ -102,9 +85,9 @@ const playlist = (
selectedTracks: new Set(),
lastSelectedIndex: null,
};
case REMOVE_TRACKS:
case "REMOVE_TRACKS":
// TODO: Consider disposing of ObjectUrls
const actionIds = new Set(action.ids.map(Number));
const actionIds = new Set((action as any).ids.map(Number));
const { currentTrack } = state;
return {
...state,
@ -118,47 +101,47 @@ const playlist = (
// TODO: This could probably be made to work, but we clear it just to be safe.
lastSelectedIndex: null,
};
case REVERSE_LIST:
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:
case "RANDOMIZE_LIST":
return {
...state,
trackOrder: shuffle(state.trackOrder),
};
case SET_TRACK_ORDER:
const { trackOrder } = action;
case "SET_TRACK_ORDER":
const { trackOrder } = action as any;
return { ...state, trackOrder };
case ADD_TRACK_FROM_URL:
case "ADD_TRACK_FROM_URL":
const atIndex =
action.atIndex == null ? state.trackOrder.length : action.atIndex;
(action as any).atIndex == null ? state.trackOrder.length : (action as any).atIndex;
return {
...state,
trackOrder: [
...state.trackOrder.slice(0, atIndex),
Number(action.id),
Number((action as any).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:
case "PLAY_TRACK":
case "BUFFER_TRACK":
return {
...state,
currentTrack: action.id,
currentTrack: (action as any).id,
};
case DRAG_SELECTED:
case "DRAG_SELECTED":
return {
...state,
trackOrder: moveSelected(
state.trackOrder,
(i) => state.selectedTracks.has(state.trackOrder[i]),
action.offset
(action as any).offset
),
// TODO: This could probably be made to work, but we clear it just to be safe.
lastSelectedIndex: null,

View file

@ -1,5 +1,4 @@
import { Action, Skin } from "../types";
import { SET_AVAILABLE_SKINS } from "../actionTypes";
export interface SettingsState {
availableSkins: Array<Skin>;
@ -14,8 +13,8 @@ const settings = (
action: Action
): SettingsState => {
switch (action.type) {
case SET_AVAILABLE_SKINS:
return { ...state, availableSkins: action.skins };
case "SET_AVAILABLE_SKINS":
return { ...state, availableSkins: (action as any).skins };
default:
return state;
}

View file

@ -1,12 +1,4 @@
import { PlaylistTrack, Action } from "../types";
import {
SET_MEDIA,
SET_MEDIA_TAGS,
SET_MEDIA_DURATION,
MEDIA_TAG_REQUEST_INITIALIZED,
MEDIA_TAG_REQUEST_FAILED,
ADD_TRACK_FROM_URL,
} from "../actionTypes";
import { MEDIA_TAG_REQUEST_STATUS } from "../constants";
import * as TrackUtils from "../trackUtils";
@ -21,54 +13,54 @@ const tracks = (
action: Action
): TracksState => {
switch (action.type) {
case ADD_TRACK_FROM_URL:
case "ADD_TRACK_FROM_URL":
return {
...state,
[action.id]: {
id: action.id,
defaultName: action.defaultName || null,
duration: action.duration ?? null,
url: action.url,
[(action as any).id]: {
id: (action as any).id,
defaultName: (action as any).defaultName || null,
duration: (action as any).duration ?? null,
url: (action as any).url,
mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.INITIALIZED,
},
};
case SET_MEDIA: {
case "SET_MEDIA": {
const newTrack = {
...state[action.id],
duration: action.length,
...state[(action as any).id],
duration: (action as any).length,
};
return {
...state,
[action.id]: newTrack,
[(action as any).id]: newTrack,
};
}
case MEDIA_TAG_REQUEST_INITIALIZED:
case "MEDIA_TAG_REQUEST_INITIALIZED":
return {
...state,
[action.id]: {
...state[action.id],
[(action as any).id]: {
...state[(action as any).id],
mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.INITIALIZED,
},
};
case MEDIA_TAG_REQUEST_FAILED:
case "MEDIA_TAG_REQUEST_FAILED":
return {
...state,
[action.id]: {
...state[action.id],
[(action as any).id]: {
...state[(action as any).id],
mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.FAILED,
},
};
case SET_MEDIA_DURATION: {
case "SET_MEDIA_DURATION": {
return {
...state,
[action.id]: {
...state[action.id],
duration: action.duration,
[(action as any).id]: {
...state[(action as any).id],
duration: (action as any).duration,
},
};
}
case SET_MEDIA_TAGS:
const track = state[action.id];
case "SET_MEDIA_TAGS":
const track = state[(action as any).id];
const {
sampleRate,
bitrate,
@ -77,11 +69,11 @@ const tracks = (
artist,
album,
albumArtUrl,
} = action;
} = action as any;
const { kbps, khz, channels } = track;
return {
...state,
[action.id]: {
[(action as any).id]: {
...track,
mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.COMPLETE,
title,

View file

@ -1,4 +1,3 @@
import { SET_FOCUS, SET_SCRUB_POSITION, UNSET_FOCUS } from "../actionTypes";
import userInput from "./userInput";
describe("userInput reducer", () => {
@ -13,7 +12,7 @@ describe("userInput reducer", () => {
});
it("can set focus", () => {
const newState = userInput(state, {
type: SET_FOCUS,
type: "SET_FOCUS",
input: "foo",
bandFocused: null,
userMessage: null,
@ -28,7 +27,7 @@ describe("userInput reducer", () => {
it("can unset focus", () => {
const newState = userInput(
{ ...state, focus: "foo", bandFocused: null },
{ type: UNSET_FOCUS }
{ type: "UNSET_FOCUS" }
);
expect(newState).toEqual({
focus: null,
@ -39,7 +38,7 @@ describe("userInput reducer", () => {
});
it("can set scrub position", () => {
const newState = userInput(state, {
type: SET_SCRUB_POSITION,
type: "SET_SCRUB_POSITION",
position: 5,
bandFocused: null,
userMessage: null,

View file

@ -1,12 +1,4 @@
import { Action, Slider } from "../types";
import {
SET_FOCUS,
SET_BAND_FOCUS,
SET_SCRUB_POSITION,
UNSET_FOCUS,
SET_USER_MESSAGE,
UNSET_USER_MESSAGE,
} from "../actionTypes";
export interface UserInputState {
focus: string | null; // TODO: Convert this to an enum?
@ -27,17 +19,17 @@ export const userInput = (
action: Action
): UserInputState => {
switch (action.type) {
case SET_FOCUS:
return { ...state, focus: action.input, bandFocused: null };
case SET_BAND_FOCUS:
return { ...state, focus: action.input, bandFocused: action.bandFocused };
case UNSET_FOCUS:
case "SET_FOCUS":
return { ...state, focus: (action as any).input, bandFocused: null };
case "SET_BAND_FOCUS":
return { ...state, focus: (action as any).input, bandFocused: (action as any).bandFocused };
case "UNSET_FOCUS":
return { ...state, focus: null, bandFocused: null };
case SET_SCRUB_POSITION:
return { ...state, scrubPosition: action.position };
case SET_USER_MESSAGE:
return { ...state, userMessage: action.message };
case UNSET_USER_MESSAGE:
case "SET_SCRUB_POSITION":
return { ...state, scrubPosition: (action as any).position };
case "SET_USER_MESSAGE":
return { ...state, userMessage: (action as any).message };
case "UNSET_USER_MESSAGE":
return { ...state, userMessage: null };
default:
return state;

View file

@ -1,17 +1,5 @@
import { Action, WindowId, Box, Point } from "../types";
import { WINDOWS } from "../constants";
import {
SET_FOCUSED_WINDOW,
TOGGLE_WINDOW,
CLOSE_WINDOW,
UPDATE_WINDOW_POSITIONS,
WINDOW_SIZE_CHANGED,
TOGGLE_WINDOW_SHADE_MODE,
LOAD_SERIALIZED_STATE,
BROWSER_WINDOW_SIZE_CHANGED,
RESET_WINDOW_SIZES,
ENABLE_MILKDROP,
} from "../actionTypes";
import * as Utils from "../utils";
import { WindowsSerializedStateV1 } from "../serializedStates/v1Types";
@ -107,7 +95,7 @@ const windows = (
action: Action
): WindowsState => {
switch (action.type) {
case ENABLE_MILKDROP:
case "ENABLE_MILKDROP":
return {
...state,
milkdropEnabled: true,
@ -115,90 +103,90 @@ const windows = (
...state.genWindows,
[WINDOWS.MILKDROP]: {
...state.genWindows[WINDOWS.MILKDROP],
open: action.open,
open: (action as any).open,
},
},
};
case SET_FOCUSED_WINDOW:
case "SET_FOCUSED_WINDOW":
let windowOrder = state.windowOrder;
if (action.window != null) {
if ((action as any).window != null) {
windowOrder = [
...state.windowOrder.filter((windowId) => windowId !== action.window),
action.window,
...state.windowOrder.filter((windowId) => windowId !== (action as any).window),
(action as any).window,
];
}
return { ...state, focused: action.window, windowOrder };
case TOGGLE_WINDOW_SHADE_MODE:
const { canShade } = state.genWindows[action.windowId];
return { ...state, focused: (action as any).window, windowOrder };
case "TOGGLE_WINDOW_SHADE_MODE":
const { canShade } = state.genWindows[(action as any).windowId];
if (!canShade) {
throw new Error(
`Tried to shade/unshade a window that cannot be shaded: ${action.windowId}`
`Tried to shade/unshade a window that cannot be shaded: ${(action as any).windowId}`
);
}
return {
...state,
genWindows: {
...state.genWindows,
[action.windowId]: {
...state.genWindows[action.windowId],
shade: !state.genWindows[action.windowId].shade,
[(action as any).windowId]: {
...state.genWindows[(action as any).windowId],
shade: !state.genWindows[(action as any).windowId].shade,
},
},
};
case TOGGLE_WINDOW:
const windowState = state.genWindows[action.windowId];
case "TOGGLE_WINDOW":
const windowState = state.genWindows[(action as any).windowId];
return {
...state,
genWindows: {
...state.genWindows,
[action.windowId]: {
[(action as any).windowId]: {
...windowState,
open: !windowState.open,
},
},
};
case CLOSE_WINDOW:
case "CLOSE_WINDOW":
return {
...state,
genWindows: {
...state.genWindows,
[action.windowId]: {
...state.genWindows[action.windowId],
[(action as any).windowId]: {
...state.genWindows[(action as any).windowId],
open: false,
},
},
};
case WINDOW_SIZE_CHANGED:
const { canResize } = state.genWindows[action.windowId];
case "WINDOW_SIZE_CHANGED":
const { canResize } = state.genWindows[(action as any).windowId];
if (!canResize) {
throw new Error(
`Tried to resize a window that cannot be resized: ${action.windowId}`
`Tried to resize a window that cannot be resized: ${(action as any).windowId}`
);
}
return {
...state,
genWindows: {
...state.genWindows,
[action.windowId]: {
...state.genWindows[action.windowId],
size: action.size,
[(action as any).windowId]: {
...state.genWindows[(action as any).windowId],
size: (action as any).size,
},
},
};
case UPDATE_WINDOW_POSITIONS:
case "UPDATE_WINDOW_POSITIONS":
return {
...state,
positionsAreRelative:
action.absolute === true ? false : state.positionsAreRelative,
(action as any).absolute === true ? false : state.positionsAreRelative,
genWindows: Utils.objectMap(state.genWindows, (w, windowId) => {
const newPosition = action.positions[windowId];
const newPosition = (action as any).positions[windowId];
if (newPosition == null) {
return w;
}
return { ...w, position: newPosition };
}),
};
case RESET_WINDOW_SIZES:
case "RESET_WINDOW_SIZES":
return {
...state,
genWindows: Utils.objectMap(state.genWindows, (w) => ({
@ -207,9 +195,9 @@ const windows = (
size: [0, 0] as [number, number],
})),
};
case LOAD_SERIALIZED_STATE: {
case "LOAD_SERIALIZED_STATE": {
const { genWindows, focused, positionsAreRelative } =
action.serializedState.windows;
(action as any).serializedState.windows;
return {
...state,
positionsAreRelative,
@ -225,10 +213,10 @@ const windows = (
focused,
};
}
case BROWSER_WINDOW_SIZE_CHANGED:
case "BROWSER_WINDOW_SIZE_CHANGED":
return {
...state,
browserWindowSize: { height: action.height, width: action.width },
browserWindowSize: { height: (action as any).height, width: (action as any).width },
};
default:

View file

@ -27,17 +27,6 @@ import * as Actions from "./actionCreators";
import { LOAD_STYLE } from "./constants";
import * as FileUtils from "./fileUtils";
import {
SET_AVAILABLE_SKINS,
NETWORK_CONNECTED,
NETWORK_DISCONNECTED,
CLOSE_WINAMP,
MINIMIZE_WINAMP,
LOADED,
SET_Z_INDEX,
CLOSE_REQUESTED,
ENABLE_MILKDROP,
} from "./actionTypes";
import Emitter from "./emitter";
import { SerializedStateV1 } from "./serializedStates/v1Types";
@ -150,18 +139,18 @@ class Webamp {
}
if (navigator.onLine) {
this.store.dispatch({ type: NETWORK_CONNECTED });
this.store.dispatch({ type: "NETWORK_CONNECTED" });
} else {
this.store.dispatch({ type: NETWORK_DISCONNECTED });
this.store.dispatch({ type: "NETWORK_DISCONNECTED" });
}
if (zIndex != null) {
this.store.dispatch({ type: SET_Z_INDEX, zIndex });
this.store.dispatch({ type: "SET_Z_INDEX", zIndex });
}
if (options.__butterchurnOptions) {
this.store.dispatch({
type: ENABLE_MILKDROP,
type: "ENABLE_MILKDROP",
open: options.__butterchurnOptions.butterchurnOpen,
});
this.store.dispatch(
@ -169,9 +158,9 @@ class Webamp {
);
}
const handleOnline = () => this.store.dispatch({ type: NETWORK_CONNECTED });
const handleOnline = () => this.store.dispatch({ type: "NETWORK_CONNECTED" });
const handleOffline = () =>
this.store.dispatch({ type: NETWORK_DISCONNECTED });
this.store.dispatch({ type: "NETWORK_DISCONNECTED" });
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
@ -185,7 +174,7 @@ class Webamp {
this.store.dispatch(Actions.setSkinFromUrl(initialSkin.url));
} else {
// We are using the default skin.
this.store.dispatch({ type: LOADED });
this.store.dispatch({ type: "LOADED" });
}
if (initialTracks) {
@ -198,9 +187,9 @@ class Webamp {
"The misspelled option `avaliableSkins` is deprecated. Please use `availableSkins` instead."
);
// @ts-ignore
this.store.dispatch({ type: SET_AVAILABLE_SKINS, skins: avaliableSkins });
this.store.dispatch({ type: "SET_AVAILABLE_SKINS", skins: avaliableSkins });
} else if (availableSkins != null) {
this.store.dispatch({ type: SET_AVAILABLE_SKINS, skins: availableSkins });
this.store.dispatch({ type: "SET_AVAILABLE_SKINS", skins: availableSkins });
}
this.store.dispatch(Actions.setWindowLayout(options.windowLayout));
@ -362,8 +351,8 @@ class Webamp {
* @returns An "unsubscribe" function. Useful if at some point in the future you want to stop listening to these events.
*/
onWillClose(cb: (cancel: () => void) => void): () => void {
return this._actionEmitter.on(CLOSE_REQUESTED, (action) => {
cb(action.cancel);
return this._actionEmitter.on("CLOSE_REQUESTED", (action) => {
cb((action as any).cancel);
});
}
@ -373,7 +362,7 @@ class Webamp {
* @returns An "unsubscribe" function. Useful if at some point in the future you want to stop listening to these events.
*/
onClose(cb: () => void): () => void {
return this._actionEmitter.on(CLOSE_WINAMP, cb);
return this._actionEmitter.on("CLOSE_WINAMP", cb);
}
/**
@ -428,7 +417,7 @@ class Webamp {
* @returns An "unsubscribe" function. Useful if at some point in the future you want to stop listening to these events.
*/
onMinimize(cb: () => void): () => void {
return this._actionEmitter.on(MINIMIZE_WINAMP, cb);
return this._actionEmitter.on("MINIMIZE_WINAMP", cb);
}
/**