зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1431573 - Part 4: Implement time label. r=gl
MozReview-Commit-ID: Cg6A4hNLXnO
This commit is contained in:
Родитель
49b1360194
Коммит
162722dd52
|
@ -19,16 +19,23 @@ const {
|
|||
updateSelectedAnimation,
|
||||
updateSidebarSize
|
||||
} = require("./actions/animations");
|
||||
const { isAllAnimationEqual } = require("./utils/utils");
|
||||
const {
|
||||
isAllAnimationEqual,
|
||||
hasPlayingAnimation,
|
||||
} = require("./utils/utils");
|
||||
|
||||
class AnimationInspector {
|
||||
constructor(inspector, win) {
|
||||
this.inspector = inspector;
|
||||
this.win = win;
|
||||
|
||||
this.addAnimationsCurrentTimeListener =
|
||||
this.addAnimationsCurrentTimeListener.bind(this);
|
||||
this.getAnimatedPropertyMap = this.getAnimatedPropertyMap.bind(this);
|
||||
this.getComputedStyle = this.getComputedStyle.bind(this);
|
||||
this.getNodeFromActor = this.getNodeFromActor.bind(this);
|
||||
this.removeAnimationsCurrentTimeListener =
|
||||
this.removeAnimationsCurrentTimeListener.bind(this);
|
||||
this.rewindAnimationsCurrentTime = this.rewindAnimationsCurrentTime.bind(this);
|
||||
this.selectAnimation = this.selectAnimation.bind(this);
|
||||
this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this);
|
||||
|
@ -36,6 +43,7 @@ class AnimationInspector {
|
|||
this.simulateAnimation = this.simulateAnimation.bind(this);
|
||||
this.toggleElementPicker = this.toggleElementPicker.bind(this);
|
||||
this.update = this.update.bind(this);
|
||||
this.onAnimationsCurrentTimeUpdated = this.onAnimationsCurrentTimeUpdated.bind(this);
|
||||
this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
|
||||
this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
|
||||
this.onSidebarResized = this.onSidebarResized.bind(this);
|
||||
|
@ -58,10 +66,13 @@ class AnimationInspector {
|
|||
} = this.inspector.getPanel("boxmodel").getComponentProps();
|
||||
|
||||
const {
|
||||
addAnimationsCurrentTimeListener,
|
||||
emit: emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getComputedStyle,
|
||||
getNodeFromActor,
|
||||
isAnimationsRunning,
|
||||
removeAnimationsCurrentTimeListener,
|
||||
rewindAnimationsCurrentTime,
|
||||
selectAnimation,
|
||||
setAnimationsPlayState,
|
||||
|
@ -73,6 +84,8 @@ class AnimationInspector {
|
|||
const target = this.inspector.target;
|
||||
this.animationsFront = new AnimationsFront(target.client, target.form);
|
||||
|
||||
this.animationsCurrentTimeListeners = [];
|
||||
|
||||
const provider = createElement(Provider,
|
||||
{
|
||||
id: "newanimationinspector",
|
||||
|
@ -81,12 +94,15 @@ class AnimationInspector {
|
|||
},
|
||||
App(
|
||||
{
|
||||
addAnimationsCurrentTimeListener,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getComputedStyle,
|
||||
getNodeFromActor,
|
||||
isAnimationsRunning,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
removeAnimationsCurrentTimeListener,
|
||||
rewindAnimationsCurrentTime,
|
||||
selectAnimation,
|
||||
setAnimationsPlayState,
|
||||
|
@ -123,6 +139,8 @@ class AnimationInspector {
|
|||
this.simulatedElement = null;
|
||||
}
|
||||
|
||||
this.stopAnimationsCurrentTimeTimer();
|
||||
|
||||
this.inspector = null;
|
||||
this.win = null;
|
||||
}
|
||||
|
@ -131,6 +149,10 @@ class AnimationInspector {
|
|||
return this.inspector.store.getState().animations;
|
||||
}
|
||||
|
||||
addAnimationsCurrentTimeListener(listener) {
|
||||
this.animationsCurrentTimeListeners.push(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of animated property from given animation actor.
|
||||
*
|
||||
|
@ -201,6 +223,12 @@ class AnimationInspector {
|
|||
this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
|
||||
}
|
||||
|
||||
onAnimationsCurrentTimeUpdated(currentTime) {
|
||||
for (const listener of this.animationsCurrentTimeListeners) {
|
||||
listener(currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
onElementPickerStarted() {
|
||||
this.inspector.store.dispatch(updateElementPickerEnabled(true));
|
||||
}
|
||||
|
@ -222,10 +250,16 @@ class AnimationInspector {
|
|||
this.inspector.store.dispatch(updateSidebarSize(size));
|
||||
}
|
||||
|
||||
removeAnimationsCurrentTimeListener(listener) {
|
||||
this.animationsCurrentTimeListeners =
|
||||
this.animationsCurrentTimeListeners.filter(l => l !== listener);
|
||||
}
|
||||
|
||||
async rewindAnimationsCurrentTime() {
|
||||
const animations = this.state.animations;
|
||||
await this.animationsFront.setCurrentTimes(animations, 0, true);
|
||||
this.updateAnimations(animations);
|
||||
await this.updateAnimations(animations);
|
||||
this.onAnimationsCurrentTimeUpdated(0);
|
||||
}
|
||||
|
||||
selectAnimation(animation) {
|
||||
|
@ -288,6 +322,19 @@ class AnimationInspector {
|
|||
return this.simulatedAnimation;
|
||||
}
|
||||
|
||||
stopAnimationsCurrentTimeTimer() {
|
||||
if (this.currentTimeTimer) {
|
||||
this.currentTimeTimer.destroy();
|
||||
this.currentTimeTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
startAnimationsCurrentTimeTimer() {
|
||||
const currentTimeTimer = new CurrentTimeTimer(this);
|
||||
currentTimeTimer.start();
|
||||
this.currentTimeTimer = currentTimeTimer;
|
||||
}
|
||||
|
||||
toggleElementPicker() {
|
||||
this.inspector.toolbox.highlighterUtils.togglePicker();
|
||||
}
|
||||
|
@ -325,6 +372,8 @@ class AnimationInspector {
|
|||
}
|
||||
|
||||
updateState(animations) {
|
||||
this.stopAnimationsCurrentTimeTimer();
|
||||
|
||||
this.inspector.store.dispatch(updateAnimations(animations));
|
||||
// If number of displayed animations is one, we select the animation automatically.
|
||||
// But if selected animation is in given animations, ignores.
|
||||
|
@ -334,6 +383,45 @@ class AnimationInspector {
|
|||
!animations.find(animation => animation.actorID === selectedAnimation.actorID)) {
|
||||
this.selectAnimation(animations.length === 1 ? animations[0] : null);
|
||||
}
|
||||
|
||||
if (hasPlayingAnimation(animations)) {
|
||||
this.startAnimationsCurrentTimeTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CurrentTimeTimer {
|
||||
constructor(animationInspector) {
|
||||
const timeScale = animationInspector.state.timeScale;
|
||||
this.baseCurrentTime = timeScale.documentCurrentTime - timeScale.minStartTime;
|
||||
this.startTime = animationInspector.win.performance.now();
|
||||
this.animationInspector = animationInspector;
|
||||
|
||||
this.next = this.next.bind(this);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.stop();
|
||||
this.animationInspector = null;
|
||||
}
|
||||
|
||||
next() {
|
||||
if (this.doStop) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { onAnimationsCurrentTimeUpdated, win } = this.animationInspector;
|
||||
const currentTime = this.baseCurrentTime + win.performance.now() - this.startTime;
|
||||
onAnimationsCurrentTimeUpdated(currentTime);
|
||||
win.requestAnimationFrame(this.next);
|
||||
}
|
||||
|
||||
start() {
|
||||
this.next();
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.doStop = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
|||
const AnimationList = createFactory(require("./AnimationList"));
|
||||
const AnimationListHeader = createFactory(require("./AnimationListHeader"));
|
||||
|
||||
const TimeScale = require("../utils/timescale");
|
||||
|
||||
class AnimationListContainer extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
|
@ -26,6 +24,7 @@ class AnimationListContainer extends PureComponent {
|
|||
selectAnimation: PropTypes.func.isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -40,8 +39,8 @@ class AnimationListContainer extends PureComponent {
|
|||
selectAnimation,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
} = this.props;
|
||||
const timeScale = new TimeScale(animations);
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
|
|
|
@ -8,13 +8,16 @@ const { createFactory, PureComponent } = require("devtools/client/shared/vendor/
|
|||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
|
||||
const CurrentTimeLabel = createFactory(require("./CurrentTimeLabel"));
|
||||
const PauseResumeButton = createFactory(require("./PauseResumeButton"));
|
||||
const RewindButton = createFactory(require("./RewindButton"));
|
||||
|
||||
class AnimationToolbar extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
|
||||
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
|
||||
rewindAnimationsCurrentTime: PropTypes.func.isRequired,
|
||||
setAnimationsPlayState: PropTypes.func.isRequired,
|
||||
};
|
||||
|
@ -22,7 +25,9 @@ class AnimationToolbar extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
addAnimationsCurrentTimeListener,
|
||||
animations,
|
||||
removeAnimationsCurrentTimeListener,
|
||||
rewindAnimationsCurrentTime,
|
||||
setAnimationsPlayState,
|
||||
} = this.props;
|
||||
|
@ -41,6 +46,12 @@ class AnimationToolbar extends PureComponent {
|
|||
animations,
|
||||
setAnimationsPlayState,
|
||||
}
|
||||
),
|
||||
CurrentTimeLabel(
|
||||
{
|
||||
addAnimationsCurrentTimeListener,
|
||||
removeAnimationsCurrentTimeListener,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ const SplitBox = createFactory(require("devtools/client/shared/components/splitt
|
|||
class App extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
|
||||
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
detailVisibility: PropTypes.bool.isRequired,
|
||||
emitEventForTest: PropTypes.func.isRequired,
|
||||
|
@ -26,12 +27,14 @@ class App extends PureComponent {
|
|||
getNodeFromActor: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
|
||||
rewindAnimationsCurrentTime: PropTypes.func.isRequired,
|
||||
selectAnimation: PropTypes.func.isRequired,
|
||||
setAnimationsPlayState: PropTypes.func.isRequired,
|
||||
setDetailVisibility: PropTypes.func.isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
toggleElementPicker: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
@ -42,6 +45,7 @@ class App extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
addAnimationsCurrentTimeListener,
|
||||
animations,
|
||||
detailVisibility,
|
||||
emitEventForTest,
|
||||
|
@ -50,12 +54,14 @@ class App extends PureComponent {
|
|||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
removeAnimationsCurrentTimeListener,
|
||||
rewindAnimationsCurrentTime,
|
||||
selectAnimation,
|
||||
setAnimationsPlayState,
|
||||
setDetailVisibility,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
toggleElementPicker,
|
||||
} = this.props;
|
||||
|
||||
|
@ -68,7 +74,9 @@ class App extends PureComponent {
|
|||
[
|
||||
AnimationToolbar(
|
||||
{
|
||||
addAnimationsCurrentTimeListener,
|
||||
animations,
|
||||
removeAnimationsCurrentTimeListener,
|
||||
rewindAnimationsCurrentTime,
|
||||
setAnimationsPlayState,
|
||||
}
|
||||
|
@ -98,6 +106,7 @@ class App extends PureComponent {
|
|||
selectAnimation,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
}
|
||||
),
|
||||
vert: false,
|
||||
|
@ -117,6 +126,7 @@ const mapStateToProps = state => {
|
|||
return {
|
||||
animations: state.animations.animations,
|
||||
detailVisibility: state.animations.detailVisibility,
|
||||
timeScale: state.animations.timeScale,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { PureComponent } = require("devtools/client/shared/vendor/react");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
|
||||
class CurrentTimeLabel extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
|
||||
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { addAnimationsCurrentTimeListener } = props;
|
||||
this.onCurrentTimeUpdated = this.onCurrentTimeUpdated.bind(this);
|
||||
|
||||
this.state = {
|
||||
currentTime: 0,
|
||||
};
|
||||
|
||||
addAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { removeAnimationsCurrentTimeListener } = this.props;
|
||||
removeAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
|
||||
}
|
||||
|
||||
onCurrentTimeUpdated(currentTime) {
|
||||
this.setState({ currentTime });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { currentTime } = this.state;
|
||||
|
||||
return dom.label(
|
||||
{
|
||||
className: "current-time-label",
|
||||
},
|
||||
formatStopwatchTime(currentTime)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp (in ms) as a mm:ss.mmm string.
|
||||
*
|
||||
* @param {Number} time
|
||||
* @return {String}
|
||||
*/
|
||||
function formatStopwatchTime(time) {
|
||||
// Format falsy values as 0
|
||||
if (!time) {
|
||||
return "00:00.000";
|
||||
}
|
||||
|
||||
let milliseconds = parseInt(time % 1000, 10);
|
||||
let seconds = parseInt((time / 1000) % 60, 10);
|
||||
let minutes = parseInt((time / (1000 * 60)), 10);
|
||||
|
||||
let pad = (nb, max) => {
|
||||
if (nb < max) {
|
||||
return new Array((max + "").length - (nb + "").length + 1).join("0") + nb;
|
||||
}
|
||||
return nb;
|
||||
};
|
||||
|
||||
minutes = pad(minutes, 10);
|
||||
seconds = pad(seconds, 10);
|
||||
milliseconds = pad(milliseconds, 100);
|
||||
|
||||
return `${minutes}:${seconds}.${milliseconds}`;
|
||||
}
|
||||
|
||||
module.exports = CurrentTimeLabel;
|
|
@ -9,6 +9,7 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
|||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
|
||||
const { getStr } = require("../utils/l10n");
|
||||
const { hasPlayingAnimation } = require("../utils/utils");
|
||||
|
||||
class PauseResumeButton extends PureComponent {
|
||||
static get propTypes() {
|
||||
|
@ -43,7 +44,7 @@ class PauseResumeButton extends PureComponent {
|
|||
|
||||
updateState() {
|
||||
const { animations } = this.props;
|
||||
const isPlaying = animations.some(({state}) => state.playState === "running");
|
||||
const isPlaying = hasPlayingAnimation(animations);
|
||||
this.setState({ isPlaying });
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ DevToolsModules(
|
|||
'AnimationTimelineTickList.js',
|
||||
'AnimationToolbar.js',
|
||||
'App.js',
|
||||
'CurrentTimeLabel.js',
|
||||
'KeyframesProgressTickItem.js',
|
||||
'KeyframesProgressTickList.js',
|
||||
'NoAnimationPanel.js',
|
||||
|
|
|
@ -12,6 +12,8 @@ const {
|
|||
UPDATE_SIDEBAR_SIZE,
|
||||
} = require("../actions/index");
|
||||
|
||||
const TimeScale = require("../utils/timescale");
|
||||
|
||||
const INITIAL_STATE = {
|
||||
animations: [],
|
||||
detailVisibility: false,
|
||||
|
@ -21,12 +23,14 @@ const INITIAL_STATE = {
|
|||
height: 0,
|
||||
width: 0,
|
||||
},
|
||||
timeScale: null,
|
||||
};
|
||||
|
||||
const reducers = {
|
||||
[UPDATE_ANIMATIONS](state, { animations }) {
|
||||
return Object.assign({}, state, {
|
||||
animations,
|
||||
timeScale: new TimeScale(animations),
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ class TimeScale {
|
|||
constructor(animations) {
|
||||
this.minStartTime = Infinity;
|
||||
this.maxEndTime = 0;
|
||||
this.documentCurrentTime = 0;
|
||||
|
||||
for (const animation of animations) {
|
||||
this.addAnimation(animation.state);
|
||||
}
|
||||
|
@ -38,6 +40,7 @@ class TimeScale {
|
|||
addAnimation(state) {
|
||||
let {
|
||||
delay,
|
||||
documentCurrentTime,
|
||||
duration,
|
||||
endDelay = 0,
|
||||
iterationCount,
|
||||
|
@ -67,6 +70,8 @@ class TimeScale {
|
|||
const length = toRate(delay) + rateRelativeDuration + toRate(minZero(endDelay));
|
||||
const endTime = previousStartTime + length;
|
||||
this.maxEndTime = Math.max(this.maxEndTime, endTime);
|
||||
|
||||
this.documentCurrentTime = Math.max(this.documentCurrentTime, documentCurrentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -69,6 +69,16 @@ function isAllAnimationEqual(animationsA, animationsB) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wether the animations are running at least one.
|
||||
*
|
||||
* @param {Array} animations.
|
||||
* @return {Boolean} true: playing
|
||||
*/
|
||||
function hasPlayingAnimation(animations) {
|
||||
return animations.some(({state}) => state.playState === "running");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the equality given states as effect timing.
|
||||
*
|
||||
|
@ -88,5 +98,6 @@ function isTimingEffectEqual(stateA, stateB) {
|
|||
}
|
||||
|
||||
exports.findOptimalTimeInterval = findOptimalTimeInterval;
|
||||
exports.hasPlayingAnimation = hasPlayingAnimation;
|
||||
exports.isAllAnimationEqual = isAllAnimationEqual;
|
||||
exports.isTimingEffectEqual = isTimingEffectEqual;
|
||||
|
|
Загрузка…
Ссылка в новой задаче