diff --git a/js/actionCreators/milkdrop.ts b/js/actionCreators/milkdrop.ts
index f026284f..8a59547a 100644
--- a/js/actionCreators/milkdrop.ts
+++ b/js/actionCreators/milkdrop.ts
@@ -4,30 +4,47 @@ import Presets from "../components/MilkdropWindow/Presets";
export function initializePresets(presetOptions: any): Dispatchable {
return async dispatch => {
- const [
- { butterchurn, presetKeys, minimalPresets },
- initialPreset
- ] = await Promise.all([
- presetOptions.loadInitialDependencies(),
- _loadInitialPreset(presetOptions)
- ]);
+ const { loadInitialDependencies, loadNonMinimalPresets } = presetOptions;
+ const {
+ butterchurn,
+ presetKeys,
+ minimalPresets
+ } = await loadInitialDependencies();
- const presets = new Presets({
- keys: presetKeys,
- initialPresets: minimalPresets,
- getRest: presetOptions.loadNonMinimalPresets,
- presetConverterEndpoint: presetOptions.presetConverterEndpoint,
- loadConvertPreset: presetOptions.loadConvertPreset
+ const presetDefinitions = presetKeys.map((key: string) => {
+ if (minimalPresets[key] != null) {
+ return {
+ type: "BUTTERCHURN_JSON",
+ name: key,
+ definition: minimalPresets[key]
+ };
+ }
+ return async () => {
+ // TODO: Avoid a race where we try to resolve this promise more than once in parallel.
+ const nonMinimalPresets = await loadNonMinimalPresets();
+ return {
+ type: "BUTTERCHURN_JSON",
+ name: key,
+ definition: nonMinimalPresets[key]
+ };
+ };
});
- if (initialPreset) {
- const presetIndices = presets.addPresets(initialPreset);
- const presetIndex = presetIndices[0];
- await presets.selectIndex(presetIndex);
- }
+ dispatch({
+ type: "GOT_BUTTERCHUN_PRESET",
+ json: minimalPresets[presetKeys[1]]
+ });
+
+ setTimeout(() => {
+ console.log(minimalPresets[presetKeys[0]]);
+ dispatch({
+ type: "GOT_BUTTERCHUN_PRESET",
+ json: minimalPresets[presetKeys[0]]
+ });
+ }, 8000);
dispatch({ type: GOT_BUTTERCHURN, butterchurn });
- dispatch({ type: INITIALIZE_PRESETS, presets });
+ // dispatch({ type: INITIALIZE_PRESETS, presets });
};
}
@@ -44,25 +61,6 @@ function _presetNameFromURL(url: string): string {
}
}
-async function _loadInitialPreset({
- initialButterchurnPresetUrl,
- initialMilkdropPresetUrl
-}: {
- initialButterchurnPresetUrl: string;
- initialMilkdropPresetUrl: string;
-}) {
- if (initialButterchurnPresetUrl && initialMilkdropPresetUrl) {
- alert(
- "Unable to handle both milkdropPresetUrl and butterchurnPresetUrl. Please specify one or the other."
- );
- } else if (initialButterchurnPresetUrl) {
- return _fetchPreset(initialButterchurnPresetUrl, { isButterchurn: true });
- } else if (initialMilkdropPresetUrl) {
- return _fetchPreset(initialMilkdropPresetUrl, { isButterchurn: false });
- }
- return null;
-}
-
async function _fetchPreset(
presetUrl: string,
{ isButterchurn }: { isButterchurn: boolean }
diff --git a/js/components/MilkdropWindow/Milkdrop.js b/js/components/MilkdropWindow/Milkdrop.js
deleted file mode 100644
index d1f21875..00000000
--- a/js/components/MilkdropWindow/Milkdrop.js
+++ /dev/null
@@ -1,283 +0,0 @@
-import React from "react";
-import { connect } from "react-redux";
-import * as Selectors from "../../selectors";
-import DropTarget from "../DropTarget";
-import PresetOverlay from "./PresetOverlay";
-
-const USER_PRESET_TRANSITION_SECONDS = 5.7;
-const PRESET_TRANSITION_SECONDS = 2.7;
-const MILLISECONDS_BETWEEN_PRESET_TRANSITIONS = 15000;
-
-function transitionTimeFromTransitionType(transitionType) {
- switch (transitionType) {
- case "DEFAULT":
- return PRESET_TRANSITION_SECONDS;
- case "IMMEDIATE":
- return 0;
- case "USER_PRESET":
- return USER_PRESET_TRANSITION_SECONDS;
- }
-}
-
-class Milkdrop extends React.Component {
- constructor(props) {
- super(props);
- this.__debugState = "CONSTRUCTED";
- this.__updates = 0;
- this.state = {
- isFullscreen: false,
- presetOverlay: false
- };
- }
-
- async componentDidMount() {
- this._initializeIfNeeded();
- }
-
- async _initializeIfNeeded() {
- if (this.visualizer || !this.props.butterchurn || !this.props.presets) {
- return;
- }
- this.visualizer = this.props.butterchurn.createVisualizer(
- this.props.analyser.context,
- this._canvasNode,
- {
- width: this.props.width,
- height: this.props.height,
- meshWidth: 32,
- meshHeight: 24,
- pixelRatio: window.devicePixelRatio || 1
- }
- );
- if (this.visualizer == null) {
- // https://github.com/captbaritone/webamp/issues/731
- throw new Error("Visualizer not initialized. WAT.");
- }
- this.__debugState = "VISUALIZER_CREATED";
- this.visualizer.connectAudio(this.props.analyser);
- this.presetCycle = true;
- this.selectPreset(this.props.presets.getCurrent(), 0);
-
- // Kick off the animation loop
- const loop = () => {
- if (this.props.playing && this.props.isEnabledVisualizer) {
- this.visualizer.render();
- }
- this._animationFrameRequest = window.requestAnimationFrame(loop);
- };
- loop();
-
- this._unsubscribeFocusedKeyDown = this.props.onFocusedKeyDown(
- this._handleFocusedKeyboardInput
- );
- if (this.visualizer == null) {
- // https://github.com/captbaritone/webamp/issues/731
- throw new Error("Visualizer not initialized after await. WAT.");
- }
- this.__debugState = "MOUNT_ENDED";
- }
-
- componentWillUnmount() {
- this.__debugState = "WILL_UNMOUNT";
- this._pauseViz();
- this._stopCycling();
- if (this._unsubscribeFocusedKeyDown) {
- this._unsubscribeFocusedKeyDown();
- }
- }
-
- componentDidUpdate(prevProps) {
- this._initializeIfNeeded();
- if (
- this.props.width !== prevProps.width ||
- this.props.height !== prevProps.height
- ) {
- this.visualizer.setRendererSize(this.props.width, this.props.height);
- }
-
- if (this.props.trackTitle !== prevProps.trackTitle) {
- this.visualizer.launchSongTitleAnim(this.props.trackTitle);
- }
- }
-
- _pauseViz() {
- if (this._animationFrameRequest) {
- window.cancelAnimationFrame(this._animationFrameRequest);
- this._animationFrameRequest = null;
- }
- }
-
- _stopCycling() {
- if (this.cycleInterval) {
- clearInterval(this.cycleInterval);
- this.cycleInterval = null;
- }
- }
-
- _restartCycling() {
- this._stopCycling();
-
- if (this.presetCycle) {
- this.cycleInterval = setInterval(() => {
- this._nextPreset(PRESET_TRANSITION_SECONDS);
- }, MILLISECONDS_BETWEEN_PRESET_TRANSITIONS);
- }
- }
-
- _handleFocusedKeyboardInput = e => {
- switch (e.keyCode) {
- case 32: // spacebar
- this._nextPreset(USER_PRESET_TRANSITION_SECONDS);
- break;
- case 8: // backspace
- this._prevPreset(0);
- break;
- case 72: // H
- this._nextPreset(0);
- break;
- case 82: // R
- this.props.presets.toggleRandomize();
- break;
- case 76: // L
- this.setState({ presetOverlay: !this.state.presetOverlay });
- e.stopPropagation();
- break;
- case 84: // T
- this.visualizer.launchSongTitleAnim(this.props.trackTitle);
- e.stopPropagation();
- break;
- case 145: // scroll lock
- case 125: // F14 (scroll lock for OS X)
- this.presetCycle = !this.presetCycle;
- this._restartCycling();
- break;
- }
- };
-
- _addNewPresets(files) {
- const presetKeys = this.props.presets.getKeys();
- const presets = {};
- const presetIndices = [];
- files.forEach(file => {
- const fileName = file.name;
- const presetName = fileName.substring(0, fileName.length - 5); // remove .milk
- const presetIdx = presetKeys.indexOf(presetName);
- if (presetIdx >= 0) {
- presetIndices.push(presetIdx);
- } else {
- presets[presetName] = { file };
- }
- });
-
- if (Object.keys(presets).length > 0) {
- const filePresetIndices = this.props.presets.addPresets(presets);
- for (let j = filePresetIndices[0]; j < filePresetIndices[1]; j++) {
- presetIndices.push(j);
- }
- }
-
- return presetIndices;
- }
-
- async _handleDrop(e) {
- const files = e.dataTransfer.files;
- if (files.length > 0) {
- const milkFiles = Array.from(files).filter(file =>
- file.name.endsWith(".milk")
- );
- if (milkFiles.length === 0) {
- alert("Visualizer only supports .milk files");
- return;
- }
-
- const presetIndices = this._addNewPresets(milkFiles);
- this.selectPreset(
- await this.props.presets.selectIndex(presetIndices[0]),
- PRESET_TRANSITION_SECONDS
- );
- }
- }
-
- async _nextPreset(blendTime) {
- this.selectPreset(await this.props.presets.next(), blendTime);
- }
-
- async _prevPreset(blendTime) {
- this.selectPreset(await this.props.presets.previous(), blendTime);
- }
-
- selectPreset(preset, blendTime) {
- if (preset != null) {
- this.visualizer.loadPreset(preset, blendTime);
- this._restartCycling();
- // TODO: Kinda weird that we use the passed preset for the visualizer,
- // but set state to the current. Maybe we should just always use the curent..
- this.setState({ currentPreset: this.props.presets.getCurrentIndex() });
- }
- }
-
- async loadPresets(presetFiles) {
- const presets = {};
- const milkFiles = Array.from(presetFiles).filter(file =>
- file.name.endsWith(".milk")
- );
- for (let i = 0; i < milkFiles.length; i++) {
- const file = milkFiles[i];
- presets[file.name] = { file };
- }
- const numPresets = this.props.presets.loadPresets(presets);
- this.selectPreset(
- await this.props.presets.selectIndex(
- Math.floor(Math.random() * numPresets)
- ),
- PRESET_TRANSITION_SECONDS
- );
- }
-
- closePresetOverlay() {
- this.setState({ presetOverlay: false });
- }
-
- render() {
- if (this.props.presets == null) {
- return null;
- }
- return (
- this._handleDrop(e)}>
- {this.state.presetOverlay && (
- this.props.onFocusedKeyDown(listener)}
- selectPreset={async idx => {
- this.selectPreset(await this.props.presets.selectIndex(idx), 0);
- }}
- loadPresets={async presetFiles => this.loadPresets(presetFiles)}
- closeOverlay={() => this.closePresetOverlay()}
- />
- )}
-
- );
- }
-}
-
-const mapStateToProps = state => ({
- trackTitle: Selectors.getCurrentTrackDisplayName(state),
- presets: Selectors.getPresets(state),
- butterchurn: Selectors.getButterchurn(state),
- transitionType: Selectors.getPresetTransitionType(state)
-});
-
-export default connect(mapStateToProps)(Milkdrop);
diff --git a/js/components/MilkdropWindow/Visualizer.tsx b/js/components/MilkdropWindow/Visualizer.tsx
new file mode 100644
index 00000000..5d90ed89
--- /dev/null
+++ b/js/components/MilkdropWindow/Visualizer.tsx
@@ -0,0 +1,132 @@
+// @ts-ignore #hook-types
+import React, { useEffect, useState, useRef } from "react";
+import { connect } from "react-redux";
+import { VISUALIZERS } from "../../constants";
+import * as Selectors from "../../selectors";
+import { AppState, TransitionType } from "../../types";
+
+interface StateProps {
+ isEnabledVisualizer: boolean;
+ playing: boolean;
+ butterchurn: any;
+ trackTitle: string;
+ currentPreset: any;
+ transitionType: TransitionType;
+}
+
+interface OwnProps {
+ analyser: AnalyserNode;
+ height: number;
+ width: number;
+}
+
+type Props = StateProps & OwnProps;
+
+const TRANSITION_TYPE_DURATIONS = {
+ [TransitionType.DEFAULT]: 2.7,
+ [TransitionType.IMMEDIATE]: 0,
+ [TransitionType.USER_PRESET]: 5.7
+};
+
+function Visualizer(props: Props) {
+ const canvasRef = useRef(null);
+ const [visualizer, setVisualizer] = useState(null);
+
+ // Initialize the visualizer
+ useEffect(() => {
+ if (canvasRef.current == null || props.butterchurn == null) {
+ return;
+ }
+ if (visualizer != null) {
+ // Note: The visualizer does not offer anyway to clean itself up. So, we
+ // don't offer any way to recreate it. So, if you swap out the analyser
+ // node, or the canvas, that change won't be respected.
+ return;
+ }
+ const _visualizer = props.butterchurn.createVisualizer(
+ props.analyser.context,
+ canvasRef.current,
+ {
+ width: props.width,
+ height: props.height,
+ meshWidth: 32,
+ meshHeight: 24,
+ pixelRatio: window.devicePixelRatio || 1
+ }
+ );
+ _visualizer.connectAudio(props.analyser);
+ setVisualizer(_visualizer);
+ }, [canvasRef.current, props.butterchurn, props.analyser]);
+
+ // Ensure render size stays up to date
+ useEffect(() => {
+ if (visualizer == null) {
+ return;
+ }
+ visualizer.setRendererSize(props.width, props.height);
+ }, [visualizer, props.width, props.height]);
+
+ // Load presets when they change
+ useEffect(() => {
+ if (visualizer == null || props.currentPreset == null) {
+ return;
+ }
+ visualizer.loadPreset(
+ props.currentPreset,
+ TRANSITION_TYPE_DURATIONS[props.transitionType]
+ );
+ }, [visualizer, props.currentPreset]);
+
+ // Handle title animations
+ useEffect(() => {
+ if (visualizer == null || !props.trackTitle) {
+ return;
+ }
+ visualizer.launchSongTitleAnim(props.trackTitle);
+ }, [visualizer, props.trackTitle]);
+
+ const shouldAnimate = props.playing && props.isEnabledVisualizer;
+
+ // Kick off the animation loop
+ useEffect(() => {
+ if (!shouldAnimate || visualizer == null) {
+ return;
+ }
+ let animationFrameRequest = null;
+ const loop = () => {
+ visualizer.render();
+ animationFrameRequest = window.requestAnimationFrame(loop);
+ };
+ loop();
+ return () => {
+ if (animationFrameRequest != null) {
+ window.cancelAnimationFrame(animationFrameRequest);
+ }
+ };
+ }, [visualizer, shouldAnimate]);
+
+ return (
+
+ );
+}
+
+const mapStateToProps = (state: AppState): StateProps => ({
+ isEnabledVisualizer:
+ Selectors.getVisualizerStyle(state) === VISUALIZERS.MILKDROP,
+ playing: Selectors.getMediaIsPlaying(state),
+ butterchurn: Selectors.getButterchurn(state),
+ trackTitle: Selectors.getCurrentTrackDisplayName(state),
+ currentPreset: Selectors.getCurrentPreset(state),
+ transitionType: Selectors.getPresetTransitionType(state)
+});
+
+export default connect(mapStateToProps)(Visualizer);
diff --git a/js/components/MilkdropWindow/index.js b/js/components/MilkdropWindow/index.js
deleted file mode 100644
index 62c056f7..00000000
--- a/js/components/MilkdropWindow/index.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import React 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 * as Selectors from "../../selectors";
-import * as Actions from "../../actionCreators";
-import MilkdropContextMenu from "./MilkdropContextMenu";
-import Desktop from "./Desktop";
-
-import Milkdrop from "./Milkdrop";
-import Background from "./Background";
-
-import "../../../css/milkdrop-window.css";
-
-// This component is just responsible for loading dependencies.
-// This simplifies the inner component, by allowing
-// it to always assume that it has its dependencies.
-class PresetsLoader extends React.Component {
- constructor(props) {
- super(props);
- this.state = { isFullscreen: false };
- }
-
- isHidden() {
- return this.props.desktop;
- }
-
- async componentDidMount() {
- this.props.initializePresets(this.props.options);
-
- screenfull.onchange(this._handleFullscreenChange);
- }
-
- componentWillUnmount() {
- screenfull.off("change", this._handleFullscreenChange);
- }
-
- _handleFullscreenChange = () => {
- this.setState({ isFullscreen: screenfull.isFullscreen });
- };
-
- _handleRequestFullsceen = () => {
- if (screenfull.enabled) {
- if (!screenfull.isFullscreen) {
- screenfull.request(this._wrapperNode);
- } else {
- screenfull.exit();
- }
- }
- };
-
- _renderMilkdrop(size) {
- const { width, height } = this.state.isFullscreen
- ? { width: screen.width, height: screen.height }
- : size;
- // Note: This _wrapperNode must not be removed from the DOM while
- // in/entering full screen mode. Ensure `this.setState({isFullscreen})`
- // does not cause this node to change identity.
- return (
- (this._wrapperNode = node)}>
-
-
- );
- }
-
- render() {
- if (this.props.desktop) {
- const size = { width: window.innerWidth, height: window.innerHeight };
- return (
- (
-
- )}
- >
- {this._renderMilkdrop(size)}
-
- );
- }
-
- return (
-
- {({ height, width }) => (
- (
-
- )}
- >
- {this._renderMilkdrop({ width, height })}
-
- )}
-
- );
- }
-}
-
-const mapStateToProps = state => ({
- isEnabledVisualizer:
- Selectors.getVisualizerStyle(state) === VISUALIZERS.MILKDROP,
- playing: Selectors.getMediaIsPlaying(state),
- desktop: Selectors.getMilkdropDesktopEnabled(state)
-});
-
-const mapDispatchToProps = dispatch => ({
- closeWindow: () => dispatch(Actions.closeWindow(WINDOWS.MILKDROP)),
- toggleDesktop: () => dispatch(Actions.toggleMilkdropDesktop()),
- initializePresets: presets => dispatch(Actions.initializePresets(presets))
-});
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(PresetsLoader);
diff --git a/js/components/MilkdropWindow/index.tsx b/js/components/MilkdropWindow/index.tsx
new file mode 100644
index 00000000..042b4761
--- /dev/null
+++ b/js/components/MilkdropWindow/index.tsx
@@ -0,0 +1,92 @@
+// @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 * 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";
+
+interface StateProps {
+ desktop: boolean;
+}
+
+interface DispatchProps {
+ closeWindow(): void;
+ toggleDesktop(): void;
+}
+
+interface OwnProps {
+ analyser: AnalyserNode;
+ height: number;
+ width: number;
+ onFocusedKeyDown(cb: (e: KeyboardEvent) => void): () => void;
+}
+
+type Props = StateProps & DispatchProps & OwnProps;
+
+function Milkdrop(props: Props) {
+ // Handle keyboard events
+ useEffect(() => {
+ return props.onFocusedKeyDown(e => {
+ switch (e.keyCode) {
+ case 32: // spacebar
+ // this._nextPreset(USER_PRESET_TRANSITION_SECONDS);
+ break;
+ case 8: // backspace
+ // this._prevPreset(0);
+ break;
+ case 72: // H
+ // this._nextPreset(0);
+ break;
+ case 82: // R
+ // this.props.presets.toggleRandomize();
+ break;
+ case 76: // L
+ // this.setState({ presetOverlay: !this.state.presetOverlay });
+ e.stopPropagation();
+ break;
+ case 84: // T
+ // this.visualizer.launchSongTitleAnim(this.props.trackTitle);
+ e.stopPropagation();
+ break;
+ case 145: // scroll lock
+ case 125: // F14 (scroll lock for OS X)
+ // this.presetCycle = !this.presetCycle;
+ // this._restartCycling();
+ break;
+ }
+ });
+ }, [props.onFocusedKeyDown]);
+ return (
+
+ {({ height, width }) => (
+
+
+
+ )}
+
+ );
+}
+
+const mapStateToProps = (state: AppState): StateProps => ({
+ desktop: Selectors.getMilkdropDesktopEnabled(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
+ closeWindow: () => dispatch(Actions.closeWindow(WINDOWS.MILKDROP)),
+ toggleDesktop: () => dispatch(Actions.toggleMilkdropDesktop())
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Milkdrop);
diff --git a/js/reducers/milkdrop.ts b/js/reducers/milkdrop.ts
index 6a694466..f24950bf 100644
--- a/js/reducers/milkdrop.ts
+++ b/js/reducers/milkdrop.ts
@@ -6,16 +6,50 @@ import {
} from "../actionTypes";
import { TransitionType } from "../types";
+// This is what we actually pass to butterchurn
+type ButterchurnPresetJson = {
+ type: "BUTTERCHURN_JSON";
+ name: string;
+ definition: any;
+};
+
+// A URL that points to a Butterchurn preset
+interface ButterchurnPresetUrl {
+ type: "BUTTERCHURN_URL";
+ url: string;
+}
+
+// A URL that points to a .milk preset
+interface MilkdropPresetUrl {
+ type: "MILKDROP_URL";
+ url: string;
+}
+
+type PresetDefinition =
+ | ButterchurnPresetJson
+ | ButterchurnPresetUrl
+ | MilkdropPresetUrl;
+
+type LazyPresetDefinition = () => Promise;
+
+type PresetId = string;
+
export interface MilkdropState {
desktop: boolean;
- presets: any;
+ presetOrder: PresetId[];
+ presetDefinitions: {
+ [presetId: string]: PresetDefinition | LazyPresetDefinition;
+ };
+ currentPresetIndex: number | null;
butterchurn: any;
transitionType: TransitionType;
}
const defaultMilkdropState = {
desktop: false,
- presets: null,
+ presetOrder: [],
+ presetDefinitions: {},
+ currentPresetIndex: null,
butterchurn: null,
transitionType: TransitionType.DEFAULT
};
@@ -27,10 +61,18 @@ export const milkdrop = (
switch (action.type) {
case SET_MILKDROP_DESKTOP:
return { ...state, desktop: action.enabled };
- case INITIALIZE_PRESETS:
- return { ...state, presets: action.presets };
case GOT_BUTTERCHURN:
return { ...state, butterchurn: action.butterchurn };
+ case "GOT_BUTTERCHUN_PRESET":
+ const id = "TEMP";
+ return {
+ ...state,
+ presetDefinitions: {
+ [id]: action.json
+ },
+ currentPresetIndex: 0,
+ presetOrder: [id]
+ };
default:
return state;
}
diff --git a/js/selectors.ts b/js/selectors.ts
index c8b6ed0c..609e3230 100644
--- a/js/selectors.ts
+++ b/js/selectors.ts
@@ -625,3 +625,11 @@ export function getButterchurn(state: AppState): any {
export function getPresetTransitionType(state: AppState): TransitionType {
return state.milkdrop.transitionType;
}
+
+export function getCurrentPreset(state: AppState): any | null {
+ const { currentPresetIndex, presetOrder, presetDefinitions } = state.milkdrop;
+ if (currentPresetIndex == null) {
+ return null;
+ }
+ return presetDefinitions[presetOrder[currentPresetIndex]];
+}
diff --git a/js/webampLazy.tsx b/js/webampLazy.tsx
index 75d97dc6..5d63b28d 100644
--- a/js/webampLazy.tsx
+++ b/js/webampLazy.tsx
@@ -199,6 +199,9 @@ class Winamp {
type: ENABLE_MILKDROP,
open: options.__butterchurnOptions.butterchurnOpen
});
+ this.store.dispatch(
+ Actions.initializePresets(options.__butterchurnOptions)
+ );
}
if (options.__enableMediaLibrary) {