summaryrefslogtreecommitdiffstats
path: root/devtools/client/animationinspector/components/rate-selector.js
blob: e46664e6a7d8a86c9de2272d597939548953c1f7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 EventEmitter = require("devtools/shared/event-emitter");
const {createNode} = require("devtools/client/animationinspector/utils");
const { LocalizationHelper } = require("devtools/shared/l10n");
const L10N =
      new LocalizationHelper("devtools/client/locales/animationinspector.properties");

// List of playback rate presets displayed in the timeline toolbar.
const PLAYBACK_RATES = [.1, .25, .5, 1, 2, 5, 10];

/**
 * UI component responsible for displaying a playback rate selector UI.
 * The rendering logic is such that a predefined list of rates is generated.
 * If *all* animations passed to render share the same rate, then that rate is
 * selected in the <select> element, otherwise, the empty value is selected.
 * If the rate that all animations share isn't part of the list of predefined
 * rates, than that rate is added to the list.
 */
function RateSelector() {
  this.onRateChanged = this.onRateChanged.bind(this);
  EventEmitter.decorate(this);
}

exports.RateSelector = RateSelector;

RateSelector.prototype = {
  init: function (containerEl) {
    this.selectEl = createNode({
      parent: containerEl,
      nodeType: "select",
      attributes: {
        "class": "devtools-button",
        "title": L10N.getStr("timeline.rateSelectorTooltip")
      }
    });

    this.selectEl.addEventListener("change", this.onRateChanged);
  },

  destroy: function () {
    this.selectEl.removeEventListener("change", this.onRateChanged);
    this.selectEl.remove();
    this.selectEl = null;
  },

  getAnimationsRates: function (animations) {
    return sortedUnique(animations.map(a => a.state.playbackRate));
  },

  getAllRates: function (animations) {
    let animationsRates = this.getAnimationsRates(animations);
    if (animationsRates.length > 1) {
      return PLAYBACK_RATES;
    }

    return sortedUnique(PLAYBACK_RATES.concat(animationsRates));
  },

  render: function (animations) {
    let allRates = this.getAnimationsRates(animations);
    let hasOneRate = allRates.length === 1;

    this.selectEl.innerHTML = "";

    if (!hasOneRate) {
      // When the animations displayed have mixed playback rates, we can't
      // select any of the predefined ones, instead, insert an empty rate.
      createNode({
        parent: this.selectEl,
        nodeType: "option",
        attributes: {value: "", selector: "true"},
        textContent: "-"
      });
    }
    for (let rate of this.getAllRates(animations)) {
      let option = createNode({
        parent: this.selectEl,
        nodeType: "option",
        attributes: {value: rate},
        textContent: L10N.getFormatStr("player.playbackRateLabel", rate)
      });

      // If there's only one rate and this is the option for it, select it.
      if (hasOneRate && rate === allRates[0]) {
        option.setAttribute("selected", "true");
      }
    }
  },

  onRateChanged: function () {
    let rate = parseFloat(this.selectEl.value);
    if (!isNaN(rate)) {
      this.emit("rate-changed", rate);
    }
  }
};

let sortedUnique = arr => [...new Set(arr)].sort((a, b) => a > b);