mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-08 00:38:39 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
159
devtools/client/webaudioeditor/views/automation.js
Normal file
159
devtools/client/webaudioeditor/views/automation.js
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* Functions handling the audio node inspector UI.
|
||||
*/
|
||||
|
||||
var AutomationView = {
|
||||
|
||||
/**
|
||||
* Initialization function called when the tool starts up.
|
||||
*/
|
||||
initialize: function () {
|
||||
this._buttons = $("#automation-param-toolbar-buttons");
|
||||
this.graph = new LineGraphWidget($("#automation-graph"), { avg: false });
|
||||
this.graph.selectionEnabled = false;
|
||||
|
||||
this._onButtonClick = this._onButtonClick.bind(this);
|
||||
this._onNodeSet = this._onNodeSet.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
|
||||
this._buttons.addEventListener("click", this._onButtonClick);
|
||||
window.on(EVENTS.UI_INSPECTOR_RESIZE, this._onResize);
|
||||
window.on(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function called when the tool cleans up.
|
||||
*/
|
||||
destroy: function () {
|
||||
this._buttons.removeEventListener("click", this._onButtonClick);
|
||||
window.off(EVENTS.UI_INSPECTOR_RESIZE, this._onResize);
|
||||
window.off(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties out the props view.
|
||||
*/
|
||||
resetUI: function () {
|
||||
this._currentNode = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* On a new node selection, create the Automation panel for
|
||||
* that specific node.
|
||||
*/
|
||||
build: Task.async(function* () {
|
||||
let node = this._currentNode;
|
||||
|
||||
let props = yield node.getParams();
|
||||
let params = props.filter(({ flags }) => flags && flags.param);
|
||||
|
||||
this._createParamButtons(params);
|
||||
|
||||
this._selectedParamName = params[0] ? params[0].param : null;
|
||||
this.render();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Renders the graph for specified `paramName`. Called when
|
||||
* the parameter view is changed, or when new param data events
|
||||
* are fired for the currently specified param.
|
||||
*/
|
||||
render: Task.async(function* () {
|
||||
let node = this._currentNode;
|
||||
let paramName = this._selectedParamName;
|
||||
// Escape if either node or parameter name does not exist.
|
||||
if (!node || !paramName) {
|
||||
this._setState("no-params");
|
||||
window.emit(EVENTS.UI_AUTOMATION_TAB_RENDERED, null);
|
||||
return;
|
||||
}
|
||||
|
||||
let { values, events } = yield node.getAutomationData(paramName);
|
||||
this._setState(events.length ? "show" : "no-events");
|
||||
yield this.graph.setDataWhenReady(values);
|
||||
window.emit(EVENTS.UI_AUTOMATION_TAB_RENDERED, node.id);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create the buttons for each AudioParam, that when clicked,
|
||||
* render the graph for that AudioParam.
|
||||
*/
|
||||
_createParamButtons: function (params) {
|
||||
this._buttons.innerHTML = "";
|
||||
params.forEach((param, i) => {
|
||||
let button = document.createElement("toolbarbutton");
|
||||
button.setAttribute("class", "devtools-toolbarbutton automation-param-button");
|
||||
button.setAttribute("data-param", param.param);
|
||||
// Set label to the parameter name, should not be L10N'd
|
||||
button.setAttribute("label", param.param);
|
||||
|
||||
// If first button, set to 'selected' for styling
|
||||
if (i === 0) {
|
||||
button.setAttribute("selected", true);
|
||||
}
|
||||
|
||||
this._buttons.appendChild(button);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Internally sets the current audio node and rebuilds appropriate
|
||||
* views.
|
||||
*/
|
||||
_setAudioNode: function (node) {
|
||||
this._currentNode = node;
|
||||
if (this._currentNode) {
|
||||
this.build();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the subviews to display messages whether or not
|
||||
* the audio node has no AudioParams, no automation events, or
|
||||
* shows the graph.
|
||||
*/
|
||||
_setState: function (state) {
|
||||
let contentView = $("#automation-content");
|
||||
let emptyView = $("#automation-empty");
|
||||
|
||||
let graphView = $("#automation-graph-container");
|
||||
let noEventsView = $("#automation-no-events");
|
||||
|
||||
contentView.hidden = state === "no-params";
|
||||
emptyView.hidden = state !== "no-params";
|
||||
|
||||
graphView.hidden = state !== "show";
|
||||
noEventsView.hidden = state !== "no-events";
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
_onButtonClick: function (e) {
|
||||
Array.forEach($$(".automation-param-button"), $btn => $btn.removeAttribute("selected"));
|
||||
let paramName = e.target.getAttribute("data-param");
|
||||
e.target.setAttribute("selected", true);
|
||||
this._selectedParamName = paramName;
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the inspector is resized.
|
||||
*/
|
||||
_onResize: function () {
|
||||
this.graph.refresh();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the inspector view determines a node is selected.
|
||||
*/
|
||||
_onNodeSet: function (_, id) {
|
||||
this._setAudioNode(id != null ? gAudioNodes.get(id) : null);
|
||||
}
|
||||
};
|
||||
314
devtools/client/webaudioeditor/views/context.js
Normal file
314
devtools/client/webaudioeditor/views/context.js
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
/* 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";
|
||||
|
||||
/* import-globals-from ../includes.js */
|
||||
|
||||
const { debounce } = require("sdk/lang/functional");
|
||||
const flags = require("devtools/shared/flags");
|
||||
|
||||
// Globals for d3 stuff
|
||||
// Default properties of the graph on rerender
|
||||
const GRAPH_DEFAULTS = {
|
||||
translate: [20, 20],
|
||||
scale: 1
|
||||
};
|
||||
|
||||
// Sizes of SVG arrows in graph
|
||||
const ARROW_HEIGHT = 5;
|
||||
const ARROW_WIDTH = 8;
|
||||
|
||||
// Styles for markers as they cannot be done with CSS.
|
||||
const MARKER_STYLING = {
|
||||
light: "#AAA",
|
||||
dark: "#CED3D9"
|
||||
};
|
||||
Object.defineProperty(this, "MARKER_STYLING", {
|
||||
value: MARKER_STYLING,
|
||||
enumerable: true,
|
||||
writable: false
|
||||
});
|
||||
|
||||
const GRAPH_DEBOUNCE_TIMER = 100;
|
||||
|
||||
// `gAudioNodes` events that should require the graph
|
||||
// to redraw
|
||||
const GRAPH_REDRAW_EVENTS = ["add", "connect", "disconnect", "remove"];
|
||||
|
||||
/**
|
||||
* Functions handling the graph UI.
|
||||
*/
|
||||
var ContextView = {
|
||||
/**
|
||||
* Initialization function, called when the tool is started.
|
||||
*/
|
||||
initialize: function () {
|
||||
this._onGraphClick = this._onGraphClick.bind(this);
|
||||
this._onThemeChange = this._onThemeChange.bind(this);
|
||||
this._onStartContext = this._onStartContext.bind(this);
|
||||
this._onEvent = this._onEvent.bind(this);
|
||||
|
||||
this.draw = debounce(this.draw.bind(this), GRAPH_DEBOUNCE_TIMER);
|
||||
$("#graph-target").addEventListener("click", this._onGraphClick, false);
|
||||
|
||||
window.on(EVENTS.THEME_CHANGE, this._onThemeChange);
|
||||
window.on(EVENTS.START_CONTEXT, this._onStartContext);
|
||||
gAudioNodes.on("*", this._onEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the tool is closed.
|
||||
*/
|
||||
destroy: function () {
|
||||
// If the graph was rendered at all, then the handler
|
||||
// for zooming in will be set. We must remove it to prevent leaks.
|
||||
if (this._zoomBinding) {
|
||||
this._zoomBinding.on("zoom", null);
|
||||
}
|
||||
$("#graph-target").removeEventListener("click", this._onGraphClick, false);
|
||||
|
||||
window.off(EVENTS.THEME_CHANGE, this._onThemeChange);
|
||||
window.off(EVENTS.START_CONTEXT, this._onStartContext);
|
||||
gAudioNodes.off("*", this._onEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a page is reloaded and waiting for a "start-context" event
|
||||
* and clears out old content
|
||||
*/
|
||||
resetUI: function () {
|
||||
this.clearGraph();
|
||||
this.resetGraphTransform();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears out the rendered graph, called when resetting the SVG elements to draw again,
|
||||
* or when resetting the entire UI tool
|
||||
*/
|
||||
clearGraph: function () {
|
||||
$("#graph-target").innerHTML = "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Moves the graph back to its original scale and translation.
|
||||
*/
|
||||
resetGraphTransform: function () {
|
||||
// Only reset if the graph was ever drawn.
|
||||
if (this._zoomBinding) {
|
||||
let { translate, scale } = GRAPH_DEFAULTS;
|
||||
// Must set the `zoomBinding` so the next `zoom` event is in sync with
|
||||
// where the graph is visually (set by the `transform` attribute).
|
||||
this._zoomBinding.scale(scale);
|
||||
this._zoomBinding.translate(translate);
|
||||
d3.select("#graph-target")
|
||||
.attr("transform", "translate(" + translate + ") scale(" + scale + ")");
|
||||
}
|
||||
},
|
||||
|
||||
getCurrentScale: function () {
|
||||
return this._zoomBinding ? this._zoomBinding.scale() : null;
|
||||
},
|
||||
|
||||
getCurrentTranslation: function () {
|
||||
return this._zoomBinding ? this._zoomBinding.translate() : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes the corresponding graph node appear "focused", removing
|
||||
* focused styles from all other nodes. If no `actorID` specified,
|
||||
* make all nodes appear unselected.
|
||||
*/
|
||||
focusNode: function (actorID) {
|
||||
// Remove class "selected" from all nodes
|
||||
Array.forEach($$(".nodes > g"), $node => $node.classList.remove("selected"));
|
||||
// Add to "selected"
|
||||
if (actorID) {
|
||||
this._getNodeByID(actorID).classList.add("selected");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an actorID and returns the corresponding DOM SVG element in the graph
|
||||
*/
|
||||
_getNodeByID: function (actorID) {
|
||||
return $(".nodes > g[data-id='" + actorID + "']");
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the appropriate class on an SVG node when its bypass
|
||||
* status is toggled.
|
||||
*/
|
||||
_bypassNode: function (node, enabled) {
|
||||
let el = this._getNodeByID(node.id);
|
||||
el.classList[enabled ? "add" : "remove"]("bypassed");
|
||||
},
|
||||
|
||||
/**
|
||||
* This method renders the nodes currently available in `gAudioNodes` and is
|
||||
* throttled to be called at most every `GRAPH_DEBOUNCE_TIMER` milliseconds.
|
||||
* It's called whenever the audio context routing changes, after being debounced.
|
||||
*/
|
||||
draw: function () {
|
||||
// Clear out previous SVG information
|
||||
this.clearGraph();
|
||||
|
||||
let graph = new dagreD3.Digraph();
|
||||
let renderer = new dagreD3.Renderer();
|
||||
gAudioNodes.populateGraph(graph);
|
||||
|
||||
// Post-render manipulation of the nodes
|
||||
let oldDrawNodes = renderer.drawNodes();
|
||||
renderer.drawNodes(function (graph, root) {
|
||||
let svgNodes = oldDrawNodes(graph, root);
|
||||
svgNodes.each(function (n) {
|
||||
let node = graph.node(n);
|
||||
let classString = "audionode type-" + node.type + (node.bypassed ? " bypassed" : "");
|
||||
this.setAttribute("class", classString);
|
||||
this.setAttribute("data-id", node.id);
|
||||
this.setAttribute("data-type", node.type);
|
||||
});
|
||||
return svgNodes;
|
||||
});
|
||||
|
||||
// Post-render manipulation of edges
|
||||
let oldDrawEdgePaths = renderer.drawEdgePaths();
|
||||
let defaultClasses = "edgePath enter";
|
||||
|
||||
renderer.drawEdgePaths(function (graph, root) {
|
||||
let svgEdges = oldDrawEdgePaths(graph, root);
|
||||
svgEdges.each(function (e) {
|
||||
let edge = graph.edge(e);
|
||||
|
||||
// We have to manually specify the default classes on the edges
|
||||
// as to not overwrite them
|
||||
let edgeClass = defaultClasses + (edge.param ? (" param-connection " + edge.param) : "");
|
||||
|
||||
this.setAttribute("data-source", edge.source);
|
||||
this.setAttribute("data-target", edge.target);
|
||||
this.setAttribute("data-param", edge.param ? edge.param : null);
|
||||
this.setAttribute("class", edgeClass);
|
||||
});
|
||||
|
||||
return svgEdges;
|
||||
});
|
||||
|
||||
// Override Dagre-d3's post render function by passing in our own.
|
||||
// This way we can leave styles out of it.
|
||||
renderer.postRender((graph, root) => {
|
||||
// We have to manually set the marker styling since we cannot
|
||||
// do this currently with CSS, although it is in spec for SVG2
|
||||
// https://svgwg.org/svg2-draft/painting.html#VertexMarkerProperties
|
||||
// For now, manually set it on creation, and the `_onThemeChange`
|
||||
// function will fire when the devtools theme changes to update the
|
||||
// styling manually.
|
||||
let theme = Services.prefs.getCharPref("devtools.theme");
|
||||
let markerColor = MARKER_STYLING[theme];
|
||||
if (graph.isDirected() && root.select("#arrowhead").empty()) {
|
||||
root
|
||||
.append("svg:defs")
|
||||
.append("svg:marker")
|
||||
.attr("id", "arrowhead")
|
||||
.attr("viewBox", "0 0 10 10")
|
||||
.attr("refX", ARROW_WIDTH)
|
||||
.attr("refY", ARROW_HEIGHT)
|
||||
.attr("markerUnits", "strokewidth")
|
||||
.attr("markerWidth", ARROW_WIDTH)
|
||||
.attr("markerHeight", ARROW_HEIGHT)
|
||||
.attr("orient", "auto")
|
||||
.attr("style", "fill: " + markerColor)
|
||||
.append("svg:path")
|
||||
.attr("d", "M 0 0 L 10 5 L 0 10 z");
|
||||
}
|
||||
|
||||
// Reselect the previously selected audio node
|
||||
let currentNode = InspectorView.getCurrentAudioNode();
|
||||
if (currentNode) {
|
||||
this.focusNode(currentNode.id);
|
||||
}
|
||||
|
||||
// Fire an event upon completed rendering, with extra information
|
||||
// if in testing mode only.
|
||||
let info = {};
|
||||
if (flags.testing) {
|
||||
info = gAudioNodes.getInfo();
|
||||
}
|
||||
window.emit(EVENTS.UI_GRAPH_RENDERED, info.nodes, info.edges, info.paramEdges);
|
||||
});
|
||||
|
||||
let layout = dagreD3.layout().rankDir("LR");
|
||||
renderer.layout(layout).run(graph, d3.select("#graph-target"));
|
||||
|
||||
// Handle the sliding and zooming of the graph,
|
||||
// store as `this._zoomBinding` so we can unbind during destruction
|
||||
if (!this._zoomBinding) {
|
||||
this._zoomBinding = d3.behavior.zoom().on("zoom", function () {
|
||||
var ev = d3.event;
|
||||
d3.select("#graph-target")
|
||||
.attr("transform", "translate(" + ev.translate + ") scale(" + ev.scale + ")");
|
||||
});
|
||||
d3.select("svg").call(this._zoomBinding);
|
||||
|
||||
// Set initial translation and scale -- this puts D3's awareness of
|
||||
// the graph in sync with what the user sees originally.
|
||||
this.resetGraphTransform();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called once "start-context" is fired, indicating that there is an audio
|
||||
* context being created to view so render the graph.
|
||||
*/
|
||||
_onStartContext: function () {
|
||||
this.draw();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when `gAudioNodes` fires an event -- most events (listed
|
||||
* in GRAPH_REDRAW_EVENTS) qualify as a redraw event.
|
||||
*/
|
||||
_onEvent: function (eventName, ...args) {
|
||||
// If bypassing, just toggle the class on the SVG node
|
||||
// rather than rerendering everything
|
||||
if (eventName === "bypass") {
|
||||
this._bypassNode.apply(this, args);
|
||||
}
|
||||
if (~GRAPH_REDRAW_EVENTS.indexOf(eventName)) {
|
||||
this.draw();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the devtools theme changes.
|
||||
*/
|
||||
_onThemeChange: function (eventName, theme) {
|
||||
let markerColor = MARKER_STYLING[theme];
|
||||
let marker = $("#arrowhead");
|
||||
if (marker) {
|
||||
marker.setAttribute("style", "fill: " + markerColor);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a click occurs in the graph.
|
||||
*
|
||||
* @param Event e
|
||||
* Click event.
|
||||
*/
|
||||
_onGraphClick: function (e) {
|
||||
let node = findGraphNodeParent(e.target);
|
||||
// If node not found (clicking outside of an audio node in the graph),
|
||||
// then ignore this event
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
let id = node.getAttribute("data-id");
|
||||
|
||||
this.focusNode(id);
|
||||
window.emit(EVENTS.UI_SELECT_NODE, id);
|
||||
}
|
||||
};
|
||||
189
devtools/client/webaudioeditor/views/inspector.js
Normal file
189
devtools/client/webaudioeditor/views/inspector.js
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/* 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";
|
||||
|
||||
/* import-globals-from ../includes.js */
|
||||
|
||||
const MIN_INSPECTOR_WIDTH = 300;
|
||||
|
||||
// Strings for rendering
|
||||
const EXPAND_INSPECTOR_STRING = L10N.getStr("expandInspector");
|
||||
const COLLAPSE_INSPECTOR_STRING = L10N.getStr("collapseInspector");
|
||||
|
||||
/**
|
||||
* Functions handling the audio node inspector UI.
|
||||
*/
|
||||
|
||||
var InspectorView = {
|
||||
_currentNode: null,
|
||||
|
||||
// Set up config for view toggling
|
||||
_collapseString: COLLAPSE_INSPECTOR_STRING,
|
||||
_expandString: EXPAND_INSPECTOR_STRING,
|
||||
_toggleEvent: EVENTS.UI_INSPECTOR_TOGGLED,
|
||||
_animated: true,
|
||||
_delayed: true,
|
||||
|
||||
/**
|
||||
* Initialization function called when the tool starts up.
|
||||
*/
|
||||
initialize: function () {
|
||||
// Set up view controller
|
||||
this.el = $("#web-audio-inspector");
|
||||
this.splitter = $("#inspector-splitter");
|
||||
this.el.setAttribute("width", Services.prefs.getIntPref("devtools.webaudioeditor.inspectorWidth"));
|
||||
this.button = $("#inspector-pane-toggle");
|
||||
mixin(this, ToggleMixin);
|
||||
this.bindToggle();
|
||||
|
||||
// Hide inspector view on startup
|
||||
this.hideImmediately();
|
||||
|
||||
this._onNodeSelect = this._onNodeSelect.bind(this);
|
||||
this._onDestroyNode = this._onDestroyNode.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
this._onCommandClick = this._onCommandClick.bind(this);
|
||||
|
||||
this.splitter.addEventListener("mouseup", this._onResize);
|
||||
for (let $el of $$("#audio-node-toolbar toolbarbutton")) {
|
||||
$el.addEventListener("command", this._onCommandClick);
|
||||
}
|
||||
window.on(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
|
||||
gAudioNodes.on("remove", this._onDestroyNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function called when the tool cleans up.
|
||||
*/
|
||||
destroy: function () {
|
||||
this.unbindToggle();
|
||||
this.splitter.removeEventListener("mouseup", this._onResize);
|
||||
|
||||
$("#audio-node-toolbar toolbarbutton").removeEventListener("command", this._onCommandClick);
|
||||
for (let $el of $$("#audio-node-toolbar toolbarbutton")) {
|
||||
$el.removeEventListener("command", this._onCommandClick);
|
||||
}
|
||||
window.off(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
|
||||
gAudioNodes.off("remove", this._onDestroyNode);
|
||||
|
||||
this.el = null;
|
||||
this.button = null;
|
||||
this.splitter = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a AudioNodeView `node` and sets it as the current
|
||||
* node and scaffolds the inspector view based off of the new node.
|
||||
*/
|
||||
setCurrentAudioNode: Task.async(function* (node) {
|
||||
this._currentNode = node || null;
|
||||
|
||||
// If no node selected, set the inspector back to "no AudioNode selected"
|
||||
// view.
|
||||
if (!node) {
|
||||
$("#web-audio-editor-details-pane-empty").removeAttribute("hidden");
|
||||
$("#web-audio-editor-tabs").setAttribute("hidden", "true");
|
||||
window.emit(EVENTS.UI_INSPECTOR_NODE_SET, null);
|
||||
}
|
||||
// Otherwise load up the tabs view and hide the empty placeholder
|
||||
else {
|
||||
$("#web-audio-editor-details-pane-empty").setAttribute("hidden", "true");
|
||||
$("#web-audio-editor-tabs").removeAttribute("hidden");
|
||||
this._buildToolbar();
|
||||
window.emit(EVENTS.UI_INSPECTOR_NODE_SET, this._currentNode.id);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns the current AudioNodeView.
|
||||
*/
|
||||
getCurrentAudioNode: function () {
|
||||
return this._currentNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties out the props view.
|
||||
*/
|
||||
resetUI: function () {
|
||||
// Set current node to empty to load empty view
|
||||
this.setCurrentAudioNode();
|
||||
|
||||
// Reset AudioNode inspector and hide
|
||||
this.hideImmediately();
|
||||
},
|
||||
|
||||
_buildToolbar: function () {
|
||||
let node = this.getCurrentAudioNode();
|
||||
|
||||
let bypassable = node.bypassable;
|
||||
let bypassed = node.isBypassed();
|
||||
let button = $("#audio-node-toolbar .bypass");
|
||||
|
||||
if (!bypassable) {
|
||||
button.setAttribute("disabled", true);
|
||||
} else {
|
||||
button.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
if (!bypassable || bypassed) {
|
||||
button.removeAttribute("checked");
|
||||
} else {
|
||||
button.setAttribute("checked", true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called on EVENTS.UI_SELECT_NODE, and takes an actorID `id`
|
||||
* and calls `setCurrentAudioNode` to scaffold the inspector view.
|
||||
*/
|
||||
_onNodeSelect: function (_, id) {
|
||||
this.setCurrentAudioNode(gAudioNodes.get(id));
|
||||
|
||||
// Ensure inspector is visible when selecting a new node
|
||||
this.show();
|
||||
},
|
||||
|
||||
_onResize: function () {
|
||||
if (this.el.getAttribute("width") < MIN_INSPECTOR_WIDTH) {
|
||||
this.el.setAttribute("width", MIN_INSPECTOR_WIDTH);
|
||||
}
|
||||
Services.prefs.setIntPref("devtools.webaudioeditor.inspectorWidth", this.el.getAttribute("width"));
|
||||
window.emit(EVENTS.UI_INSPECTOR_RESIZE);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when `DESTROY_NODE` is fired to remove the node from props view if
|
||||
* it's currently selected.
|
||||
*/
|
||||
_onDestroyNode: function (node) {
|
||||
if (this._currentNode && this._currentNode.id === node.id) {
|
||||
this.setCurrentAudioNode(null);
|
||||
}
|
||||
},
|
||||
|
||||
_onCommandClick: function (e) {
|
||||
let node = this.getCurrentAudioNode();
|
||||
let button = e.target;
|
||||
let command = button.getAttribute("data-command");
|
||||
let checked = button.getAttribute("checked");
|
||||
|
||||
if (button.getAttribute("disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "bypass") {
|
||||
if (checked) {
|
||||
button.removeAttribute("checked");
|
||||
node.bypass(true);
|
||||
} else {
|
||||
button.setAttribute("checked", true);
|
||||
node.bypass(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
163
devtools/client/webaudioeditor/views/properties.js
Normal file
163
devtools/client/webaudioeditor/views/properties.js
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/* 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 { VariablesView } = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
|
||||
|
||||
const GENERIC_VARIABLES_VIEW_SETTINGS = {
|
||||
searchEnabled: false,
|
||||
editableValueTooltip: "",
|
||||
editableNameTooltip: "",
|
||||
preventDisableOnChange: true,
|
||||
preventDescriptorModifiers: false,
|
||||
eval: () => {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions handling the audio node inspector UI.
|
||||
*/
|
||||
|
||||
var PropertiesView = {
|
||||
|
||||
/**
|
||||
* Initialization function called when the tool starts up.
|
||||
*/
|
||||
initialize: function () {
|
||||
this._onEval = this._onEval.bind(this);
|
||||
this._onNodeSet = this._onNodeSet.bind(this);
|
||||
|
||||
window.on(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
|
||||
this._propsView = new VariablesView($("#properties-content"), GENERIC_VARIABLES_VIEW_SETTINGS);
|
||||
this._propsView.eval = this._onEval;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function called when the tool cleans up.
|
||||
*/
|
||||
destroy: function () {
|
||||
window.off(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
|
||||
this._propsView = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties out the props view.
|
||||
*/
|
||||
resetUI: function () {
|
||||
this._propsView.empty();
|
||||
this._currentNode = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Internally sets the current audio node and rebuilds appropriate
|
||||
* views.
|
||||
*/
|
||||
_setAudioNode: function (node) {
|
||||
this._currentNode = node;
|
||||
if (this._currentNode) {
|
||||
this._buildPropertiesView();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reconstructs the `Properties` tab in the inspector
|
||||
* with the `this._currentNode` as it's source.
|
||||
*/
|
||||
_buildPropertiesView: Task.async(function* () {
|
||||
let propsView = this._propsView;
|
||||
let node = this._currentNode;
|
||||
propsView.empty();
|
||||
|
||||
let audioParamsScope = propsView.addScope("AudioParams");
|
||||
let props = yield node.getParams();
|
||||
|
||||
// Disable AudioParams VariableView expansion
|
||||
// when there are no props i.e. AudioDestinationNode
|
||||
this._togglePropertiesView(!!props.length);
|
||||
|
||||
props.forEach(({ param, value, flags }) => {
|
||||
let descriptor = {
|
||||
value: value,
|
||||
writable: !flags || !flags.readonly,
|
||||
};
|
||||
let item = audioParamsScope.addItem(param, descriptor);
|
||||
|
||||
// No items should currently display a dropdown
|
||||
item.twisty = false;
|
||||
});
|
||||
|
||||
audioParamsScope.expanded = true;
|
||||
|
||||
window.emit(EVENTS.UI_PROPERTIES_TAB_RENDERED, node.id);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Toggles the display of the "empty" properties view when
|
||||
* node has no properties to display.
|
||||
*/
|
||||
_togglePropertiesView: function (show) {
|
||||
let propsView = $("#properties-content");
|
||||
let emptyView = $("#properties-empty");
|
||||
(show ? propsView : emptyView).removeAttribute("hidden");
|
||||
(show ? emptyView : propsView).setAttribute("hidden", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the scope for AudioParams in the
|
||||
* VariablesView.
|
||||
*
|
||||
* @return Scope
|
||||
*/
|
||||
_getAudioPropertiesScope: function () {
|
||||
return this._propsView.getScopeAtIndex(0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when the inspector view determines a node is selected.
|
||||
*/
|
||||
_onNodeSet: function (_, id) {
|
||||
this._setAudioNode(gAudioNodes.get(id));
|
||||
},
|
||||
|
||||
/**
|
||||
* Executed when an audio prop is changed in the UI.
|
||||
*/
|
||||
_onEval: Task.async(function* (variable, value) {
|
||||
let ownerScope = variable.ownerView;
|
||||
let node = this._currentNode;
|
||||
let propName = variable.name;
|
||||
let error;
|
||||
|
||||
if (!variable._initialDescriptor.writable) {
|
||||
error = new Error("Variable " + propName + " is not writable.");
|
||||
} else {
|
||||
// Cast value to proper type
|
||||
try {
|
||||
let number = parseFloat(value);
|
||||
if (!isNaN(number)) {
|
||||
value = number;
|
||||
} else {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
error = yield node.actor.setParam(propName, value);
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO figure out how to handle and display set prop errors
|
||||
// and enable `test/brorwser_wa_properties-view-edit.js`
|
||||
// Bug 994258
|
||||
if (!error) {
|
||||
ownerScope.get(propName).setGrip(value);
|
||||
window.emit(EVENTS.UI_SET_PARAM, node.id, propName, value);
|
||||
} else {
|
||||
window.emit(EVENTS.UI_SET_PARAM_ERROR, node.id, propName, value);
|
||||
}
|
||||
})
|
||||
};
|
||||
103
devtools/client/webaudioeditor/views/utils.js
Normal file
103
devtools/client/webaudioeditor/views/utils.js
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* Takes an element in an SVG graph and iterates over
|
||||
* ancestors until it finds the graph node container. If not found,
|
||||
* returns null.
|
||||
*/
|
||||
|
||||
function findGraphNodeParent(el) {
|
||||
// Some targets may not contain `classList` property
|
||||
if (!el.classList)
|
||||
return null;
|
||||
|
||||
while (!el.classList.contains("nodes")) {
|
||||
if (el.classList.contains("audionode"))
|
||||
return el;
|
||||
else
|
||||
el = el.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object for use with `mix` into a view.
|
||||
* Must have the following properties defined on the view:
|
||||
* - `el`
|
||||
* - `button`
|
||||
* - `_collapseString`
|
||||
* - `_expandString`
|
||||
* - `_toggleEvent`
|
||||
*
|
||||
* Optional properties on the view can be defined to specify default
|
||||
* visibility options.
|
||||
* - `_animated`
|
||||
* - `_delayed`
|
||||
*/
|
||||
var ToggleMixin = {
|
||||
|
||||
bindToggle: function () {
|
||||
this._onToggle = this._onToggle.bind(this);
|
||||
this.button.addEventListener("mousedown", this._onToggle, false);
|
||||
},
|
||||
|
||||
unbindToggle: function () {
|
||||
this.button.removeEventListener("mousedown", this._onToggle);
|
||||
},
|
||||
|
||||
show: function () {
|
||||
this._viewController({ visible: true });
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this._viewController({ visible: false });
|
||||
},
|
||||
|
||||
hideImmediately: function () {
|
||||
this._viewController({ visible: false, delayed: false, animated: false });
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether or not the view.
|
||||
* is currently being shown.
|
||||
*/
|
||||
isVisible: function () {
|
||||
return !this.el.classList.contains("pane-collapsed");
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the view.
|
||||
*
|
||||
* @param object visible
|
||||
* - visible: boolean indicating whether the panel should be shown or not
|
||||
* - animated: boolean indiciating whether the pane should be animated
|
||||
* - delayed: boolean indicating whether the pane's opening should wait
|
||||
* a few cycles or not
|
||||
*/
|
||||
_viewController: function ({ visible, animated, delayed }) {
|
||||
let flags = {
|
||||
visible: visible,
|
||||
animated: animated != null ? animated : !!this._animated,
|
||||
delayed: delayed != null ? delayed : !!this._delayed,
|
||||
callback: () => window.emit(this._toggleEvent, visible)
|
||||
};
|
||||
|
||||
ViewHelpers.togglePane(flags, this.el);
|
||||
|
||||
if (flags.visible) {
|
||||
this.button.classList.remove("pane-collapsed");
|
||||
this.button.setAttribute("tooltiptext", this._collapseString);
|
||||
}
|
||||
else {
|
||||
this.button.classList.add("pane-collapsed");
|
||||
this.button.setAttribute("tooltiptext", this._expandString);
|
||||
}
|
||||
},
|
||||
|
||||
_onToggle: function () {
|
||||
this._viewController({ visible: !this.isVisible() });
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue