This is the view layer (template) for the file tree in the sidebar. It takes a FileTreeViewModel and renders it to the given element using Preact. User actions are signaled via an ActionCreator (in the Flux sense).
var _draggedItemPath;
// Constants
// Time range from first click to second click to invoke renaming.
var CLICK_RENAME_MINIMUM = 500,
RIGHT_MOUSE_BUTTON = 2,
LEFT_MOUSE_BUTTON = 0;
var INDENTATION_WIDTH = 10;
var directoryRenameInput = Preact.createFactory(Preact.createClass({
mixins: [renameBehavior],
This is a mixin that provides drag and drop move function.
var dragAndDrop = {
handleDrag: function(e) {
// Disable drag when renaming
if (this.props.entry.get("rename")) {
e.preventDefault();
e.stopPropagation();
return false;
}
// In newer CEF versions, the drag and drop data from the event
// (i.e. e.dataTransfer.getData) cannot be used to read data in dragOver event,
// so store the drag and drop data in a global variable to read it in the dragOver
// event.
_draggedItemPath = this.myPath();
// Pass the dragged item path.
e.dataTransfer.setData("text", JSON.stringify({
path: _draggedItemPath
}));
this.props.actions.dragItem(this.myPath());
this.setDragImage(e);
e.stopPropagation();
},
handleDrop: function(e) {
var data = JSON.parse(e.dataTransfer.getData("text"));
this.props.actions.moveItem(data.path, this.myPath());
this.setDraggedOver(false);
this.clearDragTimeout();
e.stopPropagation();
},
handleDragEnd: function(e) {
this.clearDragTimeout();
},
handleDragOver: function(e) {
var data = e.dataTransfer.getData("text"),
path;
if (data) {
path = JSON.parse(data).path;
} else {
path = _draggedItemPath;
}
if (path === this.myPath() || FileUtils.getParentPath(path) === this.myPath()) {
e.preventDefault();
e.stopPropagation();
return;
}
var self = this;
this.setDraggedOver(true);
// Open the directory tree when item is dragged over a directory
if (!this.dragOverTimeout) {
this.dragOverTimeout = window.setTimeout(function() {
self.props.actions.setDirectoryOpen(self.myPath(), true);
self.dragOverTimeout = null;
}, 800);
}
e.preventDefault(); // Allow the drop
e.stopPropagation();
},
handleDragLeave: function(e) {
this.setDraggedOver(false);
this.clearDragTimeout();
},
clearDragTimeout: function() {
if (this.dragOverTimeout) {
clearTimeout(this.dragOverTimeout);
this.dragOverTimeout = null;
}
},
setDraggedOver: function(draggedOver) {
if (this.state.draggedOver !== draggedOver) {
this.setState({
draggedOver: draggedOver
});
}
},
setDragImage: function(e) {
var div = window.document.createElement('div');
div.textContent = this.props.name;
div.classList.add('jstree-dragImage');
window.document.body.appendChild(div);
e.dataTransfer.setDragImage(div, -10, -10);
setTimeout(function() {
window.document.body.removeChild(div);
}, 0);
}
};
Mixin for components that support the "icons" and "addClass" extension points.
fileNode
and directoryNode
support this.
var extendable = {
var fileNode = Preact.createFactory(Preact.createClass({
mixins: [contextSettable, pathComputer, extendable, dragAndDrop],
var fileRenameInput = Preact.createFactory(Preact.createClass({
mixins: [renameBehavior],
Displays the absolutely positioned box for the selection or context in the file tree. Its position is determined by passed-in info about the scroller in which the tree resides and the top of the selected node (as reported by the node itself).
Props:
var fileSelectionBox = Preact.createFactory(Preact.createClass({
Mixin that allows a component to compute the full path to its directory entry.
var pathComputer = {
This is a mixin that provides rename input behavior. It is responsible for taking keyboard input and invoking the correct action based on that input.
var renameBehavior = {
On Windows and Linux, the selection bar in the tree does not extend over the scroll bar. The selectionExtension sits on top of the scroll bar to make the selection bar appear to span the whole width of the sidebar.
Props:
var selectionExtension = Preact.createFactory(Preact.createClass({
function _addExtension(category, callback) {
if (!callback || typeof callback !== "function") {
console.error("Attempt to add FileTreeView", category, "extension without a callback function");
return;
}
var callbackList = _extensions.get(category);
if (!callbackList) {
callbackList = Immutable.List();
}
callbackList = callbackList.push(callback);
_extensions = _extensions.set(category, callbackList);
}
function _buildDirsFirstComparator(contents) {
function _dirsFirstCompare(a, b) {
var aIsFile = FileTreeViewModel.isFile(contents.get(a)),
bIsFile = FileTreeViewModel.isFile(contents.get(b));
if (!aIsFile && bIsFile) {
return -1;
} else if (aIsFile && !bIsFile) {
return 1;
} else {
return FileUtils.compareFilenames(a, b);
}
}
return _dirsFirstCompare;
}
function _createAlignedIns(depth) {
return DOM.ins({
className: "jstree-icon",
style: {
marginLeft: INDENTATION_WIDTH * depth
}
});
}
function _createThickness(depth) {
return DOM.div({
style: {
display: "inline-block",
width: INDENTATION_WIDTH * depth
}
});
}
function _getName(fullname, extension) {
return extension !== "" ? fullname.substring(0, fullname.length - extension.length - 1) : fullname;
}
function _measureText(text) {
var measuringElement = $("<span />", { css : { "position" : "absolute", "top" : "-200px", "left" : "-1000px", "visibility" : "hidden", "white-space": "pre" } }).appendTo("body");
measuringElement.text("pW" + text);
var width = measuringElement.width();
measuringElement.remove();
return width;
}
function _sortDirectoryContents(contents, dirsFirst) {
if (dirsFirst) {
return contents.keySeq().sort(_buildDirsFirstComparator(contents));
} else {
return contents.keySeq().sort(FileUtils.compareFilenames);
}
}
// Forward references to keep JSLint happy.
var directoryNode, directoryContents;
function addClassesProvider(callback) {
_addExtension("addClass", callback);
}
// Private API for testing
exports._sortFormattedDirectory = _sortDirectoryContents;
exports._fileNode = fileNode;
exports._directoryNode = directoryNode;
exports._directoryContents = directoryContents;
exports._fileTreeView = fileTreeView;
// Public API
exports.addIconProvider = addIconProvider;
exports.addClassesProvider = addClassesProvider;
exports.render = render;
});
function isDefined(value) {
return value !== undefined;
}
Renders the file tree to the given element.
function render(element, viewModel, projectRoot, actions, forceRender, platform) {
if (!projectRoot) {
return;
}
Preact.render(fileTreeView({
treeData: viewModel.treeData,
selectionViewInfo: viewModel.selectionViewInfo,
sortDirectoriesFirst: viewModel.sortDirectoriesFirst,
parentPath: projectRoot.fullPath,
actions: actions,
extensions: _extensions,
platform: platform,
forceRender: forceRender
}),
element);
}