From 18b23d6a176b123d80b4f96555c8ef0ee6f413b9 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sun, 17 Feb 2019 21:06:16 -0800 Subject: [PATCH] Progress --- js/actionCreators/index.ts | 4 +- js/actionCreators/milkdrop.ts | 102 ++++++++++-------- .../MilkdropWindow/PresetOverlay.tsx | 7 +- js/components/MilkdropWindow/index.tsx | 32 +++--- js/fileUtils.ts | 13 +++ js/reducers/milkdrop.ts | 2 - js/types.ts | 4 +- 7 files changed, 100 insertions(+), 64 deletions(-) diff --git a/js/actionCreators/index.ts b/js/actionCreators/index.ts index d9880f89..c429f905 100644 --- a/js/actionCreators/index.ts +++ b/js/actionCreators/index.ts @@ -99,7 +99,9 @@ export { requestPresetAtIndex, selectRandomPreset, selectNextPreset, - togglePresetOverlay + togglePresetOverlay, + appendPresetFileList, + handlePresetDrop } from "./milkdrop"; import * as Selectors from "../selectors"; diff --git a/js/actionCreators/milkdrop.ts b/js/actionCreators/milkdrop.ts index 0cd84065..a0bd61ee 100644 --- a/js/actionCreators/milkdrop.ts +++ b/js/actionCreators/milkdrop.ts @@ -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 { diff --git a/js/components/MilkdropWindow/PresetOverlay.tsx b/js/components/MilkdropWindow/PresetOverlay.tsx index acb9c872..cef80a02 100644 --- a/js/components/MilkdropWindow/PresetOverlay.tsx +++ b/js/components/MilkdropWindow/PresetOverlay.tsx @@ -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 { } // 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)) }; } diff --git a/js/components/MilkdropWindow/index.tsx b/js/components/MilkdropWindow/index.tsx index a8b36f4e..f0a9b42a 100644 --- a/js/components/MilkdropWindow/index.tsx +++ b/js/components/MilkdropWindow/index.tsx @@ -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) => void + ): () => void; } type Props = StateProps & DispatchProps & OwnProps; @@ -79,18 +79,25 @@ function Milkdrop(props: Props) { ); return () => clearImmediate(intervalId); }, [props.selectRandomPreset]); + return ( {({ height, width }: { width: number; height: number }) => ( - {props.overlay && ( - + {props.overlay && ( + + )} + - )} - + )} @@ -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( diff --git a/js/fileUtils.ts b/js/fileUtils.ts index b63fd32f..66cb68ef 100644 --- a/js/fileUtils.ts +++ b/js/fileUtils.ts @@ -66,6 +66,19 @@ export async function genArrayBufferFromFileReference( }); } +export async function genStringFromFileReference( + fileReference: File +): Promise { + 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; diff --git a/js/reducers/milkdrop.ts b/js/reducers/milkdrop.ts index 29c05ae4..935980b1 100644 --- a/js/reducers/milkdrop.ts +++ b/js/reducers/milkdrop.ts @@ -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}`); } }) ) diff --git a/js/types.ts b/js/types.ts index 376418d3..5247a2c9 100644 --- a/js/types.ts +++ b/js/types.ts @@ -138,7 +138,7 @@ type ButterchurnPresetJson = { definition: Object; }; -type LazyButterchurnPresetJson = { +export type LazyButterchurnPresetJson = { type: "LAZY_BUTTERCHURN_JSON"; name: string; getDefinition: () => Promise; @@ -483,7 +483,7 @@ export type Action = } | { type: "GOT_BUTTERCHURN_PRESETS"; - presets: PresetDefinition[]; + presets: Preset[]; } | { type: "GOT_BUTTERCHURN";