tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AnimationListContainer.js (7202B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  createFactory,
      9  createRef,
     10  PureComponent,
     11 } = require("resource://devtools/client/shared/vendor/react.mjs");
     12 const {
     13  connect,
     14 } = require("resource://devtools/client/shared/vendor/react-redux.js");
     15 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     16 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     17 
     18 const AnimationList = createFactory(
     19  require("resource://devtools/client/inspector/animation/components/AnimationList.js")
     20 );
     21 const CurrentTimeScrubber = createFactory(
     22  require("resource://devtools/client/inspector/animation/components/CurrentTimeScrubber.js")
     23 );
     24 const ProgressInspectionPanel = createFactory(
     25  require("resource://devtools/client/inspector/animation/components/ProgressInspectionPanel.js")
     26 );
     27 
     28 const {
     29  findOptimalTimeInterval,
     30 } = require("resource://devtools/client/inspector/animation/utils/utils.js");
     31 const {
     32  getStr,
     33 } = require("resource://devtools/client/inspector/animation/utils/l10n.js");
     34 const { throttle } = require("resource://devtools/shared/throttle.js");
     35 
     36 // The minimum spacing between 2 time graduation headers in the timeline (px).
     37 const TIME_GRADUATION_MIN_SPACING = 40;
     38 
     39 class AnimationListContainer extends PureComponent {
     40  static get propTypes() {
     41    return {
     42      addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
     43      animations: PropTypes.arrayOf(PropTypes.object).isRequired,
     44      direction: PropTypes.string.isRequired,
     45      dispatch: PropTypes.func.isRequired,
     46      getAnimatedPropertyMap: PropTypes.func.isRequired,
     47      getNodeFromActor: PropTypes.func.isRequired,
     48      removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
     49      selectAnimation: PropTypes.func.isRequired,
     50      setAnimationsCurrentTime: PropTypes.func.isRequired,
     51      setHighlightedNode: PropTypes.func.isRequired,
     52      setSelectedNode: PropTypes.func.isRequired,
     53      sidebarWidth: PropTypes.number.isRequired,
     54      simulateAnimation: PropTypes.func.isRequired,
     55      timeScale: PropTypes.object.isRequired,
     56    };
     57  }
     58 
     59  constructor(props) {
     60    super(props);
     61 
     62    this._ref = createRef();
     63 
     64    this.updateDisplayableRange = throttle(
     65      this.updateDisplayableRange,
     66      100,
     67      this
     68    );
     69 
     70    this.state = {
     71      // tick labels and lines on the progress inspection panel
     72      ticks: [],
     73      // Displayable range.
     74      displayableRange: { startIndex: 0, endIndex: 0 },
     75    };
     76  }
     77 
     78  componentDidMount() {
     79    this.updateTicks(this.props);
     80 
     81    const current = this._ref.current;
     82    this._inspectionPanelEl = current.querySelector(
     83      ".progress-inspection-panel"
     84    );
     85    this._inspectionPanelEl.addEventListener("scroll", () => {
     86      this.updateDisplayableRange();
     87    });
     88 
     89    this._animationListEl = current.querySelector(".animation-list");
     90    const resizeObserver = new current.ownerGlobal.ResizeObserver(() => {
     91      this.updateDisplayableRange();
     92    });
     93    resizeObserver.observe(this._animationListEl);
     94 
     95    const animationItemEl = current.querySelector(".animation-item");
     96    this._itemHeight = animationItemEl.offsetHeight;
     97 
     98    this.updateDisplayableRange();
     99  }
    100 
    101  componentDidUpdate(prevProps) {
    102    const { timeScale, sidebarWidth } = this.props;
    103 
    104    if (
    105      timeScale.getDuration() !== prevProps.timeScale.getDuration() ||
    106      timeScale.zeroPositionTime !== prevProps.timeScale.zeroPositionTime ||
    107      sidebarWidth !== prevProps.sidebarWidth
    108    ) {
    109      this.updateTicks(this.props);
    110    }
    111  }
    112 
    113  /**
    114   * Since it takes too much time if we render all of animation graphs,
    115   * we restrict to render the items that are not in displaying area.
    116   * This function calculates the displayable item range.
    117   */
    118  updateDisplayableRange() {
    119    const count =
    120      Math.floor(this._animationListEl.offsetHeight / this._itemHeight) + 1;
    121    const index = Math.floor(
    122      this._inspectionPanelEl.scrollTop / this._itemHeight
    123    );
    124    this.setState({
    125      displayableRange: { startIndex: index, endIndex: index + count },
    126    });
    127  }
    128 
    129  updateTicks(props) {
    130    const { animations, timeScale } = props;
    131    const tickLinesEl = this._ref.current.querySelector(".tick-lines");
    132    const width = tickLinesEl.offsetWidth;
    133    const animationDuration = timeScale.getDuration();
    134    const minTimeInterval =
    135      (TIME_GRADUATION_MIN_SPACING * animationDuration) / width;
    136    const intervalLength = findOptimalTimeInterval(minTimeInterval);
    137    const intervalWidth = (intervalLength * width) / animationDuration;
    138    const tickCount = parseInt(width / intervalWidth, 10);
    139    const isAllDurationInfinity = animations.every(
    140      animation => animation.state.duration === Infinity
    141    );
    142    const zeroBasePosition =
    143      width * (timeScale.zeroPositionTime / animationDuration);
    144    const shiftWidth = zeroBasePosition % intervalWidth;
    145    const needToShift = zeroBasePosition !== 0 && shiftWidth !== 0;
    146 
    147    const ticks = [];
    148    // Need to display first graduation since position will be shifted.
    149    if (needToShift) {
    150      const label = timeScale.formatTime(timeScale.distanceToRelativeTime(0));
    151      ticks.push({ position: 0, label, width: shiftWidth });
    152    }
    153 
    154    for (let i = 0; i <= tickCount; i++) {
    155      const position = ((i * intervalWidth + shiftWidth) * 100) / width;
    156      const distance = timeScale.distanceToRelativeTime(position);
    157      const label =
    158        isAllDurationInfinity && i === tickCount
    159          ? getStr("player.infiniteTimeLabel")
    160          : timeScale.formatTime(distance);
    161      ticks.push({ position, label, width: intervalWidth });
    162    }
    163 
    164    this.setState({ ticks });
    165  }
    166 
    167  render() {
    168    const {
    169      addAnimationsCurrentTimeListener,
    170      animations,
    171      direction,
    172      dispatch,
    173      getAnimatedPropertyMap,
    174      getNodeFromActor,
    175      removeAnimationsCurrentTimeListener,
    176      selectAnimation,
    177      setAnimationsCurrentTime,
    178      setHighlightedNode,
    179      setSelectedNode,
    180      simulateAnimation,
    181      timeScale,
    182    } = this.props;
    183    const { displayableRange, ticks } = this.state;
    184 
    185    return dom.div(
    186      {
    187        className: "animation-list-container",
    188        ref: this._ref,
    189      },
    190      ProgressInspectionPanel({
    191        indicator: CurrentTimeScrubber({
    192          addAnimationsCurrentTimeListener,
    193          direction,
    194          removeAnimationsCurrentTimeListener,
    195          setAnimationsCurrentTime,
    196          timeScale,
    197        }),
    198        list: AnimationList({
    199          animations,
    200          dispatch,
    201          displayableRange,
    202          getAnimatedPropertyMap,
    203          getNodeFromActor,
    204          selectAnimation,
    205          setHighlightedNode,
    206          setSelectedNode,
    207          simulateAnimation,
    208          timeScale,
    209        }),
    210        ticks,
    211      })
    212    );
    213  }
    214 }
    215 
    216 const mapStateToProps = state => {
    217  return {
    218    sidebarWidth: state.animations.sidebarSize
    219      ? state.animations.sidebarSize.width
    220      : 0,
    221  };
    222 };
    223 
    224 module.exports = connect(mapStateToProps)(AnimationListContainer);