webamp/js/actionCreators/milkdrop.ts
2019-05-01 22:42:38 -07:00

202 lines
6.1 KiB
TypeScript

import {
GOT_BUTTERCHURN_PRESETS,
GOT_BUTTERCHURN,
SELECT_PRESET_AT_INDEX,
RESOLVE_PRESET_AT_INDEX,
PRESET_REQUESTED,
TOGGLE_RANDOMIZE_PRESETS,
TOGGLE_PRESET_CYCLING,
SCHEDULE_MILKDROP_MESSAGE,
} from "../actionTypes";
import * as Selectors from "../selectors";
import {
TransitionType,
Preset,
ButterchurnOptions,
StatePreset,
Thunk,
Action,
} from "../types";
import * as FileUtils from "../fileUtils";
function normalizePresetTypes(preset: Preset): StatePreset {
const { name } = preset;
if ("butterchurnPresetObject" in preset) {
return {
type: "RESOLVED",
name,
preset: preset.butterchurnPresetObject,
};
} else if ("getButterchrunPresetObject" in preset) {
return {
type: "UNRESOLVED",
name,
getPreset: preset.getButterchrunPresetObject,
};
} else if ("butterchurnPresetUrl" in preset) {
return {
type: "UNRESOLVED",
name,
getPreset: async () => {
const resp = await fetch(preset.butterchurnPresetUrl);
return resp.json();
},
};
}
throw new Error("Invalid preset object");
}
export function initializePresets(presetOptions: ButterchurnOptions): Thunk {
return async dispatch => {
const { getPresets, importButterchurn } = presetOptions;
importButterchurn().then(butterchurn => {
dispatch({ type: GOT_BUTTERCHURN, butterchurn: butterchurn.default });
});
const presets = await getPresets();
const normalizePresets = presets.map(normalizePresetTypes);
dispatch(loadPresets(normalizePresets));
};
}
export function loadPresets(presets: StatePreset[]): Thunk {
return (dispatch, getState) => {
const state = getState();
const presetsLength = state.milkdrop.presets.length;
dispatch({ type: GOT_BUTTERCHURN_PRESETS, presets });
if (presetsLength === 0 && Selectors.getRandomizePresets(state)) {
dispatch(selectRandomPreset());
} else {
dispatch(
requestPresetAtIndex(presetsLength, TransitionType.IMMEDIATE, true)
);
}
};
}
export function appendPresetFileList(fileList: FileList): Thunk {
return async (dispatch, getState, { convertPreset }) => {
const presets: StatePreset[] = Array.from(fileList)
.map(file => {
const JSON_EXT = ".json";
const MILK_EXT = ".milk";
const filename = file.name.toLowerCase();
if (filename.endsWith(MILK_EXT)) {
if (convertPreset == null) {
throw new Error("Invalid type");
}
return {
type: "UNRESOLVED",
name: file.name.slice(0, file.name.length - MILK_EXT.length),
getPreset: () => convertPreset(file),
} as StatePreset;
} else if (filename.endsWith(JSON_EXT)) {
return {
type: "UNRESOLVED",
name: file.name.slice(0, file.name.length - JSON_EXT.length),
getPreset: async () => {
const str = await FileUtils.genStringFromFileReference(file);
// TODO: How should we handle the case where json parsing fails?
return JSON.parse(str);
},
} as StatePreset;
}
console.error("Invalid type preset when loading directory");
return null as never;
})
.filter(Boolean);
dispatch(loadPresets(presets));
};
}
export function selectNextPreset(
transitionType: TransitionType = TransitionType.DEFAULT
): Thunk {
return (dispatch, getState) => {
const state = getState();
if (Selectors.getRandomizePresets(state)) {
return dispatch(selectRandomPreset(transitionType));
}
const currentPresetIndex = Selectors.getCurrentPresetIndex(state);
if (currentPresetIndex == null) {
return;
}
const nextPresetIndex = currentPresetIndex + 1;
dispatch(requestPresetAtIndex(nextPresetIndex, transitionType, true));
};
}
export function selectPreviousPreset(
transitionType: TransitionType = TransitionType.DEFAULT
): Thunk {
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
): Thunk {
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, 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,
addToHistory: boolean
): Thunk {
return async (dispatch, getState) => {
const state = getState();
const preset = state.milkdrop.presets[index];
if (preset == null) {
// 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();
// 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;
}
};
}
export function handlePresetDrop(e: React.DragEvent): Thunk {
return appendPresetFileList(e.dataTransfer.files);
}
export function toggleRandomizePresets(): Action {
return { type: TOGGLE_RANDOMIZE_PRESETS };
}
export function togglePresetCycling(): Action {
return { type: TOGGLE_PRESET_CYCLING };
}
export function scheduleMilkdropMessage(message: string): Action {
return { type: SCHEDULE_MILKDROP_MESSAGE, message };
}