mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-23 18:17:38 +00:00
Improve fade in of Webamp
Handle a bunch of unsubscribe conditions and also start fetching the skin immediately rather then waiting 400ms for the animation to complete. In all, Webamp should load 400ms faster
This commit is contained in:
parent
8ad18359b8
commit
95fd90e5fa
3 changed files with 126 additions and 78 deletions
17
src/Disposable.js
Normal file
17
src/Disposable.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
export default class Disposable {
|
||||
_teardowns = [];
|
||||
add(...teardowns) {
|
||||
this._teardowns.push(...teardowns);
|
||||
}
|
||||
dispose() {
|
||||
this._teardowns.forEach(teardown => {
|
||||
if (typeof teardown === "function") {
|
||||
teardown();
|
||||
} else if (typeof teardown.unsubscribe === "function") {
|
||||
teardown.unsubscribe();
|
||||
} else if (typeof teardown.dispose === "function") {
|
||||
teardown.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -4,50 +4,85 @@ import WebampComponent from "./WebampComponent";
|
|||
import * as Utils from "./utils";
|
||||
import * as Selectors from "./redux/selectors";
|
||||
import { SCREENSHOT_HEIGHT, SCREENSHOT_WIDTH } from "./constants";
|
||||
import { delay, first } from "rxjs/operators";
|
||||
import { Subject, combineLatest, timer } from "rxjs";
|
||||
import Disposable from "./Disposable";
|
||||
|
||||
class FocusedSkin extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._disposable = new Disposable();
|
||||
// TODO: Handle the case were we come from a permalink
|
||||
if (this.props.initialPosition == null) {
|
||||
this.state = Object.assign({ loaded: false }, this._getCenteredState());
|
||||
this.state = Object.assign(
|
||||
{ loaded: false, transitionComplete: true },
|
||||
this._getCenteredState()
|
||||
);
|
||||
} else {
|
||||
this.state = {
|
||||
loaded: false,
|
||||
centered: false,
|
||||
transitionComplete: false,
|
||||
top: this.props.initialPosition.top,
|
||||
left: this.props.initialPosition.left,
|
||||
width: this.props.initialWidth,
|
||||
height: this.props.initialHeight
|
||||
};
|
||||
}
|
||||
this._webampLoadedEvents = new Subject();
|
||||
this._transitionBeginEvents = new Subject();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
// TODO: Observe DOM and recenter
|
||||
this.setState(this._getCenteredState());
|
||||
}, 0);
|
||||
if (!this.props.centered) {
|
||||
this._disposable.add(
|
||||
timer(0).subscribe(() => {
|
||||
// TODO: Observe DOM and recenter
|
||||
this.setState(this._getCenteredState());
|
||||
this._transitionBeginEvents.next(null);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this._transitionBeginEvents.next(null);
|
||||
}
|
||||
|
||||
const transitionComplete = this._transitionBeginEvents.pipe(delay(500));
|
||||
|
||||
// Emit after both Webamp has loaded, and the transition is complete
|
||||
const startWebampFadein = combineLatest(
|
||||
this._webampLoadedEvents,
|
||||
transitionComplete
|
||||
).pipe(first());
|
||||
|
||||
// This value matches the opacity transition timing for `#webamp` in CSS
|
||||
const webampFadeinComplete = startWebampFadein.pipe(delay(400));
|
||||
|
||||
this._disposable.add(
|
||||
startWebampFadein.subscribe(() => {
|
||||
document.body.classList.add("webamp-loaded");
|
||||
this._disposable.add(() => {
|
||||
document.body.classList.remove("webamp-loaded");
|
||||
});
|
||||
}),
|
||||
webampFadeinComplete.subscribe(() => {
|
||||
this.setState({ loaded: true });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.classList.remove("webamp-loaded");
|
||||
this._disposable.dispose();
|
||||
}
|
||||
|
||||
handleWebampLoaded = () => {
|
||||
document.body.classList.add("webamp-loaded");
|
||||
setTimeout(
|
||||
() => {
|
||||
this.setState({ loaded: true });
|
||||
},
|
||||
// This matches the transition time that Webamp takes to fade in.
|
||||
400
|
||||
);
|
||||
this._webampLoadedEvents.next(null);
|
||||
};
|
||||
|
||||
_getCenteredState() {
|
||||
// TODO: Observe DOM and recenter
|
||||
const { windowWidth, windowHeight } = Utils.getWindowSize();
|
||||
return {
|
||||
centered: true,
|
||||
top: (windowHeight - SCREENSHOT_HEIGHT) / 2,
|
||||
left: (windowWidth - SCREENSHOT_WIDTH) / 2,
|
||||
height: SCREENSHOT_HEIGHT,
|
||||
|
|
@ -56,49 +91,63 @@ class FocusedSkin extends React.Component {
|
|||
}
|
||||
render() {
|
||||
const { loaded } = this.state;
|
||||
const transform = `translateX(${Math.round(
|
||||
this.state.left
|
||||
)}px) translateY(${Math.round(this.state.top)}px)`;
|
||||
|
||||
return (
|
||||
<div
|
||||
id="focused-skin"
|
||||
style={{
|
||||
position: "fixed",
|
||||
height: this.state.height,
|
||||
width: this.state.width,
|
||||
transform: `translateX(${Math.round(
|
||||
this.state.left
|
||||
)}px) translateY(${Math.round(this.state.top)}px)`,
|
||||
transition:
|
||||
"all 400ms ease-out, height 400ms ease-out, width 400ms ease-out"
|
||||
}}
|
||||
>
|
||||
<div style={{ width: "100%", height: "100%" }}>
|
||||
<WebampComponent
|
||||
key={this.props.hash} // Don't reuse instances
|
||||
skinUrl={Utils.skinUrlFromHash(this.props.hash)}
|
||||
loaded={this.handleWebampLoaded}
|
||||
/>
|
||||
{this.state.loaded || (
|
||||
<img
|
||||
className={"focused-preview"}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
// Webamp measure the scrollHeight of the container. Making this a
|
||||
// block element ensures the parent element's scrollHeight is not
|
||||
// expanded.
|
||||
display: "block",
|
||||
zIndex: 1
|
||||
}}
|
||||
src={Utils.screenshotUrlFromHash(this.props.hash)}
|
||||
<React.Fragment>
|
||||
{this.state.centered && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
height: SCREENSHOT_HEIGHT,
|
||||
width: SCREENSHOT_WIDTH,
|
||||
transform
|
||||
}}
|
||||
>
|
||||
<WebampComponent
|
||||
key={this.props.hash} // Don't reuse instances
|
||||
skinUrl={Utils.skinUrlFromHash(this.props.hash)}
|
||||
loaded={this.handleWebampLoaded}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="metadata">
|
||||
<div className="file-name">
|
||||
{Utils.filenameFromHash(this.props.hash)}
|
||||
</div>
|
||||
<a href={Utils.skinUrlFromHash(this.props.hash)}>Download</a>
|
||||
)}
|
||||
<div
|
||||
id="focused-skin"
|
||||
style={{
|
||||
position: "fixed",
|
||||
height: this.state.height,
|
||||
width: this.state.width,
|
||||
transform,
|
||||
transition:
|
||||
"all 400ms ease-out, height 400ms ease-out, width 400ms ease-out"
|
||||
}}
|
||||
>
|
||||
<div style={{ width: "100%", height: "100%" }}>
|
||||
{loaded || (
|
||||
<img
|
||||
className={"focused-preview"}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
// Webamp measure the scrollHeight of the container. Making this a
|
||||
// block element ensures the parent element's scrollHeight is not
|
||||
// expanded.
|
||||
display: "block"
|
||||
}}
|
||||
src={Utils.screenshotUrlFromHash(this.props.hash)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="metadata">
|
||||
<div className="file-name">
|
||||
{Utils.filenameFromHash(this.props.hash)}
|
||||
</div>
|
||||
<a href={Utils.skinUrlFromHash(this.props.hash)}>Download</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,7 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import * as ActionCreators from "./redux/actionCreators";
|
||||
|
||||
class Disposable {
|
||||
_subscriptions = [];
|
||||
add(subscription) {
|
||||
this._subscriptions.push(subscription);
|
||||
}
|
||||
dispose() {
|
||||
this._subscriptions.forEach(subscription => {
|
||||
subscription();
|
||||
});
|
||||
}
|
||||
}
|
||||
import Disposable from "./Disposable";
|
||||
|
||||
class WebampComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -61,19 +50,12 @@ class WebampComponent extends React.Component {
|
|||
|
||||
this._disposable.add(this._webamp.onClose(this.props.closeModal));
|
||||
|
||||
const renderTimeout = setTimeout(
|
||||
async () => {
|
||||
await this._webamp.renderWhenReady(this._ref);
|
||||
if (!this._unmounted) {
|
||||
this.props.loaded();
|
||||
}
|
||||
},
|
||||
// This number must be higher than the transition time of the CSS animation that puts this in place.
|
||||
500
|
||||
);
|
||||
this._disposable.add(() => {
|
||||
clearTimeout(renderTimeout);
|
||||
});
|
||||
await this._webamp.renderWhenReady(this._ref);
|
||||
if (!this._unmounted) {
|
||||
this.props.loaded();
|
||||
} else {
|
||||
// TODO: Remove Webamp
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue