blob: 42d2c11d12e3c7024699905e482c257c38b0eecd [file] [log] [blame]
/*
* Copyright (C) 2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.AnimationDetailsSidebarPanel = class AnimationDetailsSidebarPanel extends WI.DetailsSidebarPanel
{
constructor()
{
super("animation", WI.UIString("Animation"));
this._animation = null;
this._codeMirrorSectionMap = new Map;
}
// Public
inspect(objects)
{
if (!(objects instanceof Array))
objects = [objects];
this.animation = objects.find((object) => object instanceof WI.Animation);
return !!this.animation;
}
get animation()
{
return this._animation;
}
set animation(animation)
{
if (animation === this._animation)
return;
if (this._animation) {
this._animation.removeEventListener(WI.Animation.Event.TargetChanged, this._handleAnimationTargetChanged, this);
this._animation.removeEventListener(WI.Animation.Event.EffectChanged, this._handleAnimationEffectChanged, this);
this._animation.removeEventListener(WI.Animation.Event.NameChanged, this._handleAnimationNameChanged, this);
}
this._animation = animation || null;
if (this._animation) {
this._animation.addEventListener(WI.Animation.Event.NameChanged, this._handleAnimationNameChanged, this);
this._animation.addEventListener(WI.Animation.Event.EffectChanged, this._handleAnimationEffectChanged, this);
this._animation.addEventListener(WI.Animation.Event.TargetChanged, this._handleAnimationTargetChanged, this);
}
this.needsLayout();
}
// Protected
initialLayout()
{
super.initialLayout();
this._idRow = new WI.DetailsSectionSimpleRow(WI.UIString("Identifier"));
this._typeRow = new WI.DetailsSectionSimpleRow(WI.UIString("Type"));
this._cssAnimationNameRow = new WI.DetailsSectionSimpleRow(WI.UIString("Name"));
this._cssTransitionPropertyRow = new WI.DetailsSectionSimpleRow(WI.UIString("Property"));
this._targetRow = new WI.DetailsSectionSimpleRow(WI.UIString("Target", "Web Animation Target Label", "Label for the current DOM node target of a web animation"));
const identitySectionTitle = WI.UIString("Identity", "Web Animation Identity Title", "Section title for information about a web animation");
let identitySection = new WI.DetailsSection("animation-identity", identitySectionTitle, [new WI.DetailsSectionGroup([this._idRow, this._typeRow, this._cssAnimationNameRow, this._cssTransitionPropertyRow, this._targetRow])]);
this.contentView.element.appendChild(identitySection.element);
this._iterationCountRow = new WI.DetailsSectionSimpleRow(WI.UIString("Iterations", "Web Animation Iteration Count Label", "Label for the number of iterations of a web animation"));
this._iterationStartRow = new WI.DetailsSectionSimpleRow(WI.UIString("Start", "Web Animation Iteration Start Label", "Label for the number describing which iteration a web animation should start at"));
this._iterationDurationRow = new WI.DetailsSectionSimpleRow(WI.UIString("Duration", "Web Animation Iteration Duration Label", "Label for the time duration of each iteration of a web animation"));
let iterationsGroup = new WI.DetailsSectionGroup([this._iterationCountRow, this._iterationStartRow, this._iterationDurationRow]);
this._startDelayRow = new WI.DetailsSectionSimpleRow(WI.UIString("Start Delay", "Web Animation Start Delay Label", "Label for the start delay time of a web animation "));
this._endDelayRow = new WI.DetailsSectionSimpleRow(WI.UIString("End Delay", "Web Animation End Delay Label", "Label for the end delay time of a web animation "));
this._timingFunctionRow = new WI.DetailsSectionSimpleRow(WI.UIString("Easing", "Web Animation Easing Label", "Label for the cubic-bezier timing function of a web animation"));
let timingGroup = new WI.DetailsSectionGroup([this._startDelayRow, this._endDelayRow, this._timingFunctionRow]);
this._playbackDirectionRow = new WI.DetailsSectionSimpleRow(WI.UIString("Direction", "Web Animation Playback Direction Label", "Label for the playback direction of a web animation"));
this._fillModeRow = new WI.DetailsSectionSimpleRow(WI.UIString("Fill", "Web Animation Fill Mode Label", "Label for the fill mode of a web animation"));
let fillDirectionGroup = new WI.DetailsSectionGroup([this._playbackDirectionRow, this._fillModeRow]);
const effectSectionTitle = WI.UIString("Effect", "Web Animation Effect Title", "Section title for information about the effect of a web animation");
let effectSection = new WI.DetailsSection("animation-effect", effectSectionTitle, [iterationsGroup, timingGroup, fillDirectionGroup]);
this.contentView.element.appendChild(effectSection.element);
this._keyframesGroup = new WI.DetailsSectionGroup;
const keyframesSectionTitle = WI.UIString("Keyframes", "Web Animation Keyframes Title", "Section title for information about the keyframes of a web animation");
let keyframesSection = new WI.DetailsSection("animation-keyframes", keyframesSectionTitle, [this._keyframesGroup]);
this.contentView.element.appendChild(keyframesSection.element);
const selectable = false;
let backtraceTreeOutline = new WI.TreeOutline(selectable);
backtraceTreeOutline.disclosureButtons = false;
this._backtraceTreeController = new WI.CallFrameTreeController(backtraceTreeOutline);
let backtraceRow = new WI.DetailsSectionRow;
backtraceRow.element.appendChild(backtraceTreeOutline.element);
const backtraceSectionTitle = WI.UIString("Backtrace", "Web Animation Backtrace Title", "Section title for the JavaScript backtrace of the creation of a web animation");
this._backtraceSection = new WI.DetailsSection("animation-backtrace", backtraceSectionTitle, [new WI.DetailsSectionGroup([backtraceRow])]);
this._backtraceSection.element.hidden = true;
this.contentView.element.appendChild(this._backtraceSection.element);
}
layout()
{
super.layout();
if (!this._animation)
return;
this._refreshIdentitySection();
this._refreshEffectSection();
this._refreshBacktraceSection();
for (let codeMirror of this._codeMirrorSectionMap.values())
codeMirror.refresh();
}
attached()
{
super.attached();
for (let codeMirror of this._codeMirrorSectionMap.values())
codeMirror.refresh();
}
// Private
_refreshIdentitySection()
{
let animationType = this._animation.animationType;
let displayName = this._animation.displayName;
let cssAnimationName = this._animation.cssAnimationName;
let cssTransitionProperty = this._animation.cssTransitionProperty;
switch (animationType) {
case WI.Animation.Type.WebAnimation:
this._idRow.value = this._animation.name;
break;
case WI.Animation.Type.CSSAnimation:
this._idRow.value = cssAnimationName !== displayName ? displayName : null;
break;
case WI.Animation.Type.CSSTransition:
this._idRow.value = cssTransitionProperty !== displayName ? displayName : null;
break;
}
this._typeRow.value = WI.Animation.displayNameForAnimationType(animationType);
this._cssAnimationNameRow.value = cssAnimationName;
this._cssTransitionPropertyRow.value = cssTransitionProperty;
this._targetRow.value = null;
this._animation.requestEffectTarget((styleable) => {
this._targetRow.value = WI.linkifyStyleable(styleable);
});
}
_refreshEffectSection()
{
for (let section of this._codeMirrorSectionMap.keys())
section.removeEventListener(WI.DetailsSection.Event.CollapsedStateChanged, this._handleDetailsSectionCollapsedStateChanged, this);
this._codeMirrorSectionMap.clear();
const precision = 0;
const readOnly = true;
this._iterationCountRow.value = !isNaN(this._animation.iterationCount) ? this._animation.iterationCount.toLocaleString() : null;
this._iterationStartRow.value = !isNaN(this._animation.iterationStart) ? this._animation.iterationStart.toLocaleString() : null;
this._iterationDurationRow.value = !isNaN(this._animation.iterationDuration) ? Number.secondsToString(this._animation.iterationDuration / 1000) : null;
this._startDelayRow.value = this._animation.startDelay ? Number.secondsToString(this._animation.startDelay / 1000) : null;
this._endDelayRow.value = this._animation.endDelay ? Number.secondsToString(this._animation.endDelay / 1000) : null;
this._timingFunctionRow.value = this._animation.timingFunction ? this._animation.timingFunction.toString() : null;
this._playbackDirectionRow.value = this._animation.playbackDirection ? WI.Animation.displayNameForPlaybackDirection(this._animation.playbackDirection) : null;
this._fillModeRow.value = this._animation.fillMode ? WI.Animation.displayNameForFillMode(this._animation.fillMode) : null;
let keyframeSections = [];
for (let keyframe of this._animation.keyframes) {
let rows = [];
let keyframeSection = new WI.DetailsSection("animation-keyframe-offset-" + keyframe.offset, Number.percentageString(keyframe.offset, precision));
keyframeSection.addEventListener(WI.DetailsSection.Event.CollapsedStateChanged, this._handleDetailsSectionCollapsedStateChanged, this);
keyframeSections.push(keyframeSection);
if (keyframe.easing) {
let subtitle = keyframeSection.headerElement.appendChild(document.createElement("span"));
subtitle.className = "subtitle";
subtitle.textContent = ` ${emDash} ${keyframe.easing.toString()}`;
}
if (keyframe.style) {
let codeMirrorElement = document.createElement("div");
let codeMirror = WI.CodeMirrorEditor.create(codeMirrorElement, {
mode: "css",
readOnly: "nocursor",
lineWrapping: true,
});
codeMirror.setValue(keyframe.style);
const range = null;
function optionsForType(type) {
return {
allowedTokens: /\btag\b/,
callback(marker, valueObject, valueString) {
let swatch = new WI.InlineSwatch(type, valueObject, readOnly);
codeMirror.setUniqueBookmark(marker.range.startPosition().toCodeMirror(), swatch.element);
}
};
}
createCodeMirrorColorTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Color));
createCodeMirrorGradientTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Gradient));
createCodeMirrorCubicBezierTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Bezier));
createCodeMirrorSpringTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Spring));
let row = new WI.DetailsSectionRow;
row.element.classList.add("styles");
row.element.appendChild(codeMirrorElement);
rows.push(row);
this._codeMirrorSectionMap.set(keyframeSection, codeMirror);
}
if (!rows.length) {
let emptyRow = new WI.DetailsSectionRow(WI.UIString("No Styles"));
emptyRow.showEmptyMessage();
rows.push(emptyRow);
}
keyframeSection.groups = [new WI.DetailsSectionGroup(rows)];
}
if (!keyframeSections.length) {
let emptyRow = new WI.DetailsSectionRow(WI.UIString("No Keyframes"));
emptyRow.showEmptyMessage();
keyframeSections.push(emptyRow);
}
this._keyframesGroup.rows = keyframeSections;
}
_refreshBacktraceSection()
{
let callFrames = this._animation.backtrace;
this._backtraceTreeController.callFrames = callFrames;
this._backtraceSection.element.hidden = !callFrames.length;
}
_handleAnimationNameChanged(event)
{
this._refreshIdentitySection();
}
_handleAnimationEffectChanged(event)
{
this._refreshEffectSection();
}
_handleAnimationTargetChanged(event)
{
this._refreshIdentitySection();
}
_handleDetailsSectionCollapsedStateChanged(event)
{
let codeMirror = this._codeMirrorSectionMap.get(event.target);
codeMirror.refresh();
}
};