diff --git a/src/components/filetree.html b/src/components/filetree.html index 321fc1bd0..a1c07bdd4 100644 --- a/src/components/filetree.html +++ b/src/components/filetree.html @@ -1,24 +1,4 @@ - - - - - -
diff --git a/src/components/filetree.js b/src/components/filetree.js index 819cdbd27..13f4fd3f7 100644 --- a/src/components/filetree.js +++ b/src/components/filetree.js @@ -1,19 +1,24 @@ 'use strict'; /* global angular */ -/* global sanitize */ +/* global sanitize, isModalVisible */ angular.module('Application').component('filetree', { bindings: { backendId: '<', backendType: '<', - view: '<' + view: '<', + onUploadFile: '&', + onUploadFolder: '&', + onDeleteEntries: '&' }, templateUrl: 'components/filetree.html?<%= revision %>', controller: [ '$scope', '$translate', '$timeout', 'Client', FileTreeController ] }); function FileTreeController($scope, $translate, $timeout, Client) { + var ctrl = this; + $scope.backendId = this.backendId; $scope.backendType = this.backendType; $scope.view = this.view; @@ -30,6 +35,9 @@ function FileTreeController($scope, $translate, $timeout, Client) { $scope.dropToBody = false; $scope.applicationLink = ''; + // register so parent can call child + $scope.$parent.registerChild($scope); + $scope.owners = [ { name: 'cloudron', value: 1000 }, { name: 'www-data', value: 33 }, @@ -305,6 +313,10 @@ function FileTreeController($scope, $translate, $timeout, Client) { }; $scope.refresh = function () { + $scope.$parent.refresh(); + }; + + $scope.onRefresh = function () { $scope.selected = []; Client.filesGet($scope.backendId, $scope.backendType, $scope.cwd, 'data', function (error, result) { @@ -495,101 +507,9 @@ function FileTreeController($scope, $translate, $timeout, Client) { $scope.selected = $scope.entries.slice(); }; - $scope.uploadStatus = { - error: null, - busy: false, - fileName: '', - count: 0, - countDone: 0, - size: 0, - done: 0, - percentDone: 0, - files: [], - targetFolder: '' - }; - - function uploadFiles(files, targetFolder, overwrite) { - if (!files || !files.length) return; - - targetFolder = targetFolder || $scope.cwd; - overwrite = !!overwrite; - - // prevent it from getting closed - $('#uploadModal').modal({ - backdrop: 'static', - keyboard: false - }); - - $scope.uploadStatus.files = files; - $scope.uploadStatus.targetFolder = targetFolder; - $scope.uploadStatus.error = null; - $scope.uploadStatus.busy = true; - $scope.uploadStatus.count = files.length; - $scope.uploadStatus.countDone = 0; - $scope.uploadStatus.size = 0; - $scope.uploadStatus.sizeDone = 0; - $scope.uploadStatus.done = 0; - $scope.uploadStatus.percentDone = 0; - - for (var i = 0; i < files.length; ++i) { - $scope.uploadStatus.size += files[i].size; - } - - async.eachSeries(files, function (file, callback) { - var filePath = sanitize(targetFolder + '/' + (file.webkitRelativePath || file.name)); - - $scope.uploadStatus.fileName = file.name; - - Client.filesUpload($scope.backendId, $scope.backendType, filePath, file, overwrite, function (loaded) { - $scope.uploadStatus.percentDone = ($scope.uploadStatus.done+loaded) * 100 / $scope.uploadStatus.size; - $scope.uploadStatus.sizeDone = loaded; - }, function (error) { - if (error) return callback(error); - - $scope.uploadStatus.done += file.size; - $scope.uploadStatus.percentDone = $scope.uploadStatus.done * 100 / $scope.uploadStatus.size; - $scope.uploadStatus.countDone++; - - callback(); - }); - }, function (error) { - $scope.uploadStatus.busy = false; - - if (error && error.statusCode === 409) { - $scope.uploadStatus.error = 'exists'; - return; - } else if (error) { - console.error(error); - $scope.uploadStatus.error = 'generic'; - return; - } - - $('#uploadModal').modal('hide'); - - $scope.uploadStatus.fileName = ''; - $scope.uploadStatus.count = 0; - $scope.uploadStatus.size = 0; - $scope.uploadStatus.sizeDone = 0; - $scope.uploadStatus.done = 0; - $scope.uploadStatus.percentDone = 100; - $scope.uploadStatus.files = []; - $scope.uploadStatus.targetFolder = ''; - - $scope.refresh(); - }); - } - - $scope.retryUpload = function (overwrite) { - uploadFiles($scope.uploadStatus.files, $scope.uploadStatus.targetFolder, !!overwrite); - }; - - // file upload - $('#uploadFileInput').on('change', function (e) { uploadFiles(e.target.files || [], $scope.cwd, false); }); - $scope.onUploadFile = function () { $('#uploadFileInput').click(); }; - - // folder upload - $('#uploadFolderInput').on('change', function (e ) { uploadFiles(e.target.files || [], $scope.cwd, false); }); - $scope.onUploadFolder = function () { $('#uploadFolderInput').click(); }; + // just events to the parent controller + $scope.onUploadFile = function () { ctrl.onUploadFile({ cwd: $scope.cwd }); }; + $scope.onUploadFolder = function () { ctrl.onUploadFolder({ cwd: $scope.cwd }); }; $scope.restartBusy = false; $scope.onRestartApp = function () { @@ -799,35 +719,6 @@ function FileTreeController($scope, $translate, $timeout, Client) { } }; - $scope.entryRemove = { - busy: false, - error: null, - - show: function () { - $scope.entryRemove.error = null; - - $('#entryRemoveModal-' + $scope.$id).modal('show'); - }, - - submit: function () { - $scope.entryRemove.busy = true; - - async.eachLimit($scope.selected, 5, function (entry, callback) { - var filePath = sanitize($scope.cwd + '/' + entry.fileName); - - Client.filesRemove($scope.backendId, $scope.backendType, filePath, callback); - }, function (error) { - $scope.entryRemove.busy = false; - if (error) return Client.error(error); - - $scope.refresh(); - - $('#entryRemoveModal-' + $scope.$id).modal('hide'); - }); - - } - }; - $translate(['filemanager.list.menu.edit', 'filemanager.list.menu.cut', 'filemanager.list.menu.copy', 'filemanager.list.menu.paste', 'filemanager.list.menu.rename', 'filemanager.list.menu.chown', 'filemanager.list.menu.extract', 'filemanager.list.menu.download', 'filemanager.list.menu.delete' ]).then(function (tr) { $scope.menuOptions = [ { @@ -865,7 +756,7 @@ function FileTreeController($scope, $translate, $timeout, Client) { }, { text: tr['filemanager.list.menu.delete'], hasTopDivider: true, - click: function ($itemScope, $event, entry) { $scope.entryRemove.show(); } + click: function ($itemScope, $event, entry) { ctrl.onDeleteEntries({ cwd: $scope.cwd, entries: $scope.selected }); } } ]; }); @@ -891,27 +782,6 @@ function FileTreeController($scope, $translate, $timeout, Client) { ]; }); - $('.file-list').on('scroll', function (event) { - if (event.target.scrollTop > 10) event.target.classList.add('top-scroll-indicator'); - else event.target.classList.remove('top-scroll-indicator'); - }); - - // setup all the dialog focus handling - ['newFileModal', 'newDirectoryModal', 'renameEntryModal'].forEach(function (id) { - $('#' + id).on('shown.bs.modal', function () { - $(this).find('[autofocus]:first').focus(); - }); - }); - - // selects filename (without extension) - ['renameEntryModal'].forEach(function (id) { - $('#' + id).on('shown.bs.modal', function () { - var elem = $(this).find('[autofocus]:first'); - var text = elem.val(); - elem[0].setSelectionRange(0, text.indexOf('.')); - }); - }); - function scrollInView(element) { if (!element) return; @@ -970,6 +840,30 @@ function FileTreeController($scope, $translate, $timeout, Client) { openPath('.'); + // DOM handlers, wait for elements to exist + setTimeout(function () { + $('.file-list').on('scroll', function (event) { + if (event.target.scrollTop > 10) event.target.classList.add('top-scroll-indicator'); + else event.target.classList.remove('top-scroll-indicator'); + }); + + // setup all the dialog focus handling + ['newFileModal', 'newDirectoryModal', 'renameEntryModal'].forEach(function (id) { + $('#' + id + '-' + $scope.$id).on('shown.bs.modal', function () { + $(this).find('[autofocus]:first').focus(); + }); + }); + + // selects filename (without extension) + ['renameEntryModal'].forEach(function (id) { + $('#' + id + '-' + $scope.$id).on('shown.bs.modal', function () { + var elem = $(this).find('[autofocus]:first'); + var text = elem.val(); + elem[0].setSelectionRange(0, text.indexOf('.')); + }); + }); + }, 0); + // handle save shortcuts window.addEventListener('keydown', function (event) { if ($scope.$parent.activeView !== $scope.view || $scope.$parent.viewerOpen || isModalVisible()) return; diff --git a/src/filemanager.html b/src/filemanager.html index fb2f29200..40b20be31 100644 --- a/src/filemanager.html +++ b/src/filemanager.html @@ -75,6 +75,44 @@
{{ 'filemanager.status.restartingApp' | tr}}
+ + + + +
- - -
@@ -155,9 +174,19 @@
- + - +
diff --git a/src/js/filemanager.js b/src/js/filemanager.js index 3be90e081..4474692ee 100644 --- a/src/js/filemanager.js +++ b/src/js/filemanager.js @@ -82,6 +82,7 @@ var VIEW = { app.controller('FileManagerController', ['$scope', '$translate', '$timeout', 'Client', function ($scope, $translate, $timeout, Client) { var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {}); + // expose enums $scope.VIEW = VIEW; $scope.initialized = false; @@ -91,24 +92,189 @@ app.controller('FileManagerController', ['$scope', '$translate', '$timeout', 'Cl $scope.backendId = search.id; $scope.backendType = search.type; $scope.volumes = []; - $scope.splitView = false; + $scope.splitView = !!window.localStorage.splitView; $scope.activeView = VIEW.LEFT; $scope.viewerOpen = false; + + // add a hook for children to refresh both tree views + + $scope.children = []; + $scope.registerChild = function (child) { $scope.children.push(child); }; + $scope.refresh = function () { + $scope.children.forEach(function (child) { + child.onRefresh(); + }); + }; + + // handle uploads + + $scope.uploadStatus = { + error: null, + busy: false, + fileName: '', + count: 0, + countDone: 0, + size: 0, + done: 0, + percentDone: 0, + files: [], + targetFolder: '' + }; + + function uploadFiles(files, targetFolder, overwrite) { + if (!files || !files.length) return; + + targetFolder = targetFolder || $scope.cwd; + overwrite = !!overwrite; + + // prevent it from getting closed + $('#uploadModal').modal({ + backdrop: 'static', + keyboard: false + }); + + $scope.uploadStatus.files = files; + $scope.uploadStatus.targetFolder = targetFolder; + $scope.uploadStatus.error = null; + $scope.uploadStatus.busy = true; + $scope.uploadStatus.count = files.length; + $scope.uploadStatus.countDone = 0; + $scope.uploadStatus.size = 0; + $scope.uploadStatus.sizeDone = 0; + $scope.uploadStatus.done = 0; + $scope.uploadStatus.percentDone = 0; + + for (var i = 0; i < files.length; ++i) { + $scope.uploadStatus.size += files[i].size; + } + + async.eachSeries(files, function (file, callback) { + var filePath = sanitize(targetFolder + '/' + (file.webkitRelativePath || file.name)); + + $scope.uploadStatus.fileName = file.name; + + Client.filesUpload($scope.backendId, $scope.backendType, filePath, file, overwrite, function (loaded) { + $scope.uploadStatus.percentDone = ($scope.uploadStatus.done+loaded) * 100 / $scope.uploadStatus.size; + $scope.uploadStatus.sizeDone = loaded; + }, function (error) { + if (error) return callback(error); + + $scope.uploadStatus.done += file.size; + $scope.uploadStatus.percentDone = $scope.uploadStatus.done * 100 / $scope.uploadStatus.size; + $scope.uploadStatus.countDone++; + + callback(); + }); + }, function (error) { + $scope.uploadStatus.busy = false; + + if (error && error.statusCode === 409) { + $scope.uploadStatus.error = 'exists'; + return; + } else if (error) { + console.error(error); + $scope.uploadStatus.error = 'generic'; + return; + } + + $('#uploadModal').modal('hide'); + + $scope.uploadStatus.fileName = ''; + $scope.uploadStatus.count = 0; + $scope.uploadStatus.size = 0; + $scope.uploadStatus.sizeDone = 0; + $scope.uploadStatus.done = 0; + $scope.uploadStatus.percentDone = 100; + $scope.uploadStatus.files = []; + $scope.uploadStatus.targetFolder = ''; + + $scope.refresh(); + }); + } + + $scope.retryUpload = function (overwrite) { + uploadFiles($scope.uploadStatus.files, $scope.uploadStatus.targetFolder, !!overwrite); + }; + + + // file and folder upload hooks, stashing $scope.uploadCwd for now + + $scope.uploadCwd = ''; + $('#uploadFileInput').on('change', function (e ) { + uploadFiles(e.target.files || [], $scope.uploadCwd, false); + }); + $scope.onUploadFile = function (cwd) { + $scope.uploadCwd = cwd; + $('#uploadFileInput').click(); + }; + + $('#uploadFolderInput').on('change', function (e ) { + uploadFiles(e.target.files || [], $scope.uploadCwd, false); + }); + $scope.onUploadFolder = function (cwd) { + $scope.uploadCwd = cwd; + $('#uploadFolderInput').click(); + }; + + + // handle delete + + $scope.deleteEntries = { + busy: false, + error: null, + cwd: '', + entries: [], + + show: function (cwd, entries) { + $scope.deleteEntries.error = null; + $scope.deleteEntries.cwd = cwd; + $scope.deleteEntries.entries = entries; + + $('#entriesDeleteModal').modal('show'); + }, + + submit: function () { + $scope.deleteEntries.busy = true; + + async.eachLimit($scope.deleteEntries.entries, 5, function (entry, callback) { + var filePath = sanitize($scope.deleteEntries.cwd + '/' + entry.fileName); + + Client.filesRemove($scope.backendId, $scope.backendType, filePath, callback); + }, function (error) { + $scope.deleteEntries.busy = false; + if (error) return Client.error(error); + + $scope.refresh(); + + $('#entriesDeleteModal').modal('hide'); + }); + + } + }; + + + // split view handling + $scope.toggleSplitView = function () { $scope.splitView = !$scope.splitView; - if (!$scope.splitView) $scope.activeView = VIEW.LEFT; + if (!$scope.splitView) { + $scope.activeView = VIEW.LEFT; + delete window.localStorage.splitView; + } else { + window.localStorage.splitView = true; + } }; $scope.setActiveView = function (view) { $scope.activeView = view; }; - // for monaco editor + + // monaco text editor + var LANGUAGES = []; - require(['vs/editor/editor.main'], function() { - LANGUAGES = monaco.languages.getLanguages(); - }); + require(['vs/editor/editor.main'], function() { LANGUAGES = monaco.languages.getLanguages(); }); function getLanguage(filename) { var ext = '.' + filename.split('.').pop(); @@ -207,6 +373,9 @@ app.controller('FileManagerController', ['$scope', '$translate', '$timeout', 'Cl }, }; + + // init code + function fetchVolumesInfo(mounts) { $scope.volumes = []; @@ -299,7 +468,9 @@ app.controller('FileManagerController', ['$scope', '$translate', '$timeout', 'Cl init(); - // handle save shortcuts + + // toplevel key input handling + window.addEventListener('keydown', function (event) { if((navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey) && event.key === 's') { if ($scope.view !== 'textEditor') return;