diff --git a/packages/webamp/demo/config/webpack.common.js b/packages/webamp/demo/config/webpack.common.js
index 039dcd0f..b2ce03e6 100644
--- a/packages/webamp/demo/config/webpack.common.js
+++ b/packages/webamp/demo/config/webpack.common.js
@@ -76,7 +76,7 @@ module.exports = {
maxAssetSize: 7000000,
},
entry: {
- webamp: ["./js/index.js"],
+ webamp: ["./js/index.tsx"],
},
context: path.resolve(__dirname, "../"),
output: {
diff --git a/packages/webamp/demo/js/Webamp.ts b/packages/webamp/demo/js/Webamp.ts
index 9aca6b38..87bfbd5d 100644
--- a/packages/webamp/demo/js/Webamp.ts
+++ b/packages/webamp/demo/js/Webamp.ts
@@ -4,7 +4,12 @@
// away once we've figured out how to expose all the things that the demo site
// needs, or reduce the things that the demo site needs access to.
-export { default as WebampLazy } from "../../js/webampLazy";
+export {
+ default as WebampLazy,
+ Options,
+ PrivateOptions,
+ WindowLayout,
+} from "../../js/webampLazy";
export {
ButterchurnOptions,
Track,
diff --git a/packages/webamp/demo/js/config.ts b/packages/webamp/demo/js/config.ts
index b6b3deee..fb260444 100644
--- a/packages/webamp/demo/js/config.ts
+++ b/packages/webamp/demo/js/config.ts
@@ -33,7 +33,7 @@ if ("URLSearchParams" in window) {
// SHOW_DESKTOP_ICONS = Boolean(params.get("icons"));
}
-export const skinUrl = config.skinUrl === undefined ? null : config.skinUrl;
+export const skinUrl = config.skinUrl ?? null;
// https://freemusicarchive.org/music/netBloc_Artists/netBloc_Vol_24_tiuqottigeloot/
const album = "netBloc Vol. 24: tiuqottigeloot";
diff --git a/packages/webamp/demo/js/index.js b/packages/webamp/demo/js/index.js
deleted file mode 100644
index 9c020a7a..00000000
--- a/packages/webamp/demo/js/index.js
+++ /dev/null
@@ -1,253 +0,0 @@
-/* global SENTRY_DSN */
-
-import * as Sentry from "@sentry/browser";
-import ReactDOM from "react-dom";
-import createMiddleware from "redux-sentry-middleware";
-import isButterchurnSupported from "butterchurn/lib/isSupported.min";
-import { loggerMiddleware } from "./eventLogger";
-
-import {
- WebampLazy,
- WINDOWS,
- STEP_MARQUEE,
- UPDATE_TIME_ELAPSED,
- UPDATE_WINDOW_POSITIONS,
- SET_VOLUME,
- SET_BALANCE,
- SET_BAND_VALUE,
- DISABLE_MARQUEE,
- TOGGLE_REPEAT,
- TOGGLE_SHUFFLE,
- SET_EQ_AUTO,
- SET_DUMMY_VIZ_DATA,
-} from "./Webamp";
-
-import { getButterchurnOptions } from "./butterchurnOptions";
-import dropboxFilePicker from "./dropboxFilePicker";
-import availableSkins from "./avaliableSkins";
-
-import {
- skinUrl as configSkinUrl,
- initialTracks,
- initialState,
- disableMarquee,
-} from "./config";
-import DemoDesktop from "./DemoDesktop";
-import enableMediaSession from "./mediaSession";
-import screenshotInitialState from "./screenshotInitialState";
-
-const DEFAULT_DOCUMENT_TITLE = document.title;
-
-const NOISY_ACTION_TYPES = new Set([
- STEP_MARQUEE,
- UPDATE_TIME_ELAPSED,
- UPDATE_WINDOW_POSITIONS,
- SET_VOLUME,
- SET_BALANCE,
- SET_BAND_VALUE,
-]);
-
-const MIN_MILKDROP_WIDTH = 725;
-
-let screenshot = false;
-let skinUrl = configSkinUrl;
-if ("URLSearchParams" in window) {
- const params = new URLSearchParams(location.search);
- screenshot = params.get("screenshot");
- skinUrl = params.get("skinUrl") || skinUrl;
-}
-
-function supressDragAndDrop(e) {
- e.preventDefault();
- e.dataTransfer.effectAllowed = "none";
- e.dataTransfer.dropEffect = "none";
-}
-
-window.addEventListener("dragenter", supressDragAndDrop);
-window.addEventListener("dragover", supressDragAndDrop);
-window.addEventListener("drop", supressDragAndDrop);
-
-let lastActionType = null;
-
-// Filter out consecutive common actions
-function filterBreadcrumbActions(action) {
- const noisy =
- NOISY_ACTION_TYPES.has(action.type) &&
- NOISY_ACTION_TYPES.has(lastActionType);
- lastActionType = action.type;
- return !noisy;
-}
-
-try {
- Sentry.init({
- dsn: SENTRY_DSN,
- /* global COMMITHASH */
- release: typeof COMMITHASH !== "undefined" ? COMMITHASH : "DEV",
- });
-} catch (e) {
- // Archive.org tries to rewrite the DSN to point to a archive.org version
- // since it looks like a URL. When this happens, Sentry crashes.
- console.error(e);
-}
-
-const sentryMiddleware = createMiddleware(Sentry, {
- filterBreadcrumbActions,
- stateTransformer: getDebugData,
-});
-
-async function main() {
- const about = document.getElementsByClassName("about")[0];
- if (screenshot) {
- about.style.visibility = "hidden";
- }
- if (!WebampLazy.browserIsSupported()) {
- document.getElementById("browser-compatibility").style.display = "block";
- document.getElementById("app").style.visibility = "hidden";
- return;
- }
- about.classList.add("loaded");
-
- let __butterchurnOptions = null;
- let __initialWindowLayout = null;
- if (isButterchurnSupported()) {
- const startWithMilkdropHidden =
- document.body.clientWidth < MIN_MILKDROP_WIDTH ||
- skinUrl != null ||
- screenshot;
-
- __butterchurnOptions = getButterchurnOptions(startWithMilkdropHidden);
-
- if (startWithMilkdropHidden) {
- __initialWindowLayout = {
- [WINDOWS.MAIN]: { position: { x: 0, y: 0 } },
- [WINDOWS.EQUALIZER]: { position: { x: 0, y: 116 } },
- [WINDOWS.PLAYLIST]: { position: { x: 0, y: 232 }, size: [0, 0] },
- [WINDOWS.MILKDROP]: { position: { x: 0, y: 348 }, size: [0, 0] },
- };
- } else {
- __initialWindowLayout = {
- [WINDOWS.MAIN]: { position: { x: 0, y: 0 } },
- [WINDOWS.EQUALIZER]: { position: { x: 0, y: 116 } },
- [WINDOWS.PLAYLIST]: { position: { x: 0, y: 232 }, size: [0, 4] },
- [WINDOWS.MILKDROP]: { position: { x: 275, y: 0 }, size: [7, 12] },
- };
- }
-
- document.getElementById("butterchurn-share").style.display = "flex";
- }
-
- const initialSkin = !skinUrl ? null : { url: skinUrl };
-
- const webamp = new WebampLazy({
- initialSkin,
- initialTracks: screenshot ? null : initialTracks,
- availableSkins,
- filePickers: [dropboxFilePicker],
- enableHotkeys: true,
- handleTrackDropEvent: (e) => {
- const trackJson = e.dataTransfer.getData("text/json");
- if (trackJson == null) {
- return null;
- }
- try {
- const track = JSON.parse(trackJson);
- return [track];
- } catch (err) {
- return null;
- }
- },
- requireJSZip: () =>
- import(/* webpackChunkName: "jszip" */ "jszip/dist/jszip"),
- requireMusicMetadata: () =>
- import(
- /* webpackChunkName: "music-metadata-browser" */ "music-metadata-browser/dist/index"
- ),
- __initialWindowLayout,
- __initialState: screenshot ? screenshotInitialState : initialState,
- __butterchurnOptions,
- __customMiddlewares: [sentryMiddleware, loggerMiddleware],
- });
-
- if (disableMarquee || screenshot) {
- webamp.store.dispatch({ type: DISABLE_MARQUEE });
- }
- if (screenshot) {
- window.document.body.style.backgroundColor = "#000";
- webamp.store.dispatch({ type: TOGGLE_REPEAT });
- webamp.store.dispatch({ type: TOGGLE_SHUFFLE });
- webamp.store.dispatch({ type: SET_EQ_AUTO, value: true });
- webamp.store.dispatch({
- type: SET_DUMMY_VIZ_DATA,
- data: {
- 0: 11.75,
- 8: 11.0625,
- 16: 8.5,
- 24: 7.3125,
- 32: 6.75,
- 40: 6.4375,
- 48: 6.25,
- 56: 5.875,
- 64: 5.625,
- 72: 5.25,
- 80: 5.125,
- 88: 4.875,
- 96: 4.8125,
- 104: 4.375,
- 112: 3.625,
- 120: 1.5625,
- },
- });
- }
-
- webamp.onTrackDidChange((track) => {
- document.title =
- track == null
- ? DEFAULT_DOCUMENT_TITLE
- : `${track.metaData.title} - ${track.metaData.artist} \u00B7 ${DEFAULT_DOCUMENT_TITLE}`;
- });
-
- enableMediaSession(webamp);
-
- // Expose a file input in the DOM for testing.
- const fileInput = document.createElement("input");
- fileInput.id = "webamp-file-input";
- fileInput.style.display = "none";
- fileInput.type = "file";
- fileInput.value = null;
- fileInput.addEventListener("change", (e) => {
- const firstFile = e.target.files[0];
- if (firstFile == null) {
- return;
- }
- const url = URL.createObjectURL(firstFile);
- webamp.setSkinFromUrl(url);
- });
- document.body.appendChild(fileInput);
-
- // Expose webamp instance for debugging and integration tests.
- window.__webamp = webamp;
-
- await webamp.renderWhenReady(document.getElementById("app"));
-
- if (!screenshot) {
- ReactDOM.render(
- ,
- document.getElementById("demo-desktop")
- );
- }
-}
-
-function getDebugData(state) {
- return {
- ...state,
- display: {
- ...state.display,
- skinGenLetterWidths: "[[REDACTED]]",
- skinImages: "[[REDACTED]]",
- skinCursors: "[[REDACTED]]",
- skinRegion: "[[REDACTED]]",
- },
- };
-}
-
-main();
diff --git a/packages/webamp/demo/js/index.tsx b/packages/webamp/demo/js/index.tsx
new file mode 100644
index 00000000..d302d934
--- /dev/null
+++ b/packages/webamp/demo/js/index.tsx
@@ -0,0 +1,156 @@
+import * as Sentry from "@sentry/browser";
+import ReactDOM from "react-dom";
+// @ts-ignore
+import isButterchurnSupported from "butterchurn/lib/isSupported.min";
+import { getWebampConfig } from "./webampConfig";
+
+import {
+ WebampLazy,
+ DISABLE_MARQUEE,
+ TOGGLE_REPEAT,
+ TOGGLE_SHUFFLE,
+ SET_EQ_AUTO,
+ SET_DUMMY_VIZ_DATA,
+} from "./Webamp";
+
+import { disableMarquee, skinUrl as configSkinUrl } from "./config";
+import DemoDesktop from "./DemoDesktop";
+import enableMediaSession from "./mediaSession";
+
+declare global {
+ const SENTRY_DSN: string;
+ const COMMITHASH: string | undefined;
+ interface Window {
+ __webamp: WebampLazy;
+ }
+}
+
+const DEFAULT_DOCUMENT_TITLE = document.title;
+
+let screenshot = false;
+let skinUrl = configSkinUrl;
+if ("URLSearchParams" in window) {
+ const params = new URLSearchParams(location.search);
+ screenshot = Boolean(params.get("screenshot"));
+ skinUrl = params.get("skinUrl") || skinUrl;
+}
+
+function supressDragAndDrop(e: DragEvent) {
+ e.preventDefault();
+ if (e.dataTransfer == null) {
+ return;
+ }
+ e.dataTransfer.effectAllowed = "none";
+ e.dataTransfer.dropEffect = "none";
+}
+
+window.addEventListener("dragenter", supressDragAndDrop);
+window.addEventListener("dragover", supressDragAndDrop);
+window.addEventListener("drop", supressDragAndDrop);
+
+try {
+ Sentry.init({
+ dsn: SENTRY_DSN,
+ release: COMMITHASH ?? "DEV",
+ });
+} catch (e) {
+ // Archive.org tries to rewrite the DSN to point to a archive.org version
+ // since it looks like a URL. When this happens, Sentry crashes.
+ console.error(e);
+}
+
+async function main() {
+ const about = document.getElementsByClassName("about")[0] as HTMLDivElement;
+ if (screenshot) {
+ about.style.visibility = "hidden";
+ }
+ if (!WebampLazy.browserIsSupported()) {
+ (document.getElementById(
+ "browser-compatibility"
+ ) as HTMLDivElement).style.display = "block";
+ (document.getElementById("app") as HTMLDivElement).style.visibility =
+ "hidden";
+ return;
+ }
+ about.classList.add("loaded");
+
+ if (isButterchurnSupported()) {
+ (document.getElementById(
+ "butterchurn-share"
+ ) as HTMLDivElement).style.display = "flex";
+ }
+
+ const webamp = new WebampLazy(getWebampConfig(screenshot, skinUrl));
+
+ if (disableMarquee || screenshot) {
+ webamp.store.dispatch({ type: DISABLE_MARQUEE });
+ }
+ if (screenshot) {
+ window.document.body.style.backgroundColor = "#000";
+ webamp.store.dispatch({ type: TOGGLE_REPEAT });
+ webamp.store.dispatch({ type: TOGGLE_SHUFFLE });
+ webamp.store.dispatch({ type: SET_EQ_AUTO, value: true });
+ webamp.store.dispatch({
+ type: SET_DUMMY_VIZ_DATA,
+ data: {
+ 0: 11.75,
+ 8: 11.0625,
+ 16: 8.5,
+ 24: 7.3125,
+ 32: 6.75,
+ 40: 6.4375,
+ 48: 6.25,
+ 56: 5.875,
+ 64: 5.625,
+ 72: 5.25,
+ 80: 5.125,
+ 88: 4.875,
+ 96: 4.8125,
+ 104: 4.375,
+ 112: 3.625,
+ 120: 1.5625,
+ },
+ });
+ }
+
+ webamp.onTrackDidChange((track) => {
+ document.title =
+ track == null
+ ? DEFAULT_DOCUMENT_TITLE
+ : `${track.metaData.title} - ${track.metaData.artist} \u00B7 ${DEFAULT_DOCUMENT_TITLE}`;
+ });
+
+ enableMediaSession(webamp);
+
+ // Expose a file input in the DOM for testing.
+ const fileInput = document.createElement("input");
+ fileInput.id = "webamp-file-input";
+ fileInput.style.display = "none";
+ fileInput.type = "file";
+ fileInput.addEventListener("change", (e) => {
+ // @ts-ignore We know this will always be a file input
+ const firstFile = e.target.files[0];
+ if (firstFile == null) {
+ return;
+ }
+ const url = URL.createObjectURL(firstFile);
+ webamp.setSkinFromUrl(url);
+ });
+ document.body.appendChild(fileInput);
+
+ // Expose webamp instance for debugging and integration tests.
+ window.__webamp = webamp;
+
+ await webamp.renderWhenReady(
+ document.getElementById("app") as HTMLDivElement
+ );
+
+ if (!screenshot) {
+ ReactDOM.render(
+ ,
+ document.getElementById("demo-desktop")
+ );
+ }
+}
+
+main();
diff --git a/packages/webamp/demo/js/webampConfig.ts b/packages/webamp/demo/js/webampConfig.ts
new file mode 100644
index 00000000..a83110c2
--- /dev/null
+++ b/packages/webamp/demo/js/webampConfig.ts
@@ -0,0 +1,134 @@
+import * as Sentry from "@sentry/browser";
+// @ts-ignore
+import createMiddleware from "redux-sentry-middleware";
+// @ts-ignore
+import isButterchurnSupported from "butterchurn/lib/isSupported.min";
+import { loggerMiddleware } from "./eventLogger";
+
+import {
+ Action,
+ Options,
+ PrivateOptions,
+ WINDOWS,
+ STEP_MARQUEE,
+ UPDATE_TIME_ELAPSED,
+ UPDATE_WINDOW_POSITIONS,
+ SET_VOLUME,
+ SET_BALANCE,
+ SET_BAND_VALUE,
+ AppState,
+ WindowLayout,
+} from "./Webamp";
+
+import { getButterchurnOptions } from "./butterchurnOptions";
+import dropboxFilePicker from "./dropboxFilePicker";
+import availableSkins from "./avaliableSkins";
+
+import { initialTracks, initialState } from "./config";
+import screenshotInitialState from "./screenshotInitialState";
+
+const NOISY_ACTION_TYPES = new Set([
+ STEP_MARQUEE,
+ UPDATE_TIME_ELAPSED,
+ UPDATE_WINDOW_POSITIONS,
+ SET_VOLUME,
+ SET_BALANCE,
+ SET_BAND_VALUE,
+]);
+
+const MIN_MILKDROP_WIDTH = 725;
+
+let lastActionType: string | null = null;
+
+// Filter out consecutive common actions
+function filterBreadcrumbActions(action: Action) {
+ const noisy =
+ lastActionType != null &&
+ NOISY_ACTION_TYPES.has(action.type) &&
+ NOISY_ACTION_TYPES.has(lastActionType);
+ lastActionType = action.type;
+ return !noisy;
+}
+
+const sentryMiddleware = createMiddleware(Sentry, {
+ filterBreadcrumbActions,
+ stateTransformer: getDebugData,
+});
+
+export function getWebampConfig(
+ screenshot: boolean,
+ skinUrl: string | null
+): Options & PrivateOptions {
+ let __butterchurnOptions;
+ let __initialWindowLayout: WindowLayout | undefined;
+ if (isButterchurnSupported()) {
+ const startWithMilkdropHidden =
+ document.body.clientWidth < MIN_MILKDROP_WIDTH ||
+ skinUrl != null ||
+ screenshot;
+
+ __butterchurnOptions = getButterchurnOptions(startWithMilkdropHidden);
+
+ if (startWithMilkdropHidden) {
+ __initialWindowLayout = {
+ [WINDOWS.MAIN]: { position: { x: 0, y: 0 } },
+ [WINDOWS.EQUALIZER]: { position: { x: 0, y: 116 } },
+ [WINDOWS.PLAYLIST]: { position: { x: 0, y: 232 }, size: [0, 0] },
+ [WINDOWS.MILKDROP]: { position: { x: 0, y: 348 }, size: [0, 0] },
+ };
+ } else {
+ __initialWindowLayout = {
+ [WINDOWS.MAIN]: { position: { x: 0, y: 0 } },
+ [WINDOWS.EQUALIZER]: { position: { x: 0, y: 116 } },
+ [WINDOWS.PLAYLIST]: { position: { x: 0, y: 232 }, size: [0, 4] },
+ [WINDOWS.MILKDROP]: { position: { x: 275, y: 0 }, size: [7, 12] },
+ };
+ }
+ }
+
+ const initialSkin = !skinUrl ? undefined : { url: skinUrl };
+
+ return {
+ initialSkin,
+ initialTracks: screenshot ? undefined : initialTracks,
+ availableSkins,
+ filePickers: [dropboxFilePicker],
+ enableHotkeys: true,
+ handleTrackDropEvent: (e) => {
+ const trackJson = e.dataTransfer.getData("text/json");
+ if (trackJson == null) {
+ return null;
+ }
+ try {
+ const track = JSON.parse(trackJson);
+ return [track];
+ } catch (err) {
+ return null;
+ }
+ },
+ requireJSZip: () =>
+ // @ts-ignore
+ import(/* webpackChunkName: "jszip" */ "jszip/dist/jszip"),
+ requireMusicMetadata: () =>
+ import(
+ /* webpackChunkName: "music-metadata-browser" */ "music-metadata-browser/dist/index"
+ ),
+ __initialWindowLayout,
+ __initialState: screenshot ? screenshotInitialState : initialState,
+ __butterchurnOptions,
+ __customMiddlewares: [sentryMiddleware, loggerMiddleware],
+ };
+}
+
+function getDebugData(state: AppState) {
+ return {
+ ...state,
+ display: {
+ ...state.display,
+ skinGenLetterWidths: "[[REDACTED]]",
+ skinImages: "[[REDACTED]]",
+ skinCursors: "[[REDACTED]]",
+ skinRegion: "[[REDACTED]]",
+ },
+ };
+}
diff --git a/packages/webamp/js/types.ts b/packages/webamp/js/types.ts
index 8a3c4016..2199d62f 100644
--- a/packages/webamp/js/types.ts
+++ b/packages/webamp/js/types.ts
@@ -360,7 +360,7 @@ export type Action =
}
| {
type: "SET_DUMMY_VIZ_DATA";
- data: null;
+ data: DummyVizData;
}
| {
type: "SET_BAND_VALUE";
@@ -705,7 +705,7 @@ export interface IMusicMetadataBrowserApi {
}
export interface Extras {
- requireJSZip(): Promise;
+ requireJSZip(): Promise;
requireMusicMetadata(): Promise;
convertPreset: ((file: File) => Promise