450 lines
13 KiB
JavaScript
450 lines
13 KiB
JavaScript
/* 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("resource://devtools/client/shared/vendor/react.mjs");
|
|
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
|
|
|
|
// Show max 10 iterations for infinite animations
|
|
// to give users a clue that the animation does repeat.
|
|
const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10;
|
|
|
|
class TimingPath extends PureComponent {
|
|
/**
|
|
* Render a graph of given parameters and return as <path> element list.
|
|
*
|
|
* @param {Object} state
|
|
* State of animation.
|
|
* @param {SummaryGraphHelper} helper
|
|
* Instance of SummaryGraphHelper.
|
|
* @return {Array}
|
|
* list of <path> element.
|
|
*/
|
|
renderGraph(state, helper) {
|
|
// Starting time of main iteration.
|
|
let mainIterationStartTime = 0;
|
|
let iterationStart = state.iterationStart;
|
|
let iterationCount = state.iterationCount ? state.iterationCount : Infinity;
|
|
|
|
const pathList = [];
|
|
|
|
// Append delay.
|
|
if (state.delay > 0) {
|
|
this.renderDelay(pathList, state, helper);
|
|
mainIterationStartTime = state.delay;
|
|
} else {
|
|
const negativeDelayCount = -state.delay / state.duration;
|
|
// Move to forward the starting point for negative delay.
|
|
iterationStart += negativeDelayCount;
|
|
// Consume iteration count by negative delay.
|
|
if (iterationCount !== Infinity) {
|
|
iterationCount -= negativeDelayCount;
|
|
}
|
|
}
|
|
|
|
if (state.duration === Infinity) {
|
|
this.renderInfinityDuration(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
helper
|
|
);
|
|
return pathList;
|
|
}
|
|
|
|
// Append 1st section of iterations,
|
|
// This section is only useful in cases where iterationStart has decimals.
|
|
// e.g.
|
|
// if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75.
|
|
const firstSectionCount =
|
|
iterationStart % 1 === 0
|
|
? 0
|
|
: Math.min(1 - (iterationStart % 1), iterationCount);
|
|
|
|
if (firstSectionCount) {
|
|
this.renderFirstIteration(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
firstSectionCount,
|
|
helper
|
|
);
|
|
}
|
|
|
|
if (iterationCount === Infinity) {
|
|
// If the animation repeats infinitely,
|
|
// we fill the remaining area with iteration paths.
|
|
this.renderInfinity(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
firstSectionCount,
|
|
helper
|
|
);
|
|
} else {
|
|
// Otherwise, we show remaining iterations, endDelay and fill.
|
|
|
|
// Append forwards fill-mode.
|
|
if (state.fill === "both" || state.fill === "forwards") {
|
|
this.renderForwardsFill(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
iterationCount,
|
|
helper
|
|
);
|
|
}
|
|
|
|
// Append middle section of iterations.
|
|
// e.g.
|
|
// if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2.
|
|
const middleSectionCount = Math.floor(iterationCount - firstSectionCount);
|
|
this.renderMiddleIterations(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
firstSectionCount,
|
|
middleSectionCount,
|
|
helper
|
|
);
|
|
|
|
// Append last section of iterations, if there is remaining iteration.
|
|
// e.g.
|
|
// if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25.
|
|
const lastSectionCount =
|
|
iterationCount - middleSectionCount - firstSectionCount;
|
|
if (lastSectionCount) {
|
|
this.renderLastIteration(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
firstSectionCount,
|
|
middleSectionCount,
|
|
lastSectionCount,
|
|
helper
|
|
);
|
|
}
|
|
|
|
// Append endDelay.
|
|
if (state.endDelay > 0) {
|
|
this.renderEndDelay(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
iterationCount,
|
|
helper
|
|
);
|
|
}
|
|
}
|
|
return pathList;
|
|
}
|
|
|
|
/**
|
|
* Render 'delay' part in animation and add a <path> element to given pathList.
|
|
*
|
|
* @param {Array} pathList
|
|
* Add rendered <path> element to this array.
|
|
* @param {Object} state
|
|
* State of animation.
|
|
* @param {SummaryGraphHelper} helper
|
|
* Instance of SummaryGraphHelper.
|
|
*/
|
|
renderDelay(pathList, state, helper) {
|
|
const startSegment = helper.getSegment(0);
|
|
const endSegment = { x: state.delay, y: startSegment.y };
|
|
const segments = [startSegment, endSegment];
|
|
pathList.push(
|
|
dom.path({
|
|
className: "animation-delay-path",
|
|
d: helper.toPathString(segments),
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render 1st section of iterations and add a <path> element to given pathList.
|
|
* This section is only useful in cases where iterationStart has decimals.
|
|
*
|
|
* @param {Array} pathList
|
|
* Add rendered <path> element to this array.
|
|
* @param {Object} state
|
|
* State of animation.
|
|
* @param {Number} mainIterationStartTime
|
|
* Start time of main iteration.
|
|
* @param {Number} firstSectionCount
|
|
* Iteration count of first section.
|
|
* @param {SummaryGraphHelper} helper
|
|
* Instance of SummaryGraphHelper.
|
|
*/
|
|
renderFirstIteration(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
firstSectionCount,
|
|
helper
|
|
) {
|
|
const startTime = mainIterationStartTime;
|
|
const endTime = startTime + firstSectionCount * state.duration;
|
|
const segments = helper.createPathSegments(startTime, endTime);
|
|
pathList.push(
|
|
dom.path({
|
|
className: "animation-iteration-path",
|
|
d: helper.toPathString(segments),
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render middle iterations and add <path> elements to given pathList.
|
|
*
|
|
* @param {Array} pathList
|
|
* Add rendered <path> elements to this array.
|
|
* @param {Object} state
|
|
* State of animation.
|
|
* @param {Number} mainIterationStartTime
|
|
* Starting time of main iteration.
|
|
* @param {Number} firstSectionCount
|
|
* Iteration count of first section.
|
|
* @param {Number} middleSectionCount
|
|
* Iteration count of middle section.
|
|
* @param {SummaryGraphHelper} helper
|
|
* Instance of SummaryGraphHelper.
|
|
*/
|
|
renderMiddleIterations(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
firstSectionCount,
|
|
middleSectionCount,
|
|
helper
|
|
) {
|
|
const offset = mainIterationStartTime + firstSectionCount * state.duration;
|
|
for (let i = 0; i < middleSectionCount; i++) {
|
|
// Get the path segments of each iteration.
|
|
const startTime = offset + i * state.duration;
|
|
const endTime = startTime + state.duration;
|
|
const segments = helper.createPathSegments(startTime, endTime);
|
|
pathList.push(
|
|
dom.path({
|
|
className: "animation-iteration-path",
|
|
d: helper.toPathString(segments),
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render last section of iterations and add a <path> element to given pathList.
|
|
* This section is only useful in cases where iterationStart has decimals.
|
|
*
|
|
* @param {Array} pathList
|
|
* Add rendered <path> elements to this array.
|
|
* @param {Object} state
|
|
* State of animation.
|
|
* @param {Number} mainIterationStartTime
|
|
* Starting time of main iteration.
|
|
* @param {Number} firstSectionCount
|
|
* Iteration count of first section.
|
|
* @param {Number} middleSectionCount
|
|
* Iteration count of middle section.
|
|
* @param {Number} lastSectionCount
|
|
* Iteration count of last section.
|
|
* @param {SummaryGraphHelper} helper
|
|
* Instance of SummaryGraphHelper.
|
|
*/
|
|
renderLastIteration(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
firstSectionCount,
|
|
middleSectionCount,
|
|
lastSectionCount,
|
|
helper
|
|
) {
|
|
const startTime =
|
|
mainIterationStartTime +
|
|
(firstSectionCount + middleSectionCount) * state.duration;
|
|
const endTime = startTime + lastSectionCount * state.duration;
|
|
const segments = helper.createPathSegments(startTime, endTime);
|
|
pathList.push(
|
|
dom.path({
|
|
className: "animation-iteration-path",
|
|
d: helper.toPathString(segments),
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render infinity iterations and add <path> elements to given pathList.
|
|
*
|
|
* @param {Array} pathList
|
|
* Add rendered <path> elements to this array.
|
|
* @param {Object} state
|
|
* State of animation.
|
|
* @param {Number} mainIterationStartTime
|
|
* Starting time of main iteration.
|
|
* @param {Number} firstSectionCount
|
|
* Iteration count of first section.
|
|
* @param {SummaryGraphHelper} helper
|
|
* Instance of SummaryGraphHelper.
|
|
*/
|
|
renderInfinity(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
firstSectionCount,
|
|
helper
|
|
) {
|
|
// Calculate the number of iterations to display,
|
|
// with a maximum of MAX_INFINITE_ANIMATIONS_ITERATIONS
|
|
let uncappedInfinityIterationCount =
|
|
(helper.totalDuration - firstSectionCount * state.duration) /
|
|
state.duration;
|
|
// If there is a small floating point error resulting in, e.g. 1.0000001
|
|
// ceil will give us 2 so round first.
|
|
uncappedInfinityIterationCount = parseFloat(
|
|
uncappedInfinityIterationCount.toPrecision(6)
|
|
);
|
|
const infinityIterationCount = Math.min(
|
|
MAX_INFINITE_ANIMATIONS_ITERATIONS,
|
|
Math.ceil(uncappedInfinityIterationCount)
|
|
);
|
|
|
|
// Append first full iteration path.
|
|
const firstStartTime =
|
|
mainIterationStartTime + firstSectionCount * state.duration;
|
|
const firstEndTime = firstStartTime + state.duration;
|
|
const firstSegments = helper.createPathSegments(
|
|
firstStartTime,
|
|
firstEndTime
|
|
);
|
|
pathList.push(
|
|
dom.path({
|
|
className: "animation-iteration-path",
|
|
d: helper.toPathString(firstSegments),
|
|
})
|
|
);
|
|
|
|
// Append other iterations. We can copy first segments.
|
|
const isAlternate = state.direction.match(/alternate/);
|
|
for (let i = 1; i < infinityIterationCount; i++) {
|
|
const startTime = firstStartTime + i * state.duration;
|
|
let segments;
|
|
if (isAlternate && i % 2) {
|
|
// Copy as reverse.
|
|
segments = firstSegments.map(segment => {
|
|
return { x: firstEndTime - segment.x + startTime, y: segment.y };
|
|
});
|
|
} else {
|
|
// Copy as is.
|
|
segments = firstSegments.map(segment => {
|
|
return { x: segment.x - firstStartTime + startTime, y: segment.y };
|
|
});
|
|
}
|
|
pathList.push(
|
|
dom.path({
|
|
className: "animation-iteration-path infinity",
|
|
d: helper.toPathString(segments),
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render infinity duration.
|
|
*
|
|
* @param {Array} pathList
|
|
* Add rendered <path> elements to this array.
|
|
* @param {Object} state
|
|
* State of animation.
|
|
* @param {Number} mainIterationStartTime
|
|
* Starting time of main iteration.
|
|
* @param {SummaryGraphHelper} helper
|
|
* Instance of SummaryGraphHelper.
|
|
*/
|
|
renderInfinityDuration(pathList, state, mainIterationStartTime, helper) {
|
|
const startSegment = helper.getSegment(mainIterationStartTime);
|
|
const endSegment = { x: helper.totalDuration, y: startSegment.y };
|
|
const segments = [startSegment, endSegment];
|
|
pathList.push(
|
|
dom.path({
|
|
className: "animation-iteration-path infinity-duration",
|
|
d: helper.toPathString(segments),
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render 'endDelay' part in animation and add a <path> element to given pathList.
|
|
*
|
|
* @param {Array} pathList
|
|
* Add rendered <path> element to this array.
|
|
* @param {Object} state
|
|
* State of animation.
|
|
* @param {Number} mainIterationStartTime
|
|
* Starting time of main iteration.
|
|
* @param {Number} iterationCount
|
|
* Iteration count of whole animation.
|
|
* @param {SummaryGraphHelper} helper
|
|
* Instance of SummaryGraphHelper.
|
|
*/
|
|
renderEndDelay(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
iterationCount,
|
|
helper
|
|
) {
|
|
const startTime = mainIterationStartTime + iterationCount * state.duration;
|
|
const startSegment = helper.getSegment(startTime);
|
|
const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
|
|
pathList.push(
|
|
dom.path({
|
|
className: "animation-enddelay-path",
|
|
d: helper.toPathString([startSegment, endSegment]),
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render 'fill' for forwards part in animation and
|
|
* add a <path> element to given pathList.
|
|
*
|
|
* @param {Array} pathList
|
|
* Add rendered <path> element to this array.
|
|
* @param {Object} state
|
|
* State of animation.
|
|
* @param {Number} mainIterationStartTime
|
|
* Starting time of main iteration.
|
|
* @param {Number} iterationCount
|
|
* Iteration count of whole animation.
|
|
* @param {SummaryGraphHelper} helper
|
|
* Instance of SummaryGraphHelper.
|
|
*/
|
|
renderForwardsFill(
|
|
pathList,
|
|
state,
|
|
mainIterationStartTime,
|
|
iterationCount,
|
|
helper
|
|
) {
|
|
const startTime =
|
|
mainIterationStartTime +
|
|
iterationCount * state.duration +
|
|
(state.endDelay > 0 ? state.endDelay : 0);
|
|
const startSegment = helper.getSegment(startTime);
|
|
const endSegment = { x: helper.totalDuration, y: startSegment.y };
|
|
pathList.push(
|
|
dom.path({
|
|
className: "animation-fill-forwards-path",
|
|
d: helper.toPathString([startSegment, endSegment]),
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = TimingPath;
|