UNPKG

atheos-ide

Version:

Web-based IDE framework

749 lines (651 loc) 29.5 kB
//////////////////////////////////////////////////////////////////////80////////////80 // FileManager Init //////////////////////////////////////////////////////////////////////////////80 // Copyright (c) Atheos & Liam Siira (Atheos.io), distributed as-is and without // warranty under the MIT License. See [root]/LICENSE.md for more. // This information must remain intact. //////////////////////////////////////////////////////////////////////////////80 // Authors: Codiad Team, @Fluidbyte, Atheos Team, @hlsiira //////////////////////////////////////////////////////////////////////////////80 // Notes: // Goodness this file is very complex; it's going to take a very long time // to really get a grasp of what's going on in this file and how to // refactor it. // - Liam Siira //////////////////////////////////////////////////////////////////////80////////////80 (function(global) { 'use strict'; var atheos = global.atheos, carbon = global.carbon, echo = global.echo; var self = null; carbon.subscribe('system.loadMajor', () => atheos.filemanager.init()); atheos.filemanager = { clipboard: '', noOpen: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'exe', 'zip', 'tar', 'tar.gz'], noBrowser: ['jpg', 'jpeg', 'png', 'gif', 'bmp'], openTrigger: 'click', showHidden: true, init: function() { self = this; // Initialize node listener this.nodeListener(); let toggleHidden = oX('#fm_toggle_hidden'); toggleHidden.on('click', self.toggleHidden); carbon.subscribe('settings.loaded', function() { var local = atheos.storage('filemanager.openTrigger'); if (local === 'click' || local === 'dblclick') { self.openTrigger = local; } self.showHidden = atheos.storage('filemanager.showHidden') === false ? false : self.showHidden; if (self.showHidden === false) { toggleHidden.switchClass('fa-eye', 'fa-eye-slash'); } }); }, toggleHidden: function(e) { let toggle = oX('#fm_toggle_hidden'); toggle.switchClass('fa-eye', 'fa-eye-slash'); self.showHidden = toggle.hasClass('fa-eye') ? true : false; atheos.settings.save('filemanager.showHidden', self.showHidden, true); atheos.filemanager.rescan(); }, //////////////////////////////////////////////////////////////////////80 // Listen for click events on nodes //////////////////////////////////////////////////////////////////////80 nodeListener: function() { var checkAnchor = function(node) { node = oX(node); //////////////////////////////////////////////////////////////////////80 // This tagname business is due to the logical but annoying way // event delegation is handled. I keep trying to avoid organizing // the css in a better way for the file manager, and this is the // result. //////////////////////////////////////////////////////////////////////80 if (node.tagName === 'UL') { return false; } else if (node.tagName !== 'A') { if (node.tagName === 'LI') { node = node.find('a'); } else { node = node.parent('a'); } } return node; }; oX('#file-manager a', true).on('click, dblclick', function(e) { if (self.openTrigger === e.type) { var node = checkAnchor(e.target); if (node.attr('data-type') === 'directory' || node.attr('data-type') === 'root') { self.openDir(node.attr('data-path')); } else if (node.attr('data-type') === 'file') { self.openFile(node.attr('data-path')); } } }); // oX('#file-manager').on('mousedown', function(e) { // var options = { // dragZone: oX('#file-manager').el, // direction: 'vertical', // drop: self.handleDrop // }; // atheos.flow.dragNdrop(e, options); // }); }, handleDrop: function(node) { //Check for duplicates // repathChildren and node node = oX(node); var parent = node.parent().siblings('a[data-path]')[0]; node = node.find('a[data-path]'); var oldPath = node.attr('data-path'); var parPath = parent.attr('data-path'); var basename = pathinfo(oldPath).basename; var newPath = parPath + '/' + basename; if (oX('#file-manager a[data-path="' + newPath + '"]')) { atheos.toast.show('warning', 'Path already exists.'); return false; } else { echo({ url: atheos.controller, data: { target: 'filemanager', action: 'move', oldPath, newPath }, success: function(reply) { log(reply); } }); return true; } }, //////////////////////////////////////////////////////////////////////80 // Loop out all files and folders in directory path //////////////////////////////////////////////////////////////////////80 openDir: function(path, rescan) { var slideDuration = 200; rescan = rescan || false; var node = oX('#file-manager a[data-path="' + CSS.escape(path) + '"]'); let icon = node.find('.expand'); if (node.hasClass('open') && !rescan) { node.removeClass('open'); var list = node.siblings('ul')[0]; if (list) { atheos.flow.slide('close', list.el, slideDuration); if (icon) { icon.replaceClass('fa-minus', 'fa-plus'); } setTimeout(function() { list.remove(); }, slideDuration); } } else { if (icon) { icon.addClass('loading'); } echo({ url: atheos.controller, data: { target: 'filemanager', action: 'index', path }, success: function(reply) { if (reply.status !== 'error') { /* Notify listener */ var files = reply.index; if (files && files.length > 0) { if (icon) { icon.replaceClass('fa-plus', 'fa-minus'); } var display = ' style="display:none;"'; if (rescan) { display = ''; } var appendage = '<ul' + display + '>'; files.forEach(function(file) { appendage += self.createDirectoryItem(file.path, file.type, file.size, file.repo); if (pathinfo(file.path).basename === '.git') { atheos.codegit.addRepoIcon(path); } }); appendage += '</ul>'; if (rescan && node.siblings('ul')[0]) { node.siblings('ul')[0].remove(); } node.after(appendage); var list = node.siblings('ul')[0]; if (!rescan && list) { atheos.flow.slide('open', list.el, slideDuration); } } carbon.publish('filemanager.openDir', { files: files, node: node, path: path }); } node.addClass('open'); if (icon) { icon.removeClass('loading'); icon.replaceClass('fa-plus', 'fa-minus'); } if (rescan && self.rescanChildren.length > self.rescanCounter) { self.rescan(self.rescanChildren[self.rescanCounter++]); } else { self.rescanChildren = []; self.rescanCounter = 0; } } }); } }, //////////////////////////////////////////////////////////////////////80 // Create node in file tree //////////////////////////////////////////////////////////////////////80 createDirectoryItem: function(path, type, size, repo) { var basename = pathinfo(path).basename; if (self.showHidden === false && basename.charAt(0) === '.') { return ''; } var fileClass = type === 'directory' ? 'fa fa-folder blue' : icons.getClassWithColor(basename); var nodeClass = 'none'; if (type === 'directory' && (size > 0)) { nodeClass = 'fa fa-plus'; } fileClass = fileClass || 'fa fa-file green'; var repoIcon = repo ? '<i class="repo-icon fas fa-code-branch"></i>' : ''; var repoClass = repo ? ' class="repo"' : ''; return `<li class="draggable"> <a data-type="${type}" data-path="${path}"${repoClass}> <i class="expand ${nodeClass}"></i> <i class="${fileClass}"></i> ${repoIcon} <span>${basename}</span> </a> </li>`; }, rescanChildren: [], rescanCounter: 0, rescan: function(path) { path = path || oX('#project-root').attr('data-path'); if (self.rescanCounter === 0) { var list = oX('#file-manager a[data-path="' + path + '"]').siblings('ul')[0]; var openNodes = list.findAll('a.open'); for (var i = 0; i < openNodes.length; i++) { self.rescanChildren.push(openNodes[i].attr('data-path')); } } self.openDir(path, true); }, //////////////////////////////////////////////////////////////////////80 // Open File //////////////////////////////////////////////////////////////////////80 openFile: function(path, focus, line) { focus = typeof(focus) !== 'undefined' ? focus : true; var ext = pathinfo(path).extension.toLowerCase(); if (self.noOpen.indexOf(ext) < 0) { echo({ url: atheos.controller, data: { target: 'filemanager', action: 'open', path: path }, success: function(reply) { if (reply.status !== 'error') { atheos.active.open(path, reply.content, reply.modifyTime, focus); if (line) { setTimeout(atheos.editor.gotoLine(line), 500); } } } }); } else { if (!atheos.common.isAbsPath(path)) { if (self.noBrowser.indexOf(ext) < 0) { self.download(path); } else { self.openInModal(path); } } else { atheos.toast.show('error', 'Unable to open file in Browser'); } } }, //////////////////////////////////////////////////////////////////////80 // Save file // I'm pretty sure the save methods on this are magic and should // be worshipped. //////////////////////////////////////////////////////////////////////80 saveModifications: function(path, data, callbacks) { callbacks = callbacks || {}; data.target = 'filemanager'; data.action = 'save'; data.path = path; echo({ url: atheos.controller, data: data, success: function(data) { var context; if (data.status === 'success') { atheos.toast.show('success', 'File saved'); if (typeof callbacks.success === 'function') { context = callbacks.context || self; callbacks.success.call(context, data.modifyTime); } } else { if (data.text === 'out of sync') { atheos.alert.show({ banner: 'File changed on server!', message: 'Would you like to load the updated file?\n' + 'Pressing no will cause your changes to override the\n' + 'server\'s copy upon next save.', actions: { 'Reload File': function() { atheos.active.remove(path); self.openFile(path); }, 'Save Anyway': function() { var session = atheos.editor.getActive().getSession(); session.serverMTime = null; session.untainted = null; atheos.active.save(); } } }); } else { atheos.toast.show('error', 'File could not be saved'); } if (typeof callbacks.error === 'function') { context = callbacks.context || self; callbacks.error.apply(context, [data.data]); } } } }); }, saveFile: function(path, content, callbacks) { self.saveModifications(path, { content: content }, callbacks); }, savePatch: function(path, patch, mtime, callbacks) { if (patch.length > 0) { self.saveModifications(path, { patch: patch, modifyTime: mtime }, callbacks); } else if (typeof callbacks.success === 'function') { var context = callbacks.context || self; callbacks.success.call(context, mtime); } }, //////////////////////////////////////////////////////////////////////80 // Copy to Clipboard //////////////////////////////////////////////////////////////////////80 copy: function(path) { self.clipboard = path; atheos.toast.show('success', 'Copied to Clipboard'); }, //////////////////////////////////////////////////////////////////////80 // Paste //////////////////////////////////////////////////////////////////////80 paste: function(path) { var split = pathinfo(self.clipboard); var copy = split.basename; var type = split.type; var processPaste = function(path, duplicate) { if (duplicate) { copy = 'copy_' + copy; } echo({ url: atheos.controller, data: { target: 'filemanager', action: 'duplicate', path: self.clipboard, dest: path + '/' + copy }, success: function(reply) { if (reply.status !== 'error') { self.addToFileManager(path + '/' + copy, type, path); /* Notify listeners. */ carbon.publish('filemanager.paste', { path: path, dest: copy }); } } }); }; if (self.clipboard === '') { atheos.toast.show('error', 'Nothing in Your Clipboard'); } else if (path === self.clipboard) { atheos.toast.show('error', 'Cannot Paste Directory Into Itself'); } else if (oX('#file-manager a[data-path="' + path + '/' + copy + '"]')) { atheos.alert.show({ banner: 'Path already exists!', message: 'Would you like to overwrite or duplicate the file?', data: `${path}/${copy}`, actions: { 'Overwrite': function() { processPaste(path, false); }, 'Duplicate': function() { processPaste(path, true); } } }); } else { processPaste(path, false); } }, //////////////////////////////////////////////////////////////////////80 // Duplicate Object //////////////////////////////////////////////////////////////////////80 duplicate: function(path) { var split = pathinfo(path); var name = split.basename; var type = split.type; var listener = function(e) { e.preventDefault(); var clone = oX('#modal_content form input[name="clone"]').value(); // Build new path var parent = path.split('/').slice(0, -1).join('/'); var clonePath = parent + '/' + clone; echo({ url: atheos.controller, data: { target: 'filemanager', action: 'duplicate', path: path, dest: clonePath }, success: function(reply) { atheos.toast.show(reply); if (reply.status === 'success') { self.addToFileManager(clonePath, type, parent); atheos.modal.unload(); /* Notify listeners. */ carbon.publish('filemanager.duplicate', { sourcePath: path, clonePath: clonePath, type: type }); } } }); atheos.modal.unload(); }; atheos.modal.load(250, { target: 'filemanager', action: 'duplicate', name: name, type: type, listener }); }, //////////////////////////////////////////////////////////////////////80 // Create new node //////////////////////////////////////////////////////////////////////80 create: function(path, type) { var listener = function(e) { e.preventDefault(); var nodeName = oX('#modal_content form input[name="nodeName"]').value(); var newPath = path + '/' + nodeName; echo({ url: atheos.controller, data: { target: 'filemanager', action: 'create', path: newPath, type: type }, success: function(reply) { if (reply.status !== 'error') { atheos.toast.show('success', 'File Created'); atheos.modal.unload(); // Add new element to filemanager screen self.addToFileManager(newPath, type, path); if (type === 'file') { self.openFile(newPath, true); } /* Notify listeners. */ carbon.publish('filemanager.onCreate', { createPath: newPath, path: path, nodeName: nodeName, type: type }); } } }); }; atheos.modal.load(250, { target: 'filemanager', action: 'create', type: type, listener }); }, //////////////////////////////////////////////////////////////////////80 // Create node in file tree //////////////////////////////////////////////////////////////////////80 addToFileManager: function(path, type, parent) { var parentNode = oX('#file-manager a[data-path="' + parent + '"]'); if (!oX('#file-manager a[data-path="' + path + '"]')) { // Doesn't already exist if (parentNode.hasClass('open') && parentNode.attr('data-type').match(/^(directory|root)$/)) { // Only append node if parent is open (and a directory) var node = self.createDirectoryItem(path, type, 0); var list = parentNode.siblings('ul')[0]; if (list) { // UL exists, other children to play with list.append(node); self.sortNodes(list.el); } else { list = oX('<ul>'); list.append(node); parentNode.append(list.el); } } else { if (parentNode.find('.expand')) { parentNode.find('.expand').replaceClass('none', 'fa fa-plus'); } } } }, //////////////////////////////////////////////////////////////////////80 // Sort nodes in file tree during node creation //////////////////////////////////////////////////////////////////////80 sortNodes: function(list) { var nodesToSort = list.children; nodesToSort = Array.prototype.map.call(nodesToSort, function(node) { return { node: node, span: node.querySelector('span').textContent, type: node.querySelector('a').getAttribute('data-type') }; }); nodesToSort.sort(function(a, b) { return a.span.localeCompare(b.span); }); nodesToSort.sort(function(a, b) { return a.type.localeCompare(b.type); }); nodesToSort.forEach(function(item) { list.appendChild(item.node); }); }, //////////////////////////////////////////////////////////////////////80 // Rename //////////////////////////////////////////////////////////////////////80 rename: function(path) { var split = pathinfo(path); var nodeName = split.basename; var type = split.type; var listener = function(e) { e.preventDefault(); var newName = oX('#modal_content form input[name="name"]').value(); // Build new path var arr = path.split('/'); var temp = []; for (var i = 0; i < arr.length - 1; i++) { temp.push(arr[i]); } var newPath = temp.join('/') + '/' + newName; echo({ url: atheos.controller, data: { target: 'filemanager', action: 'rename', path: path, name: newName }, success: function(reply) { if (reply.status === 'success') { if (type === 'file') { atheos.toast.show('success', 'File Renamed.'); } else { atheos.toast.show('success', 'Folder Renamed.'); } var node = oX('#file-manager a[data-path="' + path + '"]'), icon = node.find('i:nth-child(2)'), span = node.find('span'); // Change pathing and name for node node.attr('data-path', newPath); span.text(newName); if (type === 'file') { // Change icons for file icon.removeClass(); var ico = icons.getClassWithColor(newName); if (ico) { icon.addClass(icons.getClassWithColor(newName)); } } else { // Change pathing on any sub-files/directories self.repathChildren(path, newPath); } // Change any active files atheos.active.rename(path, newPath); } } }); atheos.modal.unload(); }; atheos.modal.load(250, { target: 'filemanager', action: 'rename', name: nodeName, type: type, listener }); }, repathChildren: function(oldPath, newPath) { var node = oX('#file-manager a[data-path="' + newPath + '"]'), ul = node.el.nextElementSibling; if (ul) { var children = ul.querySelectorAll('a'); for (var i = 0; i < children.length; i++) { var path = children[i].getAttribute('data-path'); path = path.replace(oldPath, newPath); children[i].setAttribute('data-path', path); } } }, //////////////////////////////////////////////////////////////////////80 // Delete //////////////////////////////////////////////////////////////////////80 delete: function(path) { atheos.alert.show({ message: 'Are you sure you wish to delete the following:', data: pathinfo(path).basename, actions: { 'Delete': function() { echo({ url: atheos.controller, data: { target: 'filemanager', action: 'delete', path }, success: function(reply) { if (reply.status === 'success') { var node = oX('#file-manager a[data-path="' + path + '"]'); node.parent('li').remove(); // Close any active files atheos.active.remove(path); } } }); }, 'Cancel': function() {} } }); } }; })(this);