import { Resources } from '../../resources/resources';
import { SessionLogin } from './sessionlogin';
import { checkBrowserCompatibility } from '../../view/helpers';
import { Events, ApiMethods, callApiMethod, getThumbnailUrl } from './components';
import $ from 'jquery';
import { createTree } from 'jquery.fancytree/dist/modules/jquery.fancytree';
import 'jquery.fancytree/dist/modules/jquery.fancytree.dnd5';
import 'jquery.fancytree/dist/skin-win8/ui.fancytree.min.css';
import 'font-awesome/css/font-awesome.css';
let PmaStartUrl = "http://127.0.0.1:54001/";
export
/**
* Represents a UI component that shows a tree view that allows browsing through the directories and slides from multiple PMA.core servers. This component uses {@link https://github.com/mar10/fancytree|fancytree} under the hood.
* @memberof PMA.UI.Components
* @alias Tree
* @param {Context} context
* @param {object} options - Configuration options
* @param {Tree~server[]} options.servers An array of servers to show files from
* @param {string|HTMLElement} options.element - The element that hosts the tree view. It can be either a valid CSS selector or an HTMLElement instance.
* @param {function} [options.renderNode] - Allows tweaking after node state was rendered
* @param {Tree~rootDirSortCb} [options.rootDirSortCb] - Function that sorts an array of directories
* @param {Tree~filterDirectoryCb} [options.filterDirectoryCb] - Function that filters directories before they appear in the tree view
* @param {Tree~filterFileCb} [options.filterFileCb] - Function that filters files before they appear in the tree view
* @param {boolean} [options.checkboxes=false] - Allows multi selection of files with checkboxes
* @param {boolean} [options.autoDetectPmaStart=false] - Whether the tree should try to connect to a PMA.core lite server
* @param {boolean} [options.autoExpandNodes=false] - Whether the tree should expand nodes on single click
* @param {boolean} [options.preview=false] - Whether the tree should show a popover preview of slides
* @param {boolean} [options.search=false] - Whether the tree should show a textbox for enabling search
* @fires PMA.UI.Components.Events.SlideSelected
* @fires PMA.UI.Components.Events.DirectorySelected
* @fires PMA.UI.Components.Events.ServerSelected
* @fires PMA.UI.Components.Events.MultiSelectionChanged
* @fires PMA.UI.Components.Events.TreeNodeDoubleClicked
* @fires PMA.UI.Components.Events.ServerExpanded
* @fires PMA.UI.Components.Events.DirectoryExpanded
* @fires PMA.UI.Components.Events.SearchFinished
* @fires PMA.UI.Components.Events.SearchFailed
* @tutorial 04-tree
*/
class Tree {
/**
* Holds information about a PMA.core server
* @typedef {Object} Tree~server
* @property {string} name - The display name of the server
* @property {string} url - The url of the server
* @property {string} [path] - An optional path of a folder in the server to show
* @property {boolean} [showFiles=true] - Whether or not to display slides along with directories
*/
/**
* Holds information about a tree node
* @typedef {Object} Tree~item
* @property {string} serverUrl - The url of the server this node belongs to
* @property {string} path - The path of the node
* @property {boolean} isDirectory - Whether or not to the node is a directory
*/
/**
* Function that sorts an array of directories
* @callback Tree~rootDirSortCb
* @param {String[]} directories
* @returns {String[]}
*/
/**
* Function that filters a directory given it's path
* @callback Tree~filterDirectoryCb
* @param {String} dirPath
* @returns {boolean}
*/
/**
* Function that filters a file given it's path
* @callback Tree~filterFileCb
* @param {String} filePath
* @returns {boolean}
*/
/**
* Function that gets called when an attempt to add a server completes.
* @callback Tree~addServerCallback
* @param {boolean} success - Whether the server was successfully added or not
*/
constructor(context, options) {
if (!checkBrowserCompatibility()) {
return;
}
this.element = options.element;
if (typeof options.element == "string") {
var el = document.querySelector(options.element);
if (!el) {
console.error("Invalid selector for element");
}
else {
this.element = el;
}
}
this.context = context;
this.servers = options.servers || [];
var _this = this;
this.navigating = false;
this.lastNavigatePathRequest = null;
this.autoExpand = options.autoExpandNodes === true;
this.checkboxes = options.checkboxes === true;
this.lastSearchResults = {};
this.listeners = {};
this.listeners[Events.DirectorySelected] = [];
this.listeners[Events.SlideSelected] = [];
this.listeners[Events.ServerSelected] = [];
this.listeners[Events.MultiSelectionChanged] = [];
this.listeners[Events.TreeNodeDoubleClicked] = [];
this.listeners[Events.ServerExpanded] = [];
this.listeners[Events.DirectoryExpanded] = [];
this.listeners[Events.SearchFinished] = [];
this.listeners[Events.SearchFailed] = [];
this.lastSearchHash = 0;
this.previewEnabled = false;
if (typeof options.rootDirSortCb === "function") {
this.rootDirSortCb = options.rootDirSortCb;
}
if (typeof options.filterDirectoryCb === "function") {
this.filterDirectoryCb = options.filterDirectoryCb;
}
else {
this.filterDirectoryCb = null;
}
if (typeof options.filterFileCb === "function") {
this.filterFileCb = options.filterFileCb;
}
else {
this.filterFileCb = null;
}
var sourceData = [];
for (var i = 0; i < this.servers.length; i++) {
sourceData.push({
title: this.servers[i].name,
serverNode: true,
key: this.servers[i].url,
serverIndex: i,
extraClasses: "server",
dirPath: (this.servers[i].path ? this.servers[i].path : "/"),
lazy: true,
unselectableStatus: false,
unselectable: true,
selected: false,
checkbox: false
});
// try get version for each server
this.context.getVersionInfo(this.servers[i].url, serverVersionResult.bind(this, this.servers[i]));
}
var searchBox = null;
if (options.search === true) {
var cls = options.searchClass ? options.searchClass : 'pma-ui-tree-search-box';
searchBox = $("<input type='text' class='" + cls + "' placeholder='" + Resources.translate("Search") + "'/><hr />").appendTo(this.element);
}
this.fancytree = createTree(this.element, {
keyPathSeparator: "?",
checkbox: options.checkboxes === true,
extensions: ["dnd5", /*"glyph", "wide"*/],
dnd5: {
dragStart: function (node, data) {
// Called when user starts dragging `node`.
// This method MUST be defined to enable dragging for tree nodes.
//
// We can
// Add or modify the drag data using `data.dataTransfer.setData()`
// Return false to cancel dragging of `node`.
// For example:
// if( data.originalEvent.shiftKey ) ...
// if( node.isFolder() ) { return false; }
if (node.data.dirPath && !node.data.serverNode) {
node.data.dragging = true;
if (node.isActive) {
node.setActive(false);
}
data.dataTransfer.setData("text", JSON.stringify({
serverUrl: node.data.serverUrl,
path: node.data.dirPath,
isFolder: node.isFolder()
}));
return true;
}
return false;
},
dragEnd: function (node, data) {
node.data.dragging = false;
}
},
//glyph: glyph_opts,
selectMode: 3,
toggleEffect: { options: { direction: "left" }, duration: 400 },
// toggleEffect: false,
wide: {
iconWidth: "1em", // Adjust this if @fancy-icon-width != "16px"
iconSpacing: "0.5em", // Adjust this if @fancy-icon-spacing != "3px"
levelOfs: "1.5em" // Adjust this if ul padding != "16px"
},
icon: function (event, data) {
if (data.node.key === "_searchResults") {
if (data.node.data.searching) {
return "fa fa-spinner fa-spin";
}
else {
return "fa fa-search";
}
}
else if (data.node.data.serverNode === true) {
if (data.node.data.authenticated === true) {
return "server success";
} else if (data.node.data.authenticated === false) {
return "server error";
} else {
return "server";
}
}
else if (data.node.data.rootDir === true) {
return "rootdir";
}
else if (!data.node.isFolder()) {
return "image";
}
},
renderNode: (typeof options.renderNode === "function" ? options.renderNode : null),
source: sourceData,
lazyLoad: loadNode.bind(this),
activate: function (event, data) {
// A node was activated:
var node = data.node;
if (node.key == "_searchResults" && _this.autoExpand === true) {
node.setExpanded(true);
}
setTimeout(function () {
if (node.data && node.data.dragging === true) {
event.preventDefault();
return;
}
if (node.data.serverNode === true) {
_this.fireEvent(Events.ServerSelected, { serverUrl: _this.servers[node.data.serverIndex].url });
return;
}
if (node.data.dirPath !== "/") {
if (node.isFolder()) {
if (_this.autoExpand === true) {
node.setExpanded(true);
}
_this.fireEvent(Events.DirectorySelected, { serverUrl: _this.servers[node.data.serverIndex].url, path: node.data.dirPath });
}
else {
if (_this.servers[node.data.serverIndex]) {
_this.fireEvent(Events.SlideSelected, { serverUrl: _this.servers[node.data.serverIndex].url, path: node.data.dirPath });
}
}
}
else if (_this.autoExpand === true) {
node.setExpanded(true);
}
}, 300);
},
select: function (event, data) {
var n = data.tree.getSelectedNodes();
var selectionArray = [];
if (n && n.length > 0) {
for (var i = 0; i < n.length; i++) {
if (!(n[i] === null || n[i].data.serverNode === true || n[i].data.rootDir === true || n[i].isFolder())) {
selectionArray.push({ serverUrl: _this.servers[n[i].data.serverIndex].url, path: n[i].data.dirPath });
}
}
}
_this.fireEvent(Events.MultiSelectionChanged, selectionArray);
},
expand: function (event, data) {
var node = data.node;
setTimeout(function () {
if (node.data.serverNode === true) {
_this.fireEvent(Events.ServerExpanded, { serverUrl: _this.servers[node.data.serverIndex].url });
return;
}
else if (node.data.dirPath !== "/") {
if (node.isFolder()) {
_this.fireEvent(Events.DirectoryExpanded, { serverUrl: _this.servers[node.data.serverIndex].url, path: node.data.dirPath });
return;
}
}
}, 300);
},
dblclick: function (event, data) {
var node = data.node;
_this.fireEvent(Events.TreeNodeDoubleClicked,
{
serverUrl: _this.servers[node.data.serverIndex].url,
path: node.data.dirPath,
isSlide: !(node.data.serverNode === true || node.data.rootDir === true || node.isFolder())
});
}
});
if (options.preview === true) {
enablePreview.call(this, this.fancytree);
}
if (options.autoDetectPmaStart === true) {
this.addPmaStartServer();
}
if (options.search === true) {
$(this.element).find(".ui-fancytree").addClass("ui-fancytree-search");
}
// this.fancytree = $.ui.fancytree.getTree(this.element);
var self = this;
var searchTimeout = null;
var t = this.fancytree;
if (searchBox) {
searchBox.on('input propertychange paste', function () {
var val = $(this).val();
if (val && val.length > 3) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(function () {
startSearch.call(self, val);
}, 500);
}
else {
var n = t.getNodeByKey("_searchResults");
if (n) {
n.remove();
}
}
});
}
}
/**
* Toggle the live preview on/off
* @param {boolean} enable
*/
togglePreview(enable) {
if (enable && !this.previewEnabled) {
enablePreview.call(this, $(this.element));
}
else if (!enable && this.previewEnabled) {
disablePreview.call(this, $(this.element));
}
}
/**
* Adds a new server to the tree
* @param {Tree~server} server A server object
*/
addServer(server) {
if (server) {
this.servers.push(server);
var serverInfo = {
title: this.servers[this.servers.length - 1].name,
serverNode: true,
key: this.servers[this.servers.length - 1].url,
serverIndex: this.servers.length - 1,
extraClasses: "server",
dirPath: (this.servers[this.servers.length - 1].path ? this.servers[this.servers.length - 1].path : "/"),
lazy: true,
unselectableStatus: false,
unselectable: true,
selected: false,
checkbox: false
};
// try get version for server
this.context.getVersionInfo(server.url, serverVersionResult.bind(this, server));
this.fancytree.getRootNode().addChildren(serverInfo);
}
}
/**
* Removes a server from the tree
* @param {number} index The index of the server to remove
*/
removeServer(index) {
var children = this.fancytree.getRootNode().getChildren();
if (children && children.length && index >= 0 && index < children.length) {
if (children[index].data) {
this.servers.splice(children[index].data.serverIndex, 1);
}
children[index].remove();
}
else {
console.error("No children found or index out of range");
}
}
/**
* Removes a Pma.start server if it exists
*/
removePmaStartServer() {
var children = this.fancytree.getRootNode().getChildren();
for (var i = 0; i < children.length; i++) {
if (children[i].key == PmaStartUrl) {
this.removeServer(children[i].data.serverIndex);
}
}
}
/**
* Add a pma.start server if available
* @param {Tree~addServerCallback} callback - The function to call when the attempt to add server completes
*/
addPmaStartServer(callback) {
// This function adds a SessionLogin provider to the context as many times as it is called.
// This needs fixing
var _this = this;
var children = this.fancytree.getRootNode().getChildren();
for (var i = 0; i < children.length; i++) {
if (children[i].key == PmaStartUrl) {
return;
}
}
callApiMethod({
method: ApiMethods.GetVersionInfo,
httpMethod: "GET",
data: { rnd: Math.random() },
serverUrl: PmaStartUrl,
success: function () {
new SessionLogin(_this.context, [{ serverUrl: PmaStartUrl, sessionId: "pma.core.lite" }]);
_this.addServer({ name: Resources.translate("Computer"), url: PmaStartUrl });
if (typeof callback === "function") {
callback.call(this, true);
}
},
failure: function () {
if (typeof callback === "function") {
callback.call(this, false);
}
}
});
}
/**
* Returns the list of servers currently under the tree view
* @returns {Tree~server[]}
*/
getServers() {
return this.servers;
}
/**
* Gets the currently selected slide or null
* @returns {Tree~server}
*/
getSelectedSlide() {
var n = this.fancytree.getActiveNode();
if (n === null || n.data.serverNode === true || n.data.rootDir === true || n.isFolder()) {
return null;
}
return { server: this.servers[n.data.serverIndex].url, path: n.data.dirPath };
}
/**
* Gets the currently selected directory or null
* @returns {Tree~server}
*/
getSelectedDirectory() {
var n = this.fancytree.getActiveNode();
if (n !== null && (n.data.rootDir === true || n.isFolder())) {
return { server: this.servers[n.data.serverIndex].url, path: n.data.dirPath };
}
return null;
}
/**
* Gets an array with the checked slides or an empty array
* @param {("slides"|"directories"|"all")} [contentType="slides"] - The type of content to return
* @returns {Tree~item[]}
*/
getMultiSelection(contentType) {
const contentTypes = ["slides", "directories", "all"];
if (contentTypes.indexOf(contentType) === -1) {
contentType = contentTypes[0];
}
var n = this.fancytree.getSelectedNodes();
var selectionArray = [];
if (n && n.length > 0) {
for (var i = 0; i < n.length; i++) {
if (n[i] === null || n[i].data.serverNode === true) {
continue;
}
const isDir = n[i].data.rootDir || n[i].isFolder();
const addResult = (contentType === "slides" && !isDir) ||
(contentType === "directories" && isDir) ||
(contentType === "all");
if (addResult) {
selectionArray.push({ serverUrl: this.servers[n[i].data.serverIndex].url, path: n[i].data.dirPath, isDirectory: isDir });
}
}
}
return selectionArray;
}
/**
* Clears the selected nodes in the tree view
*/
clearMultiSelection() {
this.fancytree.selectAll(false);
}
/**
* Navigates to a path in the tree
* @param {string} path - The virtual path to navigate to. The server part of the path should be the server NAME(not the server url)
*/
navigateTo(path) {
// fancy tree needs a path separated with options.keyPathSeparator
// ex. ?key1?key2?key3
var self = this;
if (self.navigating) {
self.lastNavigatePathRequest = path;
return;
}
self.navigating = true;
var tree = this.fancytree;
var key = virtualPathToTreePath.call(this, path, tree);
return tree.loadKeyPath(key, function (node, status) {
if (status === "ok") {
node.setActive();
node.scrollIntoView();
self.navigating = false;
}
}).done(function () {
self.navigating = false;
if (self.lastNavigatePathRequest) {
var p = self.lastNavigatePathRequest;
self.lastNavigatePathRequest = null;
self.navigateTo(p);
}
});
}
/**
* Refreshes a node in the tree specified by the path (server or directory)
* @param {string} path - The virtual path to refresh. The server part of the path should be the server NAME(not the server url)
*/
refresh(path) {
var tree = this.fancytree;
var key = virtualPathToTreePath.call(this, path, tree);
return tree.loadKeyPath(key, function (node, status) {
if (status === "ok") {
node.resetLazy();
}
});
}
/**
* Returns the last search results, grouped by the server name
* @return {Object.<string, string[]>} The object containing the result for each server available
*/
getSearchResults() {
return this.lastSearchResults;
}
/**
* Clears any search results for this tree
*/
clearSearchResults() {
var tree = this.fancytree;
var n = tree.getNodeByKey("_searchResults");
if (n) {
n.remove();
}
}
/**
* Searches for files matching the specified value, and appends them to the tree. Use {@link Tree#getSearchResults|getSearchResults} to get the search results
* @param {string} value - The value to search for
* @fires PMA.UI.Components.Events.SearchFinished
*/
search(value) {
this.clearSearchResults();
return startSearch.call(this, value);
}
/**
* Attaches an event listener
* @param {PMA.UI.Components.Events} eventName - The name of the event to listen to
* @param {function} callback - The function to call when the event occurs
*/
listen(eventName, callback) {
// if (!this.listeners.hasOwnProperty(eventName)) {
if (!Object.prototype.hasOwnProperty.call(this.listeners, eventName)) {
console.error(eventName + " is not a valid event");
}
this.listeners[eventName].push(callback);
}
// fires an event
fireEvent(eventName, eventArgs) {
// if (!this.listeners.hasOwnProperty(eventName)) {
if (!Object.prototype.hasOwnProperty.call(this.listeners, eventName)) {
console.error(eventName + " does not exist");
return;
}
for (var i = 0, max = this.listeners[eventName].length; i < max; i++) {
this.listeners[eventName][i].call(this, eventArgs);
}
}
/**
* Gets a value indicating whether files are shown for a specified server
* @param {string} serverUrl - A server url to show/hide files for
*/
getFilesVisibility(serverUrl) {
var children = this.fancytree.getRootNode().getChildren();
for (var i = 0; i < children.length; i++) {
if (children[i].key == serverUrl) {
for (var j = 0; j < this.servers.length; j++) {
if (this.servers[j].url == children[i].key) {
return this.servers[j].showFiles;
}
}
}
}
}
/**
* Shows or hides the files of a specified server
* @param {string} serverUrl - A server url to show/hide files for
* @param {boolean} visible - Whether to show or hide files for the specific server
*/
setFilesVisibility(serverUrl, visible) {
var children = this.fancytree.getRootNode().getChildren();
for (var i = 0; i < children.length; i++) {
if (children[i].key == serverUrl) {
for (var j = 0; j < this.servers.length; j++) {
if (this.servers[j].url == children[i].key) {
this.servers[j].showFiles = visible;
}
}
children[i].resetLazy();
}
}
}
/**
* Resets the state of all servers, collapses all visible folder, and resets the lazy load state
*/
collapseAll() {
var children = this.fancytree.getRootNode().getChildren();
for (var i = 0; i < children.length; i++) {
// children[i].setExpanded(false);
children[i].resetLazy();
}
}
/**
* Resets the state of a specified server, collapses all visible folder, resets the lazy load state and invalidates the session
*/
signOut(serverUrl) {
var children = this.fancytree.getRootNode().getChildren();
for (var i = 0; i < children.length; i++) {
if (children[i].key == serverUrl) {
children[i].data.authenticated = undefined;
children[i].renderTitle();
children[i].resetLazy();
this.context.deAuthenticate(serverUrl);
}
}
}
}
function loadNode(event, data) {
let dfd = new $.Deferred();
data.result = dfd.promise();
let node = data.node;
let path = node.data.dirPath === "/" ? "" : node.data.dirPath;
let _this = this;
// if the directories to be loaded are root directories or sub directories
var isRootDir = node.data.dirPath === "/";
this.context.getDirectories(_this.servers[node.data.serverIndex].url, path,
function (sessionId, directories) {
if (node.data.serverNode) {
node.data.authenticated = true;
node.renderTitle();
}
if (_this.rootDirSortCb && path === "") {
directories = _this.rootDirSortCb(directories);
}
let result = [];
for (let i = 0; i < directories.length; i++) {
if (_this.filterDirectoryCb && _this.filterDirectoryCb.call(_this, directories[i]) === false) {
continue;
}
result.push({
title: directories[i].split('/').pop(),
lazy: true,
serverIndex: node.data.serverIndex,
serverUrl: _this.servers[node.data.serverIndex].url,
dirPath: directories[i],
key: directories[i],
folder: true,
rootDir: isRootDir,
extraClasses: isRootDir ? "rootdir" : "subdir",
// unselectableStatus: true,
checkbox: _this.checkboxes
});
}
if (_this.servers[node.data.serverIndex].showFiles !== false && path && path !== "") {
_this.context.getSlides(
{
serverUrl: _this.servers[node.data.serverIndex].url,
path: path,
success: function (sessionId, files) {
for (let i = 0; i < files.length; i++) {
if (_this.filterFileCb && _this.filterFileCb.call(_this, files[i]) === false) {
continue;
}
result.push({
title: files[i].split('/').pop(),
lazy: false,
serverIndex: node.data.serverIndex,
dirPath: files[i],
serverUrl: _this.servers[node.data.serverIndex].url,
key: files[i],
folder: false,
extraClasses: "slide"
});
}
dfd.resolve(result);
},
failure: function (error) {
dfd.reject(error.Message ? error.Message : Resources.translate("Error loading files"));
}
});
}
else {
dfd.resolve(result);
}
},
function (error) {
if (node.data.serverNode) {
node.data.authenticated = false;
node.renderTitle();
}
dfd.reject(error.Message ? error.Message : Resources.translate("Error loading directories"));
});
}
function searchResultsSuccess(searchNode, serverIndex, searchHash, sessionId, results) {
if (this.lastSearchHash !== searchHash) {
return;
}
this.lastSearchResults[this.servers[serverIndex].name] = results;
var searchServerNode = searchNode.addChildren({
title: this.servers[serverIndex].name,
serverNode: true,
key: "_searchServer_" + this.servers[serverIndex].url,
serverIndex: serverIndex,
dirPath: (this.servers[serverIndex].path ? this.servers[serverIndex].path : "/"),
lazy: false,
unselectableStatus: false,
unselectable: true,
selected: false,
checkbox: false,
resultCount: results.length
});
var tree = $(this.element).fancytree("getTree");
for (var r = 0; r < results.length; r++) {
var parts = results[r].split('/');
var thispart = "";
var currentLevel = searchServerNode;
for (var i = 0; i < parts.length; i++) {
thispart += i > 0 ? "/" + parts[i] : parts[i];
var n = tree.getNodeByKey("_searchResult_" + thispart, currentLevel);
if (n == null) {
currentLevel = currentLevel.addChildren({
title: thispart.split("/").pop(),
lazy: false,
serverIndex: serverIndex,
dirPath: thispart,
key: "_searchResult_" + thispart,
folder: i < parts.length - 1,
rootDir: i == 0,
extraClasses: i == 0 ? "rootdir" : "subdir",
checkbox: this.checkboxes,
resultCount: 1
});
}
else {
currentLevel = n;
n.data.resultCount += 1;
}
}
}
searchNode.data.resultCount += results.length;
searchNode.visit(function (n) {
if (!n.parent) {
return;
}
if (n == searchNode) {
n.setTitle(Resources.translate("Search results for \"{pattern}\" ({count})", { pattern: searchNode.data.pattern, count: n.data.resultCount }));
}
else if ((n.isFolder() || n.data.serverNode) && n == searchServerNode) {
n.setTitle(n.title + " (" + n.data.resultCount + ")");
}
}, true);
searchNode.data.serverDone++;
if (searchNode.data.serverDone >= searchNode.data.serversSearched) {
searchNode.data.searching = false;
searchNode.renderTitle();
}
searchNode.makeVisible();
searchNode.setExpanded(true);
this.fireEvent(Events.SearchFinished, this.lastSearchResults);
}
function searchResultError(searchNode, serverIndex, searchHash) {
if (this.lastSearchHash !== searchHash) {
return;
}
searchNode.data.serverDone++;
if (searchNode.data.serverDone >= searchNode.data.serversSearched) {
searchNode.data.searching = false;
searchNode.renderTitle();
}
this.fireEvent(Events.SearchFailed, this.servers[serverIndex]);
}
function serverVersionResult(server, version) {
server.version = version;
}
function startSearch(pattern) {
this.lastSearchResults = {};
var searchResultsNode = $(this.element).fancytree("getTree").getNodeByKey("_searchResults");
if (searchResultsNode == null) {
var searchResultsNodeInfo = {
title: Resources.translate("Search results for \"{pattern}\"", { pattern: pattern }),
key: "_searchResults",
lazy: false,
selected: false,
checkbox: false,
resultCount: 0,
searching: true,
serverDone: 0,
serversSearched: 0,
pattern: pattern
};
searchResultsNode = this.fancytree.getRootNode().addChildren(searchResultsNodeInfo);
}
else {
searchResultsNode.resultCount = 0;
searchResultsNode.removeChildren();
searchResultsNode.data.searching = true;
searchResultsNode.data.serverDone = 0;
searchResultsNode.data.serversSearched = 0;
searchResultsNode.data.pattern = pattern;
searchResultsNode.renderTitle();
}
searchResultsNode.makeVisible();
searchResultsNode.setExpanded(true);
var self = this;
this.lastSearchHash = Math.random();
for (var i = 0; i < this.servers.length; i++) {
if (!this.servers[i].version || this.servers[i].version.substring(0, "1.".length) === "1.") {
continue;
}
searchResultsNode.data.serversSearched++;
this.context.queryFilename(
this.servers[i].url,
"",
pattern,
searchResultsSuccess.bind(self, searchResultsNode, i, this.lastSearchHash),
searchResultError.bind(self, searchResultsNode, i, this.lastSearchHash)
);
}
}
/**
* Converts a virtual path to a key used internaly by the tree component to locate any node
* @param {string} path - The virtual path to convert. The server part should be the NAME of the server (not the url)
* @param {Object} tree - The instance of the fancytree
* @returns {string} The key to the node
* @ignore
*/
function virtualPathToTreePath(path, tree) {
var parts = path.split("/").filter(function (e) { return e != null && e != ""; });
var initialPath = "";
if (parts.length > 0) {
// find server url as first part of path
for (var i = 0; i < this.servers.length; i++) {
if (this.servers[i].name.toLowerCase() == parts[0].toLowerCase()) {
parts[0] = this.servers[i].url;
initialPath = this.servers[i].path ? this.servers[i].path : "";
break;
}
}
if (initialPath) {
parts[1] = initialPath + "/" + parts[1];
}
for (i = 2; i < parts.length; i++) {
parts[i] = parts[i - 1] + "/" + parts[i];
}
}
parts = parts.join(tree.options.keyPathSeparator);
return parts;
}
function disablePreview(tree) {
if (this.previewEnabled) {
$(this.element).off("mouseenter", "span.fancytree-title");
$(this.element).off("mouseleave", "span.fancytree-title");
$(this.element).off("mousemove", "span.fancytree-title");
$("#fancytree-preview").remove();
this.previewEnabled = false;
}
}
function enablePreview(tree) {
if (!this.previewEnabled) {
var xOffset = 100, yOffset = -30;
var hoverTimeout = null;
var _this = this;
$(this.element).on("mouseenter", "span.fancytree-title", function (event) {
// Add a hover handler to all node titles (using event delegation)
var node = $.ui.fancytree.getNode(event);
if (!(node === null || node.data.serverNode === true || node.data.rootDir === true || node.isFolder()) && node.data.serverIndex !== undefined) {
var serverUrl = _this.servers[node.data.serverIndex].url;
var f = function () {
_this.context.getSession(serverUrl, function (sessionId) {
var tUrl = getThumbnailUrl(serverUrl, sessionId, node.data.dirPath, 0, 150, 0);
var el = $("#fancytree-preview");
if (el.length > 0) {
el.remove();
}
el = $("<p id='fancytree-preview' class='fancytree-preview'><i class='fa fa-spinner fa-spin'></i><img/></p>").appendTo("body");
el.css("position", "absolute")
.css("top", (event.pageY + yOffset) + "px")
.css("left", (event.pageX + xOffset) + "px")
.fadeIn("fast");
el.find("img").bind("load", function () {
el.find("i").remove();
}).attr("src", tUrl);
});
};
hoverTimeout = setTimeout(f, 250);
}
});
$(this.element).on("mouseleave", "span.fancytree-title", function () {
$("#fancytree-preview").remove();
if (hoverTimeout) {
clearTimeout(hoverTimeout);
hoverTimeout = null;
}
});
$(this.element).on("mousemove", "span.fancytree-title", function (event) {
var el = $("#fancytree-preview");
if (el.length > 0) {
el.css("top", (event.pageY + yOffset) + "px")
.css("left", (event.pageX + xOffset) + "px");
}
});
this.previewEnabled = true;
}
}