This commit is contained in:
Jordan Eldredge 2019-02-17 21:06:16 -08:00
parent 98ca95749c
commit 18b23d6a17
7 changed files with 100 additions and 64 deletions

View file

@ -99,7 +99,9 @@ export {
requestPresetAtIndex,
selectRandomPreset,
selectNextPreset,
togglePresetOverlay
togglePresetOverlay,
appendPresetFileList,
handlePresetDrop
} from "./milkdrop";
import * as Selectors from "../selectors";

View file

@ -6,7 +6,13 @@ import {
TOGGLE_PRESET_OVERLAY
} from "../actionTypes";
import * as Selectors from "../selectors";
import { Dispatchable, TransitionType } from "../types";
import {
Dispatchable,
TransitionType,
Preset,
LazyButterchurnPresetJson
} from "../types";
import * as FileUtils from "../fileUtils";
export function initializePresets(presetOptions: any): Dispatchable {
return async dispatch => {
@ -18,7 +24,7 @@ export function initializePresets(presetOptions: any): Dispatchable {
} = await loadInitialDependencies();
dispatch({ type: GOT_BUTTERCHURN, butterchurn });
const presets = presetKeys.map((key: string) => {
const presets: Preset[] = presetKeys.map((key: string) => {
if (minimalPresets[key] != null) {
return {
type: "BUTTERCHURN_JSON",
@ -37,14 +43,54 @@ export function initializePresets(presetOptions: any): Dispatchable {
};
});
dispatch({ type: GOT_BUTTERCHURN_PRESETS, presets });
dispatch(requestPresetAtIndex(0, TransitionType.IMMEDIATE));
dispatch(loadPresets(presets));
};
}
export function appendPresetFileList(presets: FileList[]): Dispatchable {
return async dispatch => {
export function loadPresets(presets: Preset[]): Dispatchable {
return (dispatch, getState) => {
const presetLength = getState().milkdrop.presets.length;
dispatch({ type: GOT_BUTTERCHURN_PRESETS, presets });
dispatch(requestPresetAtIndex(presetLength, TransitionType.IMMEDIATE));
};
}
export function appendPresetFileList(fileList: FileList): Dispatchable {
return async dispatch => {
const presets: Preset[] = Array.from(fileList).map(file => {
const JSON_EXT = ".json";
const MILK_EXT = ".milk";
const filename = file.name.toLowerCase();
if (filename.endsWith(MILK_EXT)) {
// Not sure why we need this type definition.
const lazy: LazyButterchurnPresetJson = {
type: "LAZY_BUTTERCHURN_JSON",
name: file.name.slice(0, file.name.length - MILK_EXT.length),
getDefinition: async () => {
// TODO: Post this blob to the url end point and get the json back
return {};
}
};
throw new Error(".milk preset support not yet implemented");
return lazy;
} else if (filename.endsWith(JSON_EXT)) {
// Not sure why we need this type definition.
const lazy: LazyButterchurnPresetJson = {
type: "LAZY_BUTTERCHURN_JSON",
name: file.name.slice(0, file.name.length - JSON_EXT.length),
getDefinition: async () => {
const str = await FileUtils.genStringFromFileReference(file);
// TODO: How should we handle the case where json parsing fails?
return JSON.parse(str);
}
};
return lazy;
} else {
throw new Error("Invalid type");
}
});
dispatch(loadPresets(presets));
// TODO: Select the first of these presets
};
}
@ -88,6 +134,8 @@ export function requestPresetAtIndex(
case "LAZY_BUTTERCHURN_JSON":
const json = await preset.getDefinition();
// 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?
dispatch({ type: RESOLVE_PRESET_AT_INDEX, index, json });
dispatch({ type: SELECT_PRESET_AT_INDEX, index, transitionType });
return;
@ -95,45 +143,9 @@ export function requestPresetAtIndex(
};
}
function _presetNameFromURL(url: string): string {
try {
const urlParts = url.split("/");
const lastPart = urlParts[urlParts.length - 1];
const presetName = lastPart.substring(0, lastPart.length - 5); // remove .milk or .json
return decodeURIComponent(presetName);
} catch (e) {
// if something goes wrong parsing url, just use url as the preset name
console.error(e);
return url;
}
}
async function _fetchPreset(
presetUrl: string,
{ isButterchurn }: { isButterchurn: boolean }
) {
const response = await fetch(presetUrl);
if (!response.ok) {
console.error(response.statusText);
alert(`Unable to load MilkDrop preset from ${presetUrl}`);
return null;
}
const presetName = _presetNameFromURL(presetUrl);
let preset = null;
if (isButterchurn) {
try {
preset = await response.json();
} catch (e) {
console.error(e);
alert(`Failed to parse MilkDrop preset from ${presetUrl}`);
return null;
}
} else {
preset = { file: await response.blob() };
}
return { [presetName]: preset };
export function handlePresetDrop(e: React.DragEvent): Dispatchable {
// TODO: Ensure we actually select the new preset.
return appendPresetFileList(e.dataTransfer.files);
}
export function togglePresetOverlay(): Dispatchable {

View file

@ -46,6 +46,7 @@ interface StateProps {
interface DispatchProps {
requestPresetAtIndex(i: number): void;
togglePresetOverlay(): void;
appendPresetFileList(fileList: FileList): void;
}
interface OwnProps {
@ -168,7 +169,7 @@ class PresetOverlay extends React.Component<Props, State> {
}
// TODO: Technically there is a race condition here, since the component
// could get unmounted before the promise resolves.
this.props.loadPresets(fileReferences);
this.props.appendPresetFileList(fileReferences);
}
render() {
@ -226,7 +227,9 @@ function mapDispatchToProps(dispatch: Dispatch): DispatchProps {
requestPresetAtIndex: (i: number) => {
dispatch(Actions.requestPresetAtIndex(i, TransitionType.DEFAULT));
},
togglePresetOverlay: () => dispatch(Actions.togglePresetOverlay())
togglePresetOverlay: () => dispatch(Actions.togglePresetOverlay()),
appendPresetFileList: (fileList: FileList) =>
dispatch(Actions.appendPresetFileList(fileList))
};
}

View file

@ -1,20 +1,17 @@
// @ts-ignore #hook-types
import React, { useEffect, useState, useRef } from "react";
import { connect } from "react-redux";
import screenfull from "screenfull";
import ContextMenuWrapper from "../ContextMenuWrapper";
import GenWindow from "../GenWindow";
import { WINDOWS, VISUALIZERS } from "../../constants";
import { WINDOWS } from "../../constants";
import * as Selectors from "../../selectors";
import * as Actions from "../../actionCreators";
import MilkdropContextMenu from "./MilkdropContextMenu";
import Desktop from "./Desktop";
import { AppState, Dispatch } from "../../types";
import Visualizer from "./Visualizer";
import "../../../css/milkdrop-window.css";
import Background from "./Background";
import PresetOverlay from "./PresetOverlay";
import DropTarget from "../DropTarget";
const MILLISECONDS_BETWEEN_PRESET_TRANSITIONS = 15000;
@ -28,11 +25,14 @@ interface DispatchProps {
toggleDesktop(): void;
togglePresetOverlay(): void;
selectRandomPreset(): void;
handlePresetDrop(e: React.DragEvent): void;
}
interface OwnProps {
analyser: AnalyserNode;
onFocusedKeyDown(cb: (e: KeyboardEvent) => void): () => void;
onFocusedKeyDown(
cb: (e: React.KeyboardEvent<HTMLDivElement>) => void
): () => void;
}
type Props = StateProps & DispatchProps & OwnProps;
@ -79,18 +79,25 @@ function Milkdrop(props: Props) {
);
return () => clearImmediate(intervalId);
}, [props.selectRandomPreset]);
return (
<GenWindow title={"Milkdrop"} windowId={WINDOWS.MILKDROP}>
{({ height, width }: { width: number; height: number }) => (
<Background>
{props.overlay && (
<PresetOverlay
<DropTarget handleDrop={props.handlePresetDrop}>
{props.overlay && (
<PresetOverlay
width={width}
height={height}
onFocusedKeyDown={props.onFocusedKeyDown}
/>
)}
<Visualizer
width={width}
height={height}
onFocusedKeyDown={props.onFocusedKeyDown}
analyser={props.analyser}
/>
)}
<Visualizer width={width} height={height} analyser={props.analyser} />
</DropTarget>
</Background>
)}
</GenWindow>
@ -106,7 +113,8 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
closeWindow: () => dispatch(Actions.closeWindow(WINDOWS.MILKDROP)),
toggleDesktop: () => dispatch(Actions.toggleMilkdropDesktop()),
togglePresetOverlay: () => dispatch(Actions.togglePresetOverlay()),
selectRandomPreset: () => dispatch(Actions.selectRandomPreset())
selectRandomPreset: () => dispatch(Actions.selectRandomPreset()),
handlePresetDrop: e => dispatch(Actions.handlePresetDrop(e))
});
export default connect(

View file

@ -66,6 +66,19 @@ export async function genArrayBufferFromFileReference(
});
}
export async function genStringFromFileReference(
fileReference: File
): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result as string);
};
reader.onerror = reject;
reader.readAsText(fileReference);
});
}
interface PromptForFileReferenceOptions {
accept?: string | null;
directory?: boolean;

View file

@ -48,8 +48,6 @@ export const milkdrop = (
case "BUTTERCHURN_JSON":
case "LAZY_BUTTERCHURN_JSON":
return preset;
default:
throw new Error(`Invalid preset type: ${preset.type}`);
}
})
)

View file

@ -138,7 +138,7 @@ type ButterchurnPresetJson = {
definition: Object;
};
type LazyButterchurnPresetJson = {
export type LazyButterchurnPresetJson = {
type: "LAZY_BUTTERCHURN_JSON";
name: string;
getDefinition: () => Promise<Object>;
@ -483,7 +483,7 @@ export type Action =
}
| {
type: "GOT_BUTTERCHURN_PRESETS";
presets: PresetDefinition[];
presets: Preset[];
}
| {
type: "GOT_BUTTERCHURN";