Bug 1406285 - Part 4: Implement getting tracks(keyframes) from server. r=gl

MozReview-Commit-ID: KmnnFLZIs9a

--HG--
extra : rebase_source : d8e0d7982ca8339738d2277f35927721b3b935b1
This commit is contained in:
Daisuke Akatsuka 2018-01-18 12:17:11 +09:00
Родитель 8e47d99293
Коммит 603e851dab
4 изменённых файлов: 233 добавлений и 19 удалений

Просмотреть файл

@ -90,6 +90,59 @@ class AnimationInspector {
this.inspector = null;
}
/**
* Return a map of animated property from given animation actor.
*
* @param {Object} animation
* @return {Map} A map of animated property
* key: {String} Animated property name
* value: {Array} Array of keyframe object
* Also, the keyframe object is consisted as following.
* {
* value: {String} style,
* offset: {Number} offset of keyframe,
* easing: {String} easing from this keyframe to next keyframe,
* distance: {Number} use as y coordinate in graph,
* }
*/
async getAnimatedPropertyMap(animation) {
let properties = [];
try {
properties = await animation.getProperties();
} catch (e) {
// Expected if we've already been destroyed in the meantime.
console.error(e);
}
const animatedPropertyMap = new Map();
for (const { name, values } of properties) {
const keyframes = values.map(({ value, offset, easing, distance = 0 }) => {
offset = parseFloat(offset.toFixed(3));
return { value, offset, easing, distance };
});
animatedPropertyMap.set(name, keyframes);
}
return animatedPropertyMap;
}
getNodeFromActor(actorID) {
return this.inspector.walker.getNodeFromActor(actorID, ["node"]);
}
isPanelVisible() {
return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
this.inspector.toolbox.currentToolId === "inspector" &&
this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
}
toggleElementPicker() {
this.inspector.toolbox.highlighterUtils.togglePicker();
}
async update() {
if (!this.inspector || !this.isPanelVisible()) {
// AnimationInspector was destroyed already or the panel is hidden.
@ -105,6 +158,15 @@ class AnimationInspector {
: [];
if (!this.animations || !isAllAnimationEqual(animations, this.animations)) {
await Promise.all(animations.map(animation => {
return new Promise(resolve => {
this.getAnimatedPropertyMap(animation).then(animatedPropertyMap => {
animation.animatedPropertyMap = animatedPropertyMap;
resolve();
});
});
}));
this.inspector.store.dispatch(updateAnimations(animations));
this.animations = animations;
}
@ -112,20 +174,6 @@ class AnimationInspector {
done();
}
isPanelVisible() {
return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
this.inspector.toolbox.currentToolId === "inspector" &&
this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
}
getNodeFromActor(actorID) {
return this.inspector.walker.getNodeFromActor(actorID, ["node"]);
}
toggleElementPicker() {
this.inspector.toolbox.highlighterUtils.togglePicker();
}
onElementPickerStarted() {
this.inspector.store.dispatch(updateElementPickerEnabled(true));
}

Просмотреть файл

@ -0,0 +1,26 @@
/* 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 PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
class ComputedTimingPath extends PureComponent {
static get propTypes() {
return {
animation: PropTypes.object.isRequired,
durationPerPixel: PropTypes.number.isRequired,
keyframes: PropTypes.object.isRequired,
totalDisplayedDuration: PropTypes.number.isRequired,
};
}
render() {
return dom.g({});
}
}
module.exports = ComputedTimingPath;

Просмотреть файл

@ -4,9 +4,12 @@
"use strict";
const { PureComponent } = require("devtools/client/shared/vendor/react");
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const ComputedTimingPath = createFactory(require("./ComputedTimingPath"));
class SummaryGraphPath extends PureComponent {
static get propTypes() {
@ -16,21 +19,157 @@ class SummaryGraphPath extends PureComponent {
};
}
render() {
constructor(props) {
super(props);
this.state = {
durationPerPixel: 0,
};
}
componentDidMount() {
this.updateDurationPerPixel();
}
/**
* Return animatable keyframes list which has only offset and easing.
* Also, this method remove duplicate keyframes.
* For example, if the given animatedPropertyMap is,
* [
* {
* key: "color",
* values: [
* {
* offset: 0,
* easing: "ease",
* value: "rgb(255, 0, 0)",
* },
* {
* offset: 1,
* value: "rgb(0, 255, 0)",
* },
* ],
* },
* {
* key: "opacity",
* values: [
* {
* offset: 0,
* easing: "ease",
* value: 0,
* },
* {
* offset: 1,
* value: 1,
* },
* ],
* },
* ]
*
* then this method returns,
* [
* [
* {
* offset: 0,
* easing: "ease",
* },
* {
* offset: 1,
* },
* ],
* ]
*
* @param {Map} animated property map
* which can get form getAnimatedPropertyMap in animation.js
* @return {Array} list of keyframes which has only easing and offset.
*/
getOffsetAndEasingOnlyKeyframes(animatedPropertyMap) {
return [...animatedPropertyMap.values()].filter((keyframes1, i, self) => {
return i !== self.findIndex((keyframes2, j) => {
return this.isOffsetAndEasingKeyframesEqual(keyframes1, keyframes2) ? j : -1;
});
}).map(keyframes => {
return keyframes.map(keyframe => {
return { easing: keyframe.easing, offset: keyframe.offset };
});
});
}
getTotalDuration(animation, timeScale) {
return animation.state.playbackRate * timeScale.getDuration();
}
/**
* Return true if given keyframes have same length, offset and easing.
*
* @param {Array} keyframes1
* @param {Array} keyframes2
* @return {Boolean} true: equals
*/
isOffsetAndEasingKeyframesEqual(keyframes1, keyframes2) {
if (keyframes1.length !== keyframes2.length) {
return false;
}
for (let i = 0; i < keyframes1.length; i++) {
const keyframe1 = keyframes1[i];
const keyframe2 = keyframes2[i];
if (keyframe1.offset !== keyframe2.offset ||
keyframe1.easing !== keyframe2.easing) {
return false;
}
}
return true;
}
updateDurationPerPixel() {
const {
animation,
timeScale,
} = this.props;
const totalDisplayedDuration = animation.state.playbackRate * timeScale.getDuration();
const thisEl = ReactDOM.findDOMNode(this);
const totalDuration = this.getTotalDuration(animation, timeScale);
const durationPerPixel = totalDuration / thisEl.parentNode.clientWidth;
this.setState({ durationPerPixel });
}
render() {
const { durationPerPixel } = this.state;
if (!durationPerPixel) {
return dom.svg();
}
const {
animation,
timeScale,
} = this.props;
const totalDuration = this.getTotalDuration(animation, timeScale);
const startTime = timeScale.minStartTime;
const keyframesList =
this.getOffsetAndEasingOnlyKeyframes(animation.animatedPropertyMap);
return dom.svg(
{
className: "animation-summary-graph-path",
preserveAspectRatio: "none",
viewBox: `${ startTime } -1 ${ totalDisplayedDuration } 1`
}
viewBox: `${ startTime } -1 ${ totalDuration } 1`
},
keyframesList.map(keyframes =>
ComputedTimingPath(
{
animation,
durationPerPixel,
keyframes,
totalDuration,
}
)
)
);
}
}

Просмотреть файл

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'ComputedTimingPath.js',
'SummaryGraph.js',
'SummaryGraphPath.js'
)