mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-24 02:36:00 +00:00
202 lines
6.1 KiB
TypeScript
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 };
|
|
}
|