mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-21 09:09:01 +00:00
Select previous preset
This commit is contained in:
parent
02aa688ed3
commit
8559dd7257
8 changed files with 115 additions and 194 deletions
|
|
@ -99,9 +99,11 @@ export {
|
|||
requestPresetAtIndex,
|
||||
selectRandomPreset,
|
||||
selectNextPreset,
|
||||
selectPreviousPreset,
|
||||
togglePresetOverlay,
|
||||
appendPresetFileList,
|
||||
handlePresetDrop
|
||||
handlePresetDrop,
|
||||
loadPresets
|
||||
} from "./milkdrop";
|
||||
|
||||
import * as Selectors from "../selectors";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import {
|
|||
GOT_BUTTERCHURN,
|
||||
SELECT_PRESET_AT_INDEX,
|
||||
RESOLVE_PRESET_AT_INDEX,
|
||||
TOGGLE_PRESET_OVERLAY
|
||||
TOGGLE_PRESET_OVERLAY,
|
||||
PRESET_REQUESTED
|
||||
} from "../actionTypes";
|
||||
import * as Selectors from "../selectors";
|
||||
import {
|
||||
|
|
@ -61,7 +62,9 @@ export function loadPresets(presets: StatePreset[]): Dispatchable {
|
|||
return (dispatch, getState) => {
|
||||
const presetLength = getState().milkdrop.presets.length;
|
||||
dispatch({ type: GOT_BUTTERCHURN_PRESETS, presets });
|
||||
dispatch(requestPresetAtIndex(presetLength, TransitionType.IMMEDIATE));
|
||||
dispatch(
|
||||
requestPresetAtIndex(presetLength, TransitionType.IMMEDIATE, true)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +82,7 @@ export function appendPresetFileList(fileList: FileList): Dispatchable {
|
|||
return {
|
||||
type: "UNRESOLVED",
|
||||
name: file.name.slice(0, file.name.length - MILK_EXT.length),
|
||||
getPreset: async () => convertPreset(file)
|
||||
getPreset: () => convertPreset(file)
|
||||
} as StatePreset;
|
||||
} else if (filename.endsWith(JSON_EXT)) {
|
||||
return {
|
||||
|
|
@ -106,29 +109,50 @@ export function selectNextPreset(
|
|||
transitionType: TransitionType = TransitionType.DEFAULT
|
||||
): Dispatchable {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const currentPresetIndex = Selectors.getCurrentPresetIndex(state);
|
||||
const currentPresetIndex = Selectors.getCurrentPresetIndex(getState());
|
||||
if (currentPresetIndex == null) {
|
||||
return;
|
||||
}
|
||||
const nextPresetIndex = currentPresetIndex + 1;
|
||||
dispatch(requestPresetAtIndex(nextPresetIndex, transitionType));
|
||||
dispatch(requestPresetAtIndex(nextPresetIndex, transitionType, true));
|
||||
};
|
||||
}
|
||||
|
||||
export function selectRandomPreset(): Dispatchable {
|
||||
export function selectPreviousPreset(
|
||||
transitionType: TransitionType = TransitionType.DEFAULT
|
||||
): Dispatchable {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const { presetHistory } = state.milkdrop;
|
||||
if (presetHistory.length < 1) {
|
||||
return;
|
||||
}
|
||||
// Awkward. We do -2 becuase the the last track is the current track.
|
||||
const lastPresetIndex = presetHistory[presetHistory.length - 2];
|
||||
|
||||
dispatch(requestPresetAtIndex(lastPresetIndex, transitionType, false));
|
||||
};
|
||||
}
|
||||
|
||||
export function selectRandomPreset(
|
||||
transitionType: TransitionType = TransitionType.DEFAULT
|
||||
): Dispatchable {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
// TODO: Make this a selector.
|
||||
const randomIndex = Math.floor(
|
||||
Math.random() * state.milkdrop.presets.length
|
||||
);
|
||||
dispatch(requestPresetAtIndex(randomIndex, TransitionType.DEFAULT));
|
||||
dispatch(requestPresetAtIndex(randomIndex, transitionType, true));
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Technically there's a race here. If you request two presets in a row, the
|
||||
// first one may resolve before the second.
|
||||
export function requestPresetAtIndex(
|
||||
index: number,
|
||||
transitionType: TransitionType
|
||||
transitionType: TransitionType,
|
||||
addToHistory: boolean
|
||||
): Dispatchable {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
|
@ -137,15 +161,14 @@ export function requestPresetAtIndex(
|
|||
// Index might be out of range.
|
||||
return;
|
||||
}
|
||||
dispatch({ type: PRESET_REQUESTED, index, addToHistory });
|
||||
switch (preset.type) {
|
||||
case "RESOLVED":
|
||||
dispatch({ type: SELECT_PRESET_AT_INDEX, index, transitionType });
|
||||
return;
|
||||
case "UNRESOLVED":
|
||||
const json = await preset.getPreset();
|
||||
// What if the index has changed?
|
||||
// Perhaps we could hold a reference to the preset at the index before
|
||||
// we await and confirm that it hasn't changed after the await?
|
||||
// TODO: Ensure that this works correctly even if requests resolve out of order
|
||||
dispatch({ type: RESOLVE_PRESET_AT_INDEX, index, json });
|
||||
dispatch({ type: SELECT_PRESET_AT_INDEX, index, transitionType });
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -82,3 +82,4 @@ export const GOT_BUTTERCHURN = "GOT_BUTTERCHURN";
|
|||
export const RESOLVE_PRESET_AT_INDEX = "RESOLVE_PRESET_AT_INDEX";
|
||||
export const SELECT_PRESET_AT_INDEX = "SELECT_PRESET_AT_INDEX";
|
||||
export const TOGGLE_PRESET_OVERLAY = "TOGGLE_PRESET_OVERLAY";
|
||||
export const PRESET_REQUESTED = "PRESET_REQUESTED";
|
||||
|
|
|
|||
|
|
@ -1,173 +0,0 @@
|
|||
import { mockRandom } from "jest-mock-random";
|
||||
import Presets from "../Presets";
|
||||
|
||||
let presets;
|
||||
beforeEach(() => {
|
||||
mockRandom([0.0]);
|
||||
presets = new Presets({
|
||||
keys: ["a", "b"],
|
||||
initialPresets: { a: "Preset A", b: "Preset B" },
|
||||
randomize: true
|
||||
});
|
||||
});
|
||||
|
||||
describe("initialize", () => {
|
||||
beforeEach(() => {});
|
||||
test("picks a random value", () => {
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
});
|
||||
|
||||
test("picks another random value", () => {
|
||||
mockRandom([0.9]);
|
||||
presets = new Presets({
|
||||
keys: ["a", "b"],
|
||||
initialPresets: { a: "Preset A", b: "Preset B" },
|
||||
randomize: true
|
||||
});
|
||||
expect(presets.getCurrent()).toBe("Preset B");
|
||||
});
|
||||
|
||||
test("picks its random value from the initial presets", () => {
|
||||
presets = new Presets({
|
||||
keys: ["a", "b", "c", "d", "e", "f", "g", "h"],
|
||||
initialPresets: { a: "Preset A" },
|
||||
randomize: true
|
||||
});
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
});
|
||||
});
|
||||
|
||||
describe("next", () => {
|
||||
test("picks a 'random' preset", async () => {
|
||||
mockRandom([0.9]);
|
||||
presets.next();
|
||||
expect(presets.getCurrent()).toBe("Preset B");
|
||||
|
||||
mockRandom([0.0]);
|
||||
presets.next();
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
});
|
||||
|
||||
test("picks the next key", async () => {
|
||||
presets.setRandomize(false);
|
||||
presets.next();
|
||||
expect(presets.getCurrent()).toBe("Preset B");
|
||||
});
|
||||
|
||||
test("wraps around", async () => {
|
||||
presets.setRandomize(false);
|
||||
presets.next();
|
||||
presets.next();
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
});
|
||||
});
|
||||
|
||||
describe("previous", () => {
|
||||
test("picks the previous key", async () => {
|
||||
presets.setRandomize(false);
|
||||
presets.next();
|
||||
presets.previous();
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
});
|
||||
|
||||
test("does nothing when you are on the first item", async () => {
|
||||
presets.previous();
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
});
|
||||
|
||||
test("can do sequential previouses", async () => {
|
||||
mockRandom([0.0]);
|
||||
presets = new Presets({
|
||||
keys: ["a", "b", "c", "d"],
|
||||
initialPresets: {
|
||||
a: "Preset A",
|
||||
b: "Preset B",
|
||||
c: "Preset C",
|
||||
d: "Preset D"
|
||||
},
|
||||
randomize: false
|
||||
});
|
||||
presets.next(); // b
|
||||
presets.next(); // c
|
||||
presets.next(); // d
|
||||
presets.previous(); // c
|
||||
presets.previous(); // b
|
||||
presets.previous(); // a
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
});
|
||||
|
||||
test("will successfully resolve an unloaded preset", async () => {
|
||||
mockRandom([0.0]);
|
||||
presets = new Presets({
|
||||
keys: ["a", "b", "c", "d"],
|
||||
initialPresets: {
|
||||
a: "Preset A"
|
||||
},
|
||||
randomize: false,
|
||||
getRest: () =>
|
||||
Promise.resolve({
|
||||
b: "Preset B",
|
||||
c: "Preset C"
|
||||
})
|
||||
});
|
||||
presets.next(); // b
|
||||
presets.next(); // c
|
||||
const final = await presets.previous(); // b
|
||||
expect(final).toBe("Preset B");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRest", () => {
|
||||
beforeEach(() => {
|
||||
mockRandom([0.0]);
|
||||
presets = new Presets({
|
||||
keys: ["a", "b"],
|
||||
initialPresets: { a: "Preset A" },
|
||||
getRest: () =>
|
||||
Promise.resolve({
|
||||
b: "Preset B"
|
||||
})
|
||||
});
|
||||
});
|
||||
test("will get the rest of the presets if needed", async () => {
|
||||
mockRandom([0.9]);
|
||||
const resolved = presets.next();
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
await resolved;
|
||||
expect(presets.getCurrent()).toBe("Preset B");
|
||||
});
|
||||
|
||||
test("next (loading), previous brings us back to where we started", async () => {
|
||||
presets.setRandomize(false);
|
||||
presets.next();
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
await presets.previous();
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectIndex", () => {
|
||||
test("adds an entry to the history", async () => {
|
||||
presets.selectIndex(1);
|
||||
presets.previous();
|
||||
expect(presets.getCurrent()).toBe("Preset A");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCurrentIndex", () => {
|
||||
test("gets the active index while loading", async () => {
|
||||
presets = new Presets({
|
||||
keys: ["a", "b"],
|
||||
initialPresets: { a: "Preset A" },
|
||||
randomize: false,
|
||||
getRest: () =>
|
||||
Promise.resolve({
|
||||
b: "Preset B"
|
||||
})
|
||||
});
|
||||
const resolved = presets.next();
|
||||
expect(presets.getCurrentIndex()).toBe(0);
|
||||
await resolved;
|
||||
expect(presets.getCurrentIndex()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -30,6 +30,7 @@ interface DispatchProps {
|
|||
selectRandomPreset(): void;
|
||||
handlePresetDrop(e: React.DragEvent): void;
|
||||
selectNextPreset(transitionType?: TransitionType): void;
|
||||
selectPreviousPreset(transitionType?: TransitionType): void;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
|
|
@ -46,12 +47,13 @@ function Milkdrop(props: Props) {
|
|||
// Handle keyboard events
|
||||
useEffect(() => {
|
||||
return props.onFocusedKeyDown(e => {
|
||||
console.log(e.keyCode);
|
||||
switch (e.keyCode) {
|
||||
case 32: // spacebar
|
||||
props.selectNextPreset();
|
||||
break;
|
||||
case 8: // backspace
|
||||
// this._prevPreset(0);
|
||||
props.selectPreviousPreset(TransitionType.IMMEDIATE);
|
||||
break;
|
||||
case 72: // H
|
||||
props.selectNextPreset(TransitionType.IMMEDIATE);
|
||||
|
|
@ -141,7 +143,9 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
|||
selectRandomPreset: () => dispatch(Actions.selectRandomPreset()),
|
||||
handlePresetDrop: e => dispatch(Actions.handlePresetDrop(e)),
|
||||
selectNextPreset: (transitionType?: TransitionType) =>
|
||||
dispatch(Actions.selectNextPreset(transitionType))
|
||||
dispatch(Actions.selectNextPreset(transitionType)),
|
||||
selectPreviousPreset: (transitionType?: TransitionType) =>
|
||||
dispatch(Actions.selectPreviousPreset(transitionType))
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
|||
52
js/reducers/milkdrop.test.js
Normal file
52
js/reducers/milkdrop.test.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import Emitter from "../emitter";
|
||||
import createStore from "../store";
|
||||
import * as Actions from "../actionCreators";
|
||||
import * as Selectors from "../selectors";
|
||||
|
||||
// Actually tests action creators too
|
||||
const media = {
|
||||
addEventListener: jest.fn(),
|
||||
setVolume: jest.fn(),
|
||||
setBalance: jest.fn(),
|
||||
setPreamp: jest.fn(),
|
||||
getAnalyser: () => null,
|
||||
on: jest.fn()
|
||||
};
|
||||
|
||||
const store = createStore(media, new Emitter());
|
||||
|
||||
test("handles history correctly", () => {
|
||||
const mockPreset = [
|
||||
{ type: "RESOLVED", name: "First", preset: {} },
|
||||
{ type: "RESOLVED", name: "Second", preset: {} }
|
||||
];
|
||||
// Check initial state
|
||||
expect(store.getState().milkdrop.presetHistory).toEqual([]);
|
||||
expect(Selectors.getCurrentPreset(store.getState())).toBe(null);
|
||||
expect(Selectors.getCurrentPresetIndex(store.getState())).toBe(null);
|
||||
|
||||
// Load some presets
|
||||
store.dispatch(Actions.loadPresets(mockPreset));
|
||||
expect(store.getState().milkdrop.presetHistory).toEqual([0]);
|
||||
expect(Selectors.getCurrentPresetIndex(store.getState())).toBe(0);
|
||||
|
||||
// Select next
|
||||
store.dispatch(Actions.selectNextPreset());
|
||||
expect(store.getState().milkdrop.presetHistory).toEqual([0, 1]);
|
||||
expect(Selectors.getCurrentPresetIndex(store.getState())).toBe(1);
|
||||
|
||||
// Select previous
|
||||
store.dispatch(Actions.selectPreviousPreset());
|
||||
expect(store.getState().milkdrop.presetHistory).toEqual([0]);
|
||||
expect(Selectors.getCurrentPresetIndex(store.getState())).toBe(0);
|
||||
|
||||
// Next again
|
||||
store.dispatch(Actions.selectNextPreset());
|
||||
expect(store.getState().milkdrop.presetHistory).toEqual([0, 1]);
|
||||
expect(Selectors.getCurrentPresetIndex(store.getState())).toBe(1);
|
||||
|
||||
// Previous again
|
||||
store.dispatch(Actions.selectPreviousPreset());
|
||||
expect(store.getState().milkdrop.presetHistory).toEqual([0]);
|
||||
expect(Selectors.getCurrentPresetIndex(store.getState())).toBe(0);
|
||||
});
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import { Action, PresetId, StatePreset } from "../types";
|
||||
import { Action, StatePreset } from "../types";
|
||||
import {
|
||||
SET_MILKDROP_DESKTOP,
|
||||
GOT_BUTTERCHURN_PRESETS,
|
||||
GOT_BUTTERCHURN,
|
||||
RESOLVE_PRESET_AT_INDEX,
|
||||
SELECT_PRESET_AT_INDEX,
|
||||
TOGGLE_PRESET_OVERLAY
|
||||
TOGGLE_PRESET_OVERLAY,
|
||||
PRESET_REQUESTED
|
||||
} from "../actionTypes";
|
||||
import * as Utils from "../utils";
|
||||
import { TransitionType } from "../types";
|
||||
|
|
@ -13,7 +14,7 @@ import { TransitionType } from "../types";
|
|||
export interface MilkdropState {
|
||||
desktop: boolean;
|
||||
overlay: boolean;
|
||||
presetOrder: PresetId[];
|
||||
presetHistory: number[];
|
||||
presets: StatePreset[];
|
||||
currentPresetIndex: number | null;
|
||||
butterchurn: any;
|
||||
|
|
@ -23,7 +24,7 @@ export interface MilkdropState {
|
|||
const defaultMilkdropState = {
|
||||
desktop: false,
|
||||
overlay: false,
|
||||
presetOrder: [],
|
||||
presetHistory: [],
|
||||
presets: [],
|
||||
currentPresetIndex: null,
|
||||
butterchurn: null,
|
||||
|
|
@ -44,6 +45,18 @@ export const milkdrop = (
|
|||
...state,
|
||||
presets: state.presets.concat(action.presets)
|
||||
};
|
||||
case PRESET_REQUESTED:
|
||||
if (action.addToHistory) {
|
||||
return {
|
||||
...state,
|
||||
presetHistory: [...state.presetHistory, action.index]
|
||||
};
|
||||
}
|
||||
console.log(state.presetHistory);
|
||||
return {
|
||||
...state,
|
||||
presetHistory: state.presetHistory.slice(0, -1)
|
||||
};
|
||||
case RESOLVE_PRESET_AT_INDEX:
|
||||
const preset = state.presets[action.index];
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -141,8 +141,6 @@ export type StatePreset =
|
|||
| { type: "RESOLVED"; name: string; preset: Object }
|
||||
| { type: "UNRESOLVED"; name: string; getPreset: () => Promise<Object> };
|
||||
|
||||
export type PresetId = string;
|
||||
|
||||
export interface ButterchurnOptions {
|
||||
getPresets(): Promise<Preset[]>;
|
||||
importButterchurn(): Promise<any>;
|
||||
|
|
@ -468,6 +466,7 @@ export type Action =
|
|||
type: "SET_MILKDROP_DESKTOP";
|
||||
enabled: boolean;
|
||||
}
|
||||
| { type: "PRESET_REQUESTED"; index: number; addToHistory: boolean }
|
||||
| {
|
||||
type: "GOT_BUTTERCHURN_PRESETS";
|
||||
presets: StatePreset[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue