diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/client/inspector/animation/components/graph/SummaryGraphPath.js | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/inspector/animation/components/graph/SummaryGraphPath.js')
-rw-r--r-- | devtools/client/inspector/animation/components/graph/SummaryGraphPath.js | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js b/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js new file mode 100644 index 0000000000..0183c7413e --- /dev/null +++ b/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js @@ -0,0 +1,282 @@ +/* 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 { + Component, + createFactory, +} = require("resource://devtools/client/shared/vendor/react.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const ReactDOM = require("resource://devtools/client/shared/vendor/react-dom.js"); + +const ComputedTimingPath = createFactory( + require("resource://devtools/client/inspector/animation/components/graph/ComputedTimingPath.js") +); +const EffectTimingPath = createFactory( + require("resource://devtools/client/inspector/animation/components/graph/EffectTimingPath.js") +); +const NegativeDelayPath = createFactory( + require("resource://devtools/client/inspector/animation/components/graph/NegativeDelayPath.js") +); +const NegativeEndDelayPath = createFactory( + require("resource://devtools/client/inspector/animation/components/graph/NegativeEndDelayPath.js") +); +const { + DEFAULT_GRAPH_HEIGHT, +} = require("resource://devtools/client/inspector/animation/utils/graph-helper.js"); + +// Minimum opacity for semitransparent fill color for keyframes's easing graph. +const MIN_KEYFRAMES_EASING_OPACITY = 0.5; + +class SummaryGraphPath extends Component { + static get propTypes() { + return { + animation: PropTypes.object.isRequired, + getAnimatedPropertyMap: PropTypes.object.isRequired, + simulateAnimation: PropTypes.func.isRequired, + timeScale: PropTypes.object.isRequired, + }; + } + + constructor(props) { + super(props); + + this.state = { + // Duration which can display in one pixel. + durationPerPixel: 0, + // To avoid rendering while the state is updating + // since we call an async function in updateState. + isStateUpdating: false, + // List of keyframe which consists by only offset and easing. + keyframesList: [], + }; + } + + componentDidMount() { + // No need to set isStateUpdating state since paint sequence is finish here. + this.updateState(this.props); + } + + // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 + UNSAFE_componentWillReceiveProps(nextProps) { + this.setState({ isStateUpdating: true }); + this.updateState(nextProps); + } + + shouldComponentUpdate(nextProps, nextState) { + return !nextState.isStateUpdating; + } + + /** + * 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 }; + }); + }); + } + + /** + * 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; + } + + updateState(props) { + const { animation, getAnimatedPropertyMap, timeScale } = props; + + let animatedPropertyMap = null; + let thisEl = null; + + try { + animatedPropertyMap = getAnimatedPropertyMap(animation); + thisEl = ReactDOM.findDOMNode(this); + } catch (e) { + // Expected if we've already been destroyed or other node have been selected + // in the meantime. + console.error(e); + return; + } + + const keyframesList = + this.getOffsetAndEasingOnlyKeyframes(animatedPropertyMap); + const totalDuration = + timeScale.getDuration() * Math.abs(animation.state.playbackRate); + const durationPerPixel = totalDuration / thisEl.parentNode.clientWidth; + + this.setState({ + durationPerPixel, + isStateUpdating: false, + keyframesList, + }); + } + + render() { + const { durationPerPixel, keyframesList } = this.state; + const { animation, simulateAnimation, timeScale } = this.props; + + if (!durationPerPixel || !animation.state.type) { + // Undefined animation.state.type means that the animation had been removed already. + // Even if the animation was removed, we still need the empty svg since the + // component might be re-used. + return dom.svg(); + } + + const { playbackRate } = animation.state; + const { createdTime } = animation.state.absoluteValues; + const absPlaybackRate = Math.abs(playbackRate); + + // Absorb the playbackRate in viewBox of SVG and offset of child path elements + // in order to each graph path components can draw without considering to the + // playbackRate. + const offset = createdTime * absPlaybackRate; + const startTime = timeScale.minStartTime * absPlaybackRate; + const totalDuration = timeScale.getDuration() * absPlaybackRate; + const opacity = Math.max( + 1 / keyframesList.length, + MIN_KEYFRAMES_EASING_OPACITY + ); + + return dom.svg( + { + className: "animation-summary-graph-path", + preserveAspectRatio: "none", + viewBox: + `${startTime} -${DEFAULT_GRAPH_HEIGHT} ` + + `${totalDuration} ${DEFAULT_GRAPH_HEIGHT}`, + }, + keyframesList.map(keyframes => + ComputedTimingPath({ + animation, + durationPerPixel, + keyframes, + offset, + opacity, + simulateAnimation, + totalDuration, + }) + ), + animation.state.easing !== "linear" + ? EffectTimingPath({ + animation, + durationPerPixel, + offset, + simulateAnimation, + totalDuration, + }) + : null, + animation.state.delay < 0 + ? keyframesList.map(keyframes => { + return NegativeDelayPath({ + animation, + durationPerPixel, + keyframes, + offset, + simulateAnimation, + totalDuration, + }); + }) + : null, + animation.state.iterationCount && animation.state.endDelay < 0 + ? keyframesList.map(keyframes => { + return NegativeEndDelayPath({ + animation, + durationPerPixel, + keyframes, + offset, + simulateAnimation, + totalDuration, + }); + }) + : null + ); + } +} + +module.exports = SummaryGraphPath; |