diff --git a/js/components/EqualizerWindow/EqGraph.js b/js/components/EqualizerWindow/EqGraph.js index 321d48a3..bc93319f 100644 --- a/js/components/EqualizerWindow/EqGraph.js +++ b/js/components/EqualizerWindow/EqGraph.js @@ -3,6 +3,7 @@ import { connect } from "react-redux"; import { percentToRange, clamp } from "../../utils"; import { BANDS } from "../../constants"; import spline from "./spline"; +import * as Selectros from "../../selectors"; const GRAPH_HEIGHT = 19; const GRAPH_WIDTH = 113; @@ -18,12 +19,8 @@ class EqGraph extends React.Component { this.width = Number(this.canvas.width); this.height = Number(this.canvas.height); - if (this.props.lineColorsImage) { - this.createColorPattern(this.props.lineColorsImage); - } - if (this.props.preampLineUrl) { - this.createPreampLineImage(this.props.preampLineUrl); - } + this.createColorPattern(this.props.lineColorsImagePromise); + this.createPreampLineImage(this.props.preampLineImagePromise); } componentDidUpdate() { @@ -33,37 +30,35 @@ class EqGraph extends React.Component { } shouldComponentUpdate(nextProps) { - if (this.props.lineColorsImage !== nextProps.lineColorsImage) { - this.createColorPattern(nextProps.lineColorsImage); + if ( + this.props.lineColorsImagePromise !== nextProps.lineColorsImagePromise + ) { + this.createColorPattern(nextProps.lineColorsImagePromise); } - if (this.props.preampLineUrl !== nextProps.preampLineUrl) { - this.createPreampLineImage(nextProps.preampLineUrl); + if ( + this.props.preampLineImagePromise !== nextProps.preampLineImagePromise + ) { + this.createPreampLineImage(nextProps.preampLineImagePromise); } return true; } - createPreampLineImage(preampLineUrl) { - const preampLineImg = new Image(); - preampLineImg.onload = () => { - this.setState({ preampLineImg }); - }; - preampLineImg.src = preampLineUrl; + async createPreampLineImage(preampLineImagePromise) { + const preampLineImg = await preampLineImagePromise; + this.setState({ preampLineImg }); } - createColorPattern(lineColorsImage) { - const bgImage = new Image(); - bgImage.onload = () => { - const { width, height } = bgImage; - const colorsCanvas = document.createElement("canvas"); - const colorsCtx = colorsCanvas.getContext("2d"); - colorsCanvas.width = width; - colorsCanvas.height = height; - colorsCtx.drawImage(bgImage, 0, 0, width, height); - this.setState({ - colorPattern: this.canvasCtx.createPattern(colorsCanvas, "repeat-x"), - }); - }; - bgImage.src = lineColorsImage; + async createColorPattern(lineColorsImagePromise) { + const bgImage = await lineColorsImagePromise; + const { width, height } = bgImage; + const colorsCanvas = document.createElement("canvas"); + const colorsCtx = colorsCanvas.getContext("2d"); + colorsCanvas.width = width; + colorsCanvas.height = height; + colorsCtx.drawImage(bgImage, 0, 0, width, height); + this.setState({ + colorPattern: this.canvasCtx.createPattern(colorsCanvas, "repeat-x"), + }); } drawEqLine() { @@ -136,6 +131,11 @@ class EqGraph extends React.Component { export default connect(state => ({ ...state.equalizer.sliders, - lineColorsImage: state.display.skinImages.EQ_GRAPH_LINE_COLORS, - preampLineUrl: state.display.skinImages.EQ_PREAMP_LINE, + // WebampLazy.skinIsLoaded() makes an effort to ensure that these promises are + // already resolved before our initial render. This means our setStates + // should get called on a microtask and we should rerender with this loaded + // before we paint. + // This does not work when loading skins after initial render. + lineColorsImagePromise: Selectros.getLineColorsImage(state), + preampLineImagePromise: Selectros.getPreampLineImage(state), }))(EqGraph); diff --git a/js/selectors.ts b/js/selectors.ts index 17779344..9ab084d1 100644 --- a/js/selectors.ts +++ b/js/selectors.ts @@ -718,3 +718,25 @@ export function getPresetsAreCycling(state: AppState): boolean { export function getRandomizePresets(state: AppState): boolean { return state.milkdrop.randomize; } + +export function getPreampLineUrl(state: AppState): string { + return state.display.skinImages.EQ_PREAMP_LINE; +} + +export function getLineColorsUrl(state: AppState): string { + return state.display.skinImages.EQ_GRAPH_LINE_COLORS; +} + +export const getPreampLineImage = createSelector( + getPreampLineUrl, + async (url): Promise => { + return Utils.imgFromUrl(url); + } +); + +export const getLineColorsImage = createSelector( + getLineColorsUrl, + async (url): Promise => { + return Utils.imgFromUrl(url); + } +); diff --git a/js/utils.ts b/js/utils.ts index c9e76d66..71550b42 100644 --- a/js/utils.ts +++ b/js/utils.ts @@ -14,6 +14,17 @@ interface IniData { }; } +export function imgFromUrl(url: string): Promise { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + resolve(img); + }; + img.onerror = reject; + img.src = url; + }); +} + export const getTimeObj = (time: number | null): Time => { if (time == null) { // If we clean up `` we don't need to do this any more. diff --git a/js/webampLazy.tsx b/js/webampLazy.tsx index 7b48b296..83ec62fc 100644 --- a/js/webampLazy.tsx +++ b/js/webampLazy.tsx @@ -390,7 +390,18 @@ class Winamp { async skinIsLoaded(): Promise { // Wait for the skin to load. // TODO #leak - return storeHas(this.store, state => !state.display.loading); + await storeHas(this.store, state => !state.display.loading); + // We attempt to pre-resolve these promises before we declare the skin + // loaded. That's because `` needs these in order to render fully. + // As long as these are resolved before we attempt to render, we can ensure + // that we will have all the data we need on first paint. + // + // Note: This won't help for non-initial skin loads. + await Promise.all([ + Selectors.getPreampLineImage(this.store.getState()), + Selectors.getLineColorsImage(this.store.getState()), + ]); + return; } __loadSerializedState(serializedState: SerializedStateV1): void {