2
* Copyright (C) 2020 Apple Inc. All rights reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions
7
* 1. Redistributions of source code must retain the above copyright
8
* notice, this list of conditions and the following disclaimer.
9
* 2. Redistributions in binary form must reproduce the above copyright
10
* notice, this list of conditions and the following disclaimer in the
11
* documentation and/or other materials provided with the distribution.
13
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23
* THE POSSIBILITY OF SUCH DAMAGE.
26
WI.AnimationContentView = class AnimationContentView extends WI.ContentView
28
constructor(representedObject)
30
console.assert(representedObject instanceof WI.Animation);
32
super(representedObject);
34
this._animationTargetDOMNode = null;
35
this._cachedWidth = NaN;
37
this.element.classList.add("animation");
42
static get previewHeight()
49
handleRefreshButtonClicked()
51
this._refreshSubtitle();
58
super.initialLayout();
60
let headerElement = this.element.appendChild(document.createElement("header"));
62
let titlesContainer = headerElement.appendChild(document.createElement("div"));
63
titlesContainer.className = "titles";
65
let titleElement = titlesContainer.appendChild(document.createElement("span"));
66
titleElement.className = "title";
67
titleElement.textContent = this.representedObject.displayName;
69
this._subtitleElement = titlesContainer.appendChild(document.createElement("span"));
70
this._subtitleElement.className = "subtitle";
72
let navigationBar = new WI.NavigationBar;
74
let animationTargetButtonNavigationItem = new WI.ButtonNavigationItem("animation-target", WI.UIString("Animation Target"), "Images/Markup.svg", 16, 16);
75
animationTargetButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
76
WI.addMouseDownContextMenuHandlers(animationTargetButtonNavigationItem.element, this._populateAnimationTargetButtonContextMenu.bind(this));
77
navigationBar.addNavigationItem(animationTargetButtonNavigationItem);
79
headerElement.append(navigationBar.element);
80
this.addSubview(navigationBar);
82
this._previewContainer = this.element.appendChild(document.createElement("div"));
83
this._previewContainer.className = "preview";
90
this._refreshSubtitle();
91
this._refreshPreview();
96
super.sizeDidChange();
98
this._cachedWidth = this.element.realOffsetWidth;
105
this.representedObject.addEventListener(WI.Animation.Event.EffectChanged, this._handleEffectChanged, this);
106
this.representedObject.addEventListener(WI.Animation.Event.TargetChanged, this._handleTargetChanged, this);
111
this.representedObject.removeEventListener(WI.Animation.Event.TargetChanged, this._handleTargetChanged, this);
112
this.representedObject.removeEventListener(WI.Animation.Event.EffectChanged, this._handleEffectChanged, this);
121
this.representedObject.requestEffectTarget((domNode) => {
122
this._animationTargetDOMNode = domNode;
124
this._subtitleElement.removeChildren();
126
this._subtitleElement.appendChild(WI.linkifyNodeReference(domNode));
132
this._previewContainer.removeChildren();
134
let keyframes = this.representedObject.keyframes;
135
if (!keyframes.length) {
136
let span = this._previewContainer.appendChild(document.createElement("span"));
137
span.textContent = WI.UIString("This animation has no keyframes.");
141
let startDelay = this.representedObject.startDelay || 0;
142
let iterationDuration = this.representedObject.iterationDuration || 0;
143
let endDelay = this.representedObject.endDelay || 0;
144
let totalDuration = startDelay + iterationDuration + endDelay;
145
if (totalDuration === 0) {
146
let span = this._previewContainer.appendChild(document.createElement("span"));
147
span.textContent = WI.UIString("This animation has no duration.");
151
const previewHeight = WI.AnimationContentView.previewHeight;
153
const markerHeadRadius = 4;
154
const markerHeadPadding = 2;
156
// Squeeze the entire preview so that markers aren't cut off.
157
const squeezeXStart = (iterationDuration && startDelay) ? 0 : markerHeadRadius + markerHeadPadding;
158
const squeezeXEnd = (iterationDuration && endDelay) ? 0 : markerHeadRadius + markerHeadPadding;
159
const squeezeYStart = markerHeadRadius + (markerHeadPadding * 2);
161
// Move the easing line down to cut off the bottom border.
162
const adjustEasingY = 0.5;
164
let secondsPerPixel = this._cachedWidth / totalDuration;
166
startDelay *= secondsPerPixel;
167
iterationDuration = (iterationDuration * secondsPerPixel) - squeezeXStart - squeezeXEnd;
168
endDelay *= secondsPerPixel;
170
let svg = this._previewContainer.appendChild(createSVGElement("svg"));
171
svg.setAttribute("viewBox", `0 0 ${this._cachedWidth} ${previewHeight}`);
173
function addTitle(parent, title) {
174
let titleElement = parent.appendChild(createSVGElement("title"));
175
titleElement.textContent = title;
179
let startDelayContainer = svg.appendChild(createSVGElement("g"));
180
startDelayContainer.classList.add("delay", "start");
182
let startDelayLine = startDelayContainer.appendChild(createSVGElement("line"));
183
startDelayLine.setAttribute("y1", (previewHeight + squeezeYStart) / 2);
184
startDelayLine.setAttribute("x2", startDelay);
185
startDelayLine.setAttribute("y2", (previewHeight + squeezeYStart) / 2);
187
let startDelayElement = startDelayContainer.appendChild(createSVGElement("rect"));
188
startDelayElement.setAttribute("width", startDelay);
189
startDelayElement.setAttribute("height", previewHeight);
191
const startDelayTitleFormat = WI.UIString("Start Delay %s", "Web Animation Start Delay Tooltip", "Tooltip for section of graph representing delay before a web animation begins applying styles");
192
addTitle(startDelayElement, startDelayTitleFormat.format(Number.secondsToString(this.representedObject.startDelay / 1000)));
196
let endDelayContainer = svg.appendChild(createSVGElement("g"));
197
endDelayContainer.setAttribute("transform", `translate(${startDelay + iterationDuration + squeezeXStart}, 0)`);
198
endDelayContainer.classList.add("delay", "end");
200
let endDelayLine = endDelayContainer.appendChild(createSVGElement("line"));
201
endDelayLine.setAttribute("y1", (previewHeight + squeezeYStart) / 2);
202
endDelayLine.setAttribute("x2", endDelay);
203
endDelayLine.setAttribute("y2", (previewHeight + squeezeYStart) / 2);
205
let endDelayElement = endDelayContainer.appendChild(createSVGElement("rect"));
206
endDelayElement.setAttribute("width", startDelay + iterationDuration + endDelay);
207
endDelayElement.setAttribute("height", previewHeight);
209
const endDelayTitleFormat = WI.UIString("End Delay %s", "Web Animation End Delay Tooltip", "Tooltip for section of graph representing delay after a web animation finishes applying styles");
210
addTitle(endDelayElement, endDelayTitleFormat.format(Number.secondsToString(this.representedObject.endDelay / 1000)));
213
if (iterationDuration) {
214
let timingFunction = this.representedObject.timingFunction;
216
let activeDurationContainer = svg.appendChild(createSVGElement("g"));
217
activeDurationContainer.classList.add("active");
218
activeDurationContainer.setAttribute("transform", `translate(${startDelay + squeezeXStart}, ${squeezeYStart})`);
221
const endY = previewHeight - squeezeYStart;
222
const height = endY - startY;
224
for (let [keyframeA, keyframeB] of keyframes.adjacencies()) {
225
let startX = iterationDuration * keyframeA.offset;
226
let endX = iterationDuration * keyframeB.offset;
227
let width = endX - startX;
229
let easing = keyframeA.easing || timingFunction;
231
let easingContainer = activeDurationContainer.appendChild(createSVGElement("g"));
232
easingContainer.classList.add("easing");
233
easingContainer.setAttribute("transform", `translate(${startX}, ${startY})`);
235
let x1 = easing.inPoint.x * width;
236
let y1 = ((1 - easing.inPoint.y) * height) + adjustEasingY;
237
let x2 = easing.outPoint.x * width;
238
let y2 = ((1 - easing.outPoint.y) * height) + adjustEasingY;
240
let easingPath = easingContainer.appendChild(createSVGElement("path"));
241
easingPath.setAttribute("d", `M 0 ${height + adjustEasingY} C ${x1} ${y1} ${x2} ${y2} ${width} ${adjustEasingY} V ${height + adjustEasingY} Z`);
243
let titleRect = easingContainer.appendChild(createSVGElement("rect"));
244
titleRect.setAttribute("width", width);
245
titleRect.setAttribute("height", height);
247
addTitle(titleRect, easing.toString());
250
for (let keyframe of keyframes) {
251
let x = iterationDuration * keyframe.offset;
253
let keyframeContainer = activeDurationContainer.appendChild(createSVGElement("g"));
254
keyframeContainer.classList.add("keyframe");
255
keyframeContainer.setAttribute("transform", `translate(${x}, ${startY})`);
257
let keyframeMarkerHead = keyframeContainer.appendChild(createSVGElement("circle"));
258
keyframeMarkerHead.setAttribute("r", markerHeadRadius);
260
let keyframeMarkerLine = keyframeContainer.appendChild(createSVGElement("line"));
261
keyframeMarkerLine.setAttribute("y1", height);
263
let titleRect = keyframeContainer.appendChild(createSVGElement("rect"));
264
titleRect.setAttribute("x", -1 * (markerHeadRadius + markerHeadPadding));
265
titleRect.setAttribute("y", -1 * squeezeYStart);
266
titleRect.setAttribute("width", (markerHeadRadius + markerHeadPadding) * 2);
267
titleRect.setAttribute("height", height + squeezeYStart);
269
addTitle(titleRect, keyframe.style);
274
_handleEffectChanged(event)
276
this._refreshPreview();
279
_handleTargetChanged(event)
281
this._refreshSubtitle();
284
_populateAnimationTargetButtonContextMenu(contextMenu)
286
contextMenu.appendItem(WI.UIString("Log Animation"), () => {
287
WI.RemoteObject.resolveAnimation(this.representedObject, WI.RuntimeManager.ConsoleObjectGroup, (remoteObject) => {
291
const text = WI.UIString("Selected Animation", "Appears as a label when a given web animation is logged to the Console");
292
const addSpecialUserLogClass = true;
293
WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, addSpecialUserLogClass);
297
contextMenu.appendSeparator();
299
if (this._animationTargetDOMNode)
300
WI.appendContextMenuItemsForDOMNode(contextMenu, this._animationTargetDOMNode);