mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 16:28:38 +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
593
devtools/client/projecteditor/lib/tree.js
Normal file
593
devtools/client/projecteditor/lib/tree.js
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
const { emit } = require("sdk/event/core");
|
||||
const { EventTarget } = require("sdk/event/target");
|
||||
const { merge } = require("sdk/util/object");
|
||||
const promise = require("promise");
|
||||
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
|
||||
const { on, forget } = require("devtools/client/projecteditor/lib/helpers/event");
|
||||
const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
/**
|
||||
* ResourceContainer is used as the view of a single Resource in
|
||||
* the tree. It is not exported.
|
||||
*/
|
||||
var ResourceContainer = Class({
|
||||
/**
|
||||
* @param ProjectTreeView tree
|
||||
* @param Resource resource
|
||||
*/
|
||||
initialize: function (tree, resource) {
|
||||
this.tree = tree;
|
||||
this.resource = resource;
|
||||
this.elt = null;
|
||||
this.expander = null;
|
||||
this.children = null;
|
||||
|
||||
let doc = tree.doc;
|
||||
|
||||
this.elt = doc.createElementNS(HTML_NS, "li");
|
||||
this.elt.classList.add("child");
|
||||
|
||||
this.line = doc.createElementNS(HTML_NS, "div");
|
||||
this.line.classList.add("child");
|
||||
this.line.classList.add("entry");
|
||||
this.line.setAttribute("theme", "dark");
|
||||
this.line.setAttribute("tabindex", "0");
|
||||
|
||||
this.elt.appendChild(this.line);
|
||||
|
||||
this.highlighter = doc.createElementNS(HTML_NS, "span");
|
||||
this.highlighter.classList.add("highlighter");
|
||||
this.line.appendChild(this.highlighter);
|
||||
|
||||
this.expander = doc.createElementNS(HTML_NS, "span");
|
||||
this.expander.className = "arrow expander";
|
||||
this.expander.setAttribute("open", "");
|
||||
this.line.appendChild(this.expander);
|
||||
|
||||
this.label = doc.createElementNS(HTML_NS, "span");
|
||||
this.label.className = "file-label";
|
||||
this.line.appendChild(this.label);
|
||||
|
||||
this.line.addEventListener("contextmenu", (ev) => {
|
||||
this.select();
|
||||
this.openContextMenu(ev);
|
||||
}, false);
|
||||
|
||||
this.children = doc.createElementNS(HTML_NS, "ul");
|
||||
this.children.classList.add("children");
|
||||
|
||||
this.elt.appendChild(this.children);
|
||||
|
||||
this.line.addEventListener("click", (evt) => {
|
||||
this.select();
|
||||
this.toggleExpansion();
|
||||
evt.stopPropagation();
|
||||
}, false);
|
||||
this.expander.addEventListener("click", (evt) => {
|
||||
this.toggleExpansion();
|
||||
this.select();
|
||||
evt.stopPropagation();
|
||||
}, true);
|
||||
|
||||
if (!this.resource.isRoot) {
|
||||
this.expanded = false;
|
||||
}
|
||||
this.update();
|
||||
},
|
||||
|
||||
toggleExpansion: function () {
|
||||
if (!this.resource.isRoot) {
|
||||
this.expanded = !this.expanded;
|
||||
} else {
|
||||
this.expanded = true;
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.elt.remove();
|
||||
this.expander.remove();
|
||||
this.highlighter.remove();
|
||||
this.children.remove();
|
||||
this.label.remove();
|
||||
this.elt = this.expander = this.highlighter = this.children = this.label = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the context menu when right clicking on the view.
|
||||
* XXX: We could pass this to plugins to allow themselves
|
||||
* to be register/remove items from the context menu if needed.
|
||||
*
|
||||
* @param Event e
|
||||
*/
|
||||
openContextMenu: function (ev) {
|
||||
ev.preventDefault();
|
||||
let popup = this.tree.options.contextMenuPopup;
|
||||
popup.openPopupAtScreen(ev.screenX, ev.screenY, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the view based on the current state of the Resource.
|
||||
*/
|
||||
update: function () {
|
||||
let visible = this.tree.options.resourceVisible ?
|
||||
this.tree.options.resourceVisible(this.resource) :
|
||||
true;
|
||||
|
||||
this.elt.hidden = !visible;
|
||||
|
||||
this.tree.options.resourceFormatter(this.resource, this.label);
|
||||
|
||||
this.expander.style.visibility = this.resource.hasChildren ? "visible" : "hidden";
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Select this view in the ProjectTreeView.
|
||||
*/
|
||||
select: function () {
|
||||
this.tree.selectContainer(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns Boolean
|
||||
* Is this view currently selected
|
||||
*/
|
||||
get selected() {
|
||||
return this.line.classList.contains("selected");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the selected state in the UI.
|
||||
*/
|
||||
set selected(v) {
|
||||
if (v) {
|
||||
this.line.classList.add("selected");
|
||||
} else {
|
||||
this.line.classList.remove("selected");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns Boolean
|
||||
* Are any children visible.
|
||||
*/
|
||||
get expanded() {
|
||||
return !this.elt.classList.contains("tree-collapsed");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the visiblity state of children.
|
||||
*/
|
||||
set expanded(v) {
|
||||
if (v) {
|
||||
this.elt.classList.remove("tree-collapsed");
|
||||
this.expander.setAttribute("open", "");
|
||||
} else {
|
||||
this.expander.removeAttribute("open");
|
||||
this.elt.classList.add("tree-collapsed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* TreeView is a view managing a list of children.
|
||||
* It is not to be instantiated directly - only extended.
|
||||
* Use ProjectTreeView instead.
|
||||
*/
|
||||
var TreeView = Class({
|
||||
extends: EventTarget,
|
||||
|
||||
/**
|
||||
* @param Document document
|
||||
* @param Object options
|
||||
* - contextMenuPopup: a <menupopup> element
|
||||
* - resourceFormatter: a function(Resource, DOMNode)
|
||||
* that renders the resource into the view
|
||||
* - resourceVisible: a function(Resource) -> Boolean
|
||||
* that determines if the resource should show up.
|
||||
*/
|
||||
initialize: function (doc, options) {
|
||||
this.doc = doc;
|
||||
this.options = merge({
|
||||
resourceFormatter: function (resource, elt) {
|
||||
elt.textContent = resource.toString();
|
||||
}
|
||||
}, options);
|
||||
this.models = new Set();
|
||||
this.roots = new Set();
|
||||
this._containers = new Map();
|
||||
this.elt = this.doc.createElementNS(HTML_NS, "div");
|
||||
this.elt.tree = this;
|
||||
this.elt.className = "sources-tree";
|
||||
this.elt.setAttribute("with-arrows", "true");
|
||||
this.elt.setAttribute("theme", "dark");
|
||||
this.elt.setAttribute("flex", "1");
|
||||
|
||||
this.children = this.doc.createElementNS(HTML_NS, "ul");
|
||||
this.elt.appendChild(this.children);
|
||||
|
||||
this.resourceChildrenChanged = this.resourceChildrenChanged.bind(this);
|
||||
this.removeResource = this.removeResource.bind(this);
|
||||
this.updateResource = this.updateResource.bind(this);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this._destroyed = true;
|
||||
this.elt.remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to create DOM elements for promptNew and promptEdit
|
||||
*/
|
||||
createInputContainer: function () {
|
||||
let inputholder = this.doc.createElementNS(HTML_NS, "div");
|
||||
inputholder.className = "child entry";
|
||||
|
||||
let expander = this.doc.createElementNS(HTML_NS, "span");
|
||||
expander.className = "arrow expander";
|
||||
expander.setAttribute("invisible", "");
|
||||
inputholder.appendChild(expander);
|
||||
|
||||
let placeholder = this.doc.createElementNS(HTML_NS, "div");
|
||||
placeholder.className = "child";
|
||||
inputholder.appendChild(placeholder);
|
||||
|
||||
return {inputholder, placeholder};
|
||||
},
|
||||
|
||||
/**
|
||||
* Prompt the user to create a new file in the tree.
|
||||
*
|
||||
* @param string initial
|
||||
* The suggested starting file name
|
||||
* @param Resource parent
|
||||
* @param Resource sibling
|
||||
* Which resource to put this next to. If not set,
|
||||
* it will be put in front of all other children.
|
||||
*
|
||||
* @returns Promise
|
||||
* Resolves once the prompt has been successful,
|
||||
* Rejected if it is cancelled
|
||||
*/
|
||||
promptNew: function (initial, parent, sibling = null) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let parentContainer = this._containers.get(parent);
|
||||
let item = this.doc.createElement("li");
|
||||
item.className = "child";
|
||||
|
||||
let {inputholder, placeholder} = this.createInputContainer();
|
||||
item.appendChild(inputholder);
|
||||
|
||||
let children = parentContainer.children;
|
||||
sibling = sibling ? this._containers.get(sibling).elt : null;
|
||||
parentContainer.children.insertBefore(item, sibling ? sibling.nextSibling : children.firstChild);
|
||||
|
||||
new InplaceEditor({
|
||||
element: placeholder,
|
||||
initial: initial,
|
||||
preserveTextStyles: true,
|
||||
start: editor => {
|
||||
editor.input.select();
|
||||
},
|
||||
done: function (val, commit) {
|
||||
if (commit) {
|
||||
deferred.resolve(val);
|
||||
} else {
|
||||
deferred.reject(val);
|
||||
}
|
||||
parentContainer.line.focus();
|
||||
},
|
||||
destroy: () => {
|
||||
item.parentNode.removeChild(item);
|
||||
},
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prompt the user to rename file in the tree.
|
||||
*
|
||||
* @param string initial
|
||||
* The suggested starting file name
|
||||
* @param resource
|
||||
*
|
||||
* @returns Promise
|
||||
* Resolves once the prompt has been successful,
|
||||
* Rejected if it is cancelled
|
||||
*/
|
||||
promptEdit: function (initial, resource) {
|
||||
let deferred = promise.defer();
|
||||
let item = this._containers.get(resource).elt;
|
||||
let originalText = item.childNodes[0];
|
||||
|
||||
let {inputholder, placeholder} = this.createInputContainer();
|
||||
item.insertBefore(inputholder, originalText);
|
||||
|
||||
item.removeChild(originalText);
|
||||
|
||||
new InplaceEditor({
|
||||
element: placeholder,
|
||||
initial: initial,
|
||||
preserveTextStyles: true,
|
||||
start: editor => {
|
||||
editor.input.select();
|
||||
},
|
||||
done: function (val, commit) {
|
||||
if (val === initial) {
|
||||
item.insertBefore(originalText, inputholder);
|
||||
}
|
||||
|
||||
item.removeChild(inputholder);
|
||||
|
||||
if (commit) {
|
||||
deferred.resolve(val);
|
||||
} else {
|
||||
deferred.reject(val);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new Store into the TreeView
|
||||
*
|
||||
* @param Store model
|
||||
*/
|
||||
addModel: function (model) {
|
||||
if (this.models.has(model)) {
|
||||
// Requesting to add a model that already exists
|
||||
return;
|
||||
}
|
||||
this.models.add(model);
|
||||
let placeholder = this.doc.createElementNS(HTML_NS, "li");
|
||||
placeholder.style.display = "none";
|
||||
this.children.appendChild(placeholder);
|
||||
this.roots.add(model.root);
|
||||
model.root.refresh().then(root => {
|
||||
if (this._destroyed || !this.models.has(model)) {
|
||||
// model may have been removed during the initial refresh.
|
||||
// In this case, do not import the resource or add to DOM, just leave it be.
|
||||
return;
|
||||
}
|
||||
let container = this.importResource(root);
|
||||
container.line.classList.add("entry-group-title");
|
||||
container.line.setAttribute("theme", "dark");
|
||||
this.selectContainer(container);
|
||||
|
||||
this.children.insertBefore(container.elt, placeholder);
|
||||
this.children.removeChild(placeholder);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a Store from the TreeView
|
||||
*
|
||||
* @param Store model
|
||||
*/
|
||||
removeModel: function (model) {
|
||||
this.models.delete(model);
|
||||
this.removeResource(model.root);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Get the ResourceContainer. Used for testing the view.
|
||||
*
|
||||
* @param Resource resource
|
||||
* @returns ResourceContainer
|
||||
*/
|
||||
getViewContainer: function (resource) {
|
||||
return this._containers.get(resource);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a ResourceContainer in the tree.
|
||||
*
|
||||
* @param ResourceContainer container
|
||||
*/
|
||||
selectContainer: function (container) {
|
||||
if (this.selectedContainer === container) {
|
||||
return;
|
||||
}
|
||||
if (this.selectedContainer) {
|
||||
this.selectedContainer.selected = false;
|
||||
}
|
||||
this.selectedContainer = container;
|
||||
container.selected = true;
|
||||
emit(this, "selection", container.resource);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a Resource in the tree.
|
||||
*
|
||||
* @param Resource resource
|
||||
*/
|
||||
selectResource: function (resource) {
|
||||
this.selectContainer(this._containers.get(resource));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the currently selected Resource
|
||||
*
|
||||
* @param Resource resource
|
||||
*/
|
||||
getSelectedResource: function () {
|
||||
return this.selectedContainer.resource;
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert a Resource into the view.
|
||||
* Makes a new ResourceContainer if needed
|
||||
*
|
||||
* @param Resource resource
|
||||
*/
|
||||
importResource: function (resource) {
|
||||
if (!resource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._containers.has(resource)) {
|
||||
return this._containers.get(resource);
|
||||
}
|
||||
var container = ResourceContainer(this, resource);
|
||||
this._containers.set(resource, container);
|
||||
this._updateChildren(container);
|
||||
|
||||
on(this, resource, "children-changed", this.resourceChildrenChanged);
|
||||
on(this, resource, "label-change", this.updateResource);
|
||||
on(this, resource, "deleted", this.removeResource);
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a Resource (including children) from the view.
|
||||
*
|
||||
* @param Resource resource
|
||||
*/
|
||||
removeResource: function (resource) {
|
||||
let toRemove = resource.allDescendants();
|
||||
toRemove.add(resource);
|
||||
for (let remove of toRemove) {
|
||||
this._removeResource(remove);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an individual Resource (but not children) from the view.
|
||||
*
|
||||
* @param Resource resource
|
||||
*/
|
||||
_removeResource: function (resource) {
|
||||
forget(this, resource);
|
||||
if (this._containers.get(resource)) {
|
||||
this._containers.get(resource).destroy();
|
||||
this._containers.delete(resource);
|
||||
}
|
||||
emit(this, "resource-removed", resource);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for when a resource has new children.
|
||||
* This can happen as files are being loaded in from FileSystem, for example.
|
||||
*
|
||||
* @param Resource resource
|
||||
*/
|
||||
resourceChildrenChanged: function (resource) {
|
||||
this.updateResource(resource);
|
||||
this._updateChildren(this._containers.get(resource));
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for when a label in the view has been updated.
|
||||
* For example, the 'dirty' plugin marks changed files with an '*'
|
||||
* next to the filename, and notifies with this event.
|
||||
*
|
||||
* @param Resource resource
|
||||
*/
|
||||
updateResource: function (resource) {
|
||||
let container = this._containers.get(resource);
|
||||
container.update();
|
||||
},
|
||||
|
||||
/**
|
||||
* Build necessary ResourceContainers for a Resource and its
|
||||
* children, then append them into the view.
|
||||
*
|
||||
* @param ResourceContainer container
|
||||
*/
|
||||
_updateChildren: function (container) {
|
||||
let resource = container.resource;
|
||||
let fragment = this.doc.createDocumentFragment();
|
||||
if (resource.children) {
|
||||
for (let child of resource.childrenSorted) {
|
||||
let childContainer = this.importResource(child);
|
||||
fragment.appendChild(childContainer.elt);
|
||||
}
|
||||
}
|
||||
|
||||
while (container.children.firstChild) {
|
||||
container.children.firstChild.remove();
|
||||
}
|
||||
|
||||
container.children.appendChild(fragment);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* ProjectTreeView is the implementation of TreeView
|
||||
* that is exported. This is the class that is to be used
|
||||
* directly.
|
||||
*/
|
||||
var ProjectTreeView = Class({
|
||||
extends: TreeView,
|
||||
|
||||
/**
|
||||
* See TreeView.initialize
|
||||
*
|
||||
* @param Document document
|
||||
* @param Object options
|
||||
*/
|
||||
initialize: function (document, options) {
|
||||
TreeView.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.forgetProject();
|
||||
TreeView.prototype.destroy.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove current project and empty the tree
|
||||
*/
|
||||
forgetProject: function () {
|
||||
if (this.project) {
|
||||
forget(this, this.project);
|
||||
for (let store of this.project.allStores()) {
|
||||
this.removeModel(store);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a project in the tree
|
||||
*
|
||||
* @param Project project
|
||||
* The project to render into a tree
|
||||
*/
|
||||
setProject: function (project) {
|
||||
this.forgetProject();
|
||||
this.project = project;
|
||||
if (this.project) {
|
||||
on(this, project, "store-added", this.addModel.bind(this));
|
||||
on(this, project, "store-removed", this.removeModel.bind(this));
|
||||
on(this, project, "project-saved", this.refresh.bind(this));
|
||||
this.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the tree with all of the current project stores
|
||||
*/
|
||||
refresh: function () {
|
||||
for (let store of this.project.allStores()) {
|
||||
this.addModel(store);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.ProjectTreeView = ProjectTreeView;
|
||||
Loading…
Add table
Add a link
Reference in a new issue