From b53bd1fe375d6d60cf42f13ef973d3c96c9f82c2 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sat, 2 Jan 2021 13:16:26 -0800 Subject: [PATCH] Pull demo site webamp config into typed module (#1057) * Pull demo site webamp config into typed module * Tybe the demo index.js file * Update entrypoint to point to tsx file --- packages/webamp/demo/config/webpack.common.js | 2 +- packages/webamp/demo/js/Webamp.ts | 7 +- packages/webamp/demo/js/config.ts | 2 +- packages/webamp/demo/js/index.js | 253 ------------------ packages/webamp/demo/js/index.tsx | 156 +++++++++++ packages/webamp/demo/js/webampConfig.ts | 134 ++++++++++ packages/webamp/js/types.ts | 4 +- packages/webamp/js/webampLazy.tsx | 31 ++- 8 files changed, 317 insertions(+), 272 deletions(-) delete mode 100644 packages/webamp/demo/js/index.js create mode 100644 packages/webamp/demo/js/index.tsx create mode 100644 packages/webamp/demo/js/webampConfig.ts 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) | null; handleTrackDropEvent?: ( diff --git a/packages/webamp/js/webampLazy.tsx b/packages/webamp/js/webampLazy.tsx index 935cabbd..e2f60307 100644 --- a/packages/webamp/js/webampLazy.tsx +++ b/packages/webamp/js/webampLazy.tsx @@ -39,8 +39,9 @@ import Emitter from "./emitter"; import "../css/base-skin.css"; import { SerializedStateV1 } from "./serializedStates/v1Types"; import Disposable from "./Disposable"; +import { DeepPartial } from "redux"; -interface Options { +export interface Options { /** * An object representing the initial skin to use. * @@ -113,21 +114,23 @@ interface Options { handleSaveListEvent?: (tracks: Track[]) => null | Promise; } -interface PrivateOptions { - avaliableSkins?: { url: string; name: string }[]; // Old misspelled name - requireJSZip(): Promise; // TODO: Type JSZip - requireMusicMetadata(): Promise; // TODO: Type musicmetadata - __initialState?: AppState; - __customMiddlewares?: Middleware[]; - __initialWindowLayout: { - [windowId: string]: { - size: null | [number, number]; - position: WindowPosition; - }; +export type WindowLayout = { + [windowId: string]: { + size?: null | [number, number]; + position: WindowPosition; }; - __butterchurnOptions: ButterchurnOptions; +}; + +export interface PrivateOptions { + avaliableSkins?: { url: string; name: string }[]; // Old misspelled name + requireJSZip(): Promise; // TODO: Type JSZip + requireMusicMetadata(): Promise; // TODO: Type musicmetadata + __initialState?: DeepPartial; + __customMiddlewares?: Middleware[]; + __initialWindowLayout?: WindowLayout; + __butterchurnOptions?: ButterchurnOptions; // This is used by https://winampify.io/ to proxy through to Spotify's API. - __customMediaClass: typeof Media; // This should have the same interface as Media + __customMediaClass?: typeof Media; // This should have the same interface as Media } // Return a promise that resolves when the store matches a predicate.