summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/animation/components/AnimationListContainer.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/inspector/animation/components/AnimationListContainer.js224
1 files changed, 224 insertions, 0 deletions
diff --git a/devtools/client/inspector/animation/components/AnimationListContainer.js b/devtools/client/inspector/animation/components/AnimationListContainer.js
new file mode 100644
index 0000000000..d97b368f70
--- /dev/null
+++ b/devtools/client/inspector/animation/components/AnimationListContainer.js
@@ -0,0 +1,224 @@
+/* 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 {
+ createFactory,
+ createRef,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const AnimationList = createFactory(
+ require("resource://devtools/client/inspector/animation/components/AnimationList.js")
+);
+const CurrentTimeScrubber = createFactory(
+ require("resource://devtools/client/inspector/animation/components/CurrentTimeScrubber.js")
+);
+const ProgressInspectionPanel = createFactory(
+ require("resource://devtools/client/inspector/animation/components/ProgressInspectionPanel.js")
+);
+
+const {
+ findOptimalTimeInterval,
+} = require("resource://devtools/client/inspector/animation/utils/utils.js");
+const {
+ getStr,
+} = require("resource://devtools/client/inspector/animation/utils/l10n.js");
+const { throttle } = require("resource://devtools/shared/throttle.js");
+
+// The minimum spacing between 2 time graduation headers in the timeline (px).
+const TIME_GRADUATION_MIN_SPACING = 40;
+
+class AnimationListContainer extends PureComponent {
+ static get propTypes() {
+ return {
+ addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
+ animations: PropTypes.arrayOf(PropTypes.object).isRequired,
+ direction: PropTypes.string.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ getAnimatedPropertyMap: PropTypes.func.isRequired,
+ getNodeFromActor: PropTypes.func.isRequired,
+ removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
+ selectAnimation: PropTypes.func.isRequired,
+ setAnimationsCurrentTime: PropTypes.func.isRequired,
+ setHighlightedNode: PropTypes.func.isRequired,
+ setSelectedNode: PropTypes.func.isRequired,
+ sidebarWidth: PropTypes.number.isRequired,
+ simulateAnimation: PropTypes.func.isRequired,
+ timeScale: PropTypes.object.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this._ref = createRef();
+
+ this.updateDisplayableRange = throttle(
+ this.updateDisplayableRange,
+ 100,
+ this
+ );
+
+ this.state = {
+ // tick labels and lines on the progress inspection panel
+ ticks: [],
+ // Displayable range.
+ displayableRange: { startIndex: 0, endIndex: 0 },
+ };
+ }
+
+ componentDidMount() {
+ this.updateTicks(this.props);
+
+ const current = this._ref.current;
+ this._inspectionPanelEl = current.querySelector(
+ ".progress-inspection-panel"
+ );
+ this._inspectionPanelEl.addEventListener("scroll", () => {
+ this.updateDisplayableRange();
+ });
+
+ this._animationListEl = current.querySelector(".animation-list");
+ const resizeObserver = new current.ownerGlobal.ResizeObserver(() => {
+ this.updateDisplayableRange();
+ });
+ resizeObserver.observe(this._animationListEl);
+
+ const animationItemEl = current.querySelector(".animation-item");
+ this._itemHeight = animationItemEl.offsetHeight;
+
+ this.updateDisplayableRange();
+ }
+
+ componentDidUpdate(prevProps) {
+ const { timeScale, sidebarWidth } = this.props;
+
+ if (
+ timeScale.getDuration() !== prevProps.timeScale.getDuration() ||
+ timeScale.zeroPositionTime !== prevProps.timeScale.zeroPositionTime ||
+ sidebarWidth !== prevProps.sidebarWidth
+ ) {
+ this.updateTicks(this.props);
+ }
+ }
+
+ /**
+ * Since it takes too much time if we render all of animation graphs,
+ * we restrict to render the items that are not in displaying area.
+ * This function calculates the displayable item range.
+ */
+ updateDisplayableRange() {
+ const count =
+ Math.floor(this._animationListEl.offsetHeight / this._itemHeight) + 1;
+ const index = Math.floor(
+ this._inspectionPanelEl.scrollTop / this._itemHeight
+ );
+ this.setState({
+ displayableRange: { startIndex: index, endIndex: index + count },
+ });
+ }
+
+ updateTicks(props) {
+ const { animations, timeScale } = props;
+ const tickLinesEl = this._ref.current.querySelector(".tick-lines");
+ const width = tickLinesEl.offsetWidth;
+ const animationDuration = timeScale.getDuration();
+ const minTimeInterval =
+ (TIME_GRADUATION_MIN_SPACING * animationDuration) / width;
+ const intervalLength = findOptimalTimeInterval(minTimeInterval);
+ const intervalWidth = (intervalLength * width) / animationDuration;
+ const tickCount = parseInt(width / intervalWidth, 10);
+ const isAllDurationInfinity = animations.every(
+ animation => animation.state.duration === Infinity
+ );
+ const zeroBasePosition =
+ width * (timeScale.zeroPositionTime / animationDuration);
+ const shiftWidth = zeroBasePosition % intervalWidth;
+ const needToShift = zeroBasePosition !== 0 && shiftWidth !== 0;
+
+ const ticks = [];
+ // Need to display first graduation since position will be shifted.
+ if (needToShift) {
+ const label = timeScale.formatTime(timeScale.distanceToRelativeTime(0));
+ ticks.push({ position: 0, label, width: shiftWidth });
+ }
+
+ for (let i = 0; i <= tickCount; i++) {
+ const position = ((i * intervalWidth + shiftWidth) * 100) / width;
+ const distance = timeScale.distanceToRelativeTime(position);
+ const label =
+ isAllDurationInfinity && i === tickCount
+ ? getStr("player.infiniteTimeLabel")
+ : timeScale.formatTime(distance);
+ ticks.push({ position, label, width: intervalWidth });
+ }
+
+ this.setState({ ticks });
+ }
+
+ render() {
+ const {
+ addAnimationsCurrentTimeListener,
+ animations,
+ direction,
+ dispatch,
+ getAnimatedPropertyMap,
+ getNodeFromActor,
+ removeAnimationsCurrentTimeListener,
+ selectAnimation,
+ setAnimationsCurrentTime,
+ setHighlightedNode,
+ setSelectedNode,
+ simulateAnimation,
+ timeScale,
+ } = this.props;
+ const { displayableRange, ticks } = this.state;
+
+ return dom.div(
+ {
+ className: "animation-list-container",
+ ref: this._ref,
+ },
+ ProgressInspectionPanel({
+ indicator: CurrentTimeScrubber({
+ addAnimationsCurrentTimeListener,
+ direction,
+ removeAnimationsCurrentTimeListener,
+ setAnimationsCurrentTime,
+ timeScale,
+ }),
+ list: AnimationList({
+ animations,
+ dispatch,
+ displayableRange,
+ getAnimatedPropertyMap,
+ getNodeFromActor,
+ selectAnimation,
+ setHighlightedNode,
+ setSelectedNode,
+ simulateAnimation,
+ timeScale,
+ }),
+ ticks,
+ })
+ );
+ }
+}
+
+const mapStateToProps = state => {
+ return {
+ sidebarWidth: state.animations.sidebarSize
+ ? state.animations.sidebarSize.width
+ : 0,
+ };
+};
+
+module.exports = connect(mapStateToProps)(AnimationListContainer);