Add drag'n'drop to filemanager

This commit is contained in:
Johannes Zellner
2020-07-13 15:41:10 +02:00
parent 6b64dd52b9
commit da54699815
3 changed files with 132 additions and 6 deletions

View File

@@ -53,7 +53,7 @@
</head>
<body class="filemanager">
<body class="filemanager" ng-drop="drop($event)" ng-dragover="dragEnter($event)" ng-dragexit="dragExit($event)">
<a class="offline-banner animateMe" ng-show="client.offline" ng-cloak href="https://cloudron.io/documentation/troubleshooting/" target="_blank"><i class="fa fa-circle-notch fa-spin"></i> Cloudron is offline. Reconnecting...</a>
@@ -181,7 +181,7 @@
<br/>
<table class="table table-hover" style="margin: 0;">
<table class="table table-hover" style="margin: 0;" ng-class="{ 'entry-hovered': dropToBody }">
<thead>
<tr>
<th style="width: 3%;">&nbsp;</th>
@@ -195,7 +195,7 @@
<tr ng-show="entries.length === 0">
<td colspan="5" class="text-center">No files</td>
</tr>
<tr ng-repeat="entry in entries">
<tr ng-repeat="entry in entries" ng-drop="drop($event, entry)" ng-dragexit="dragExit($event, entry)" ng-dragover="dragEnter($event, entry)" ng-class="{ 'entry-hovered': entry.hovered }">
<td ng-click="open(entry)" class="hand text-center">
<i class="fas fa-folder fa-lg" ng-show="entry.isDirectory"></i>
<i class="far fa-file fa-lg" ng-show="entry.isFile"></i>

View File

@@ -3,7 +3,7 @@
/* global angular, $, async */
// create main application module
var app = angular.module('Application', ['angular-md5', 'ui-notification']);
var app = angular.module('Application', ['angular-md5', 'ui-notification', 'ngDrag']);
angular.module('Application').filter('prettyOwner', function () {
return function (uid) {
@@ -21,6 +21,35 @@ app.config(function ($sceProvider) {
$sceProvider.enabled(false);
});
// https://stackoverflow.com/questions/25621321/angularjs-ng-drag
var ngDragEventDirectives = {};
angular.forEach(
'drag dragend dragenter dragexit dragleave dragover dragstart drop'.split(' '),
function(eventName) {
var directiveName = 'ng' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
ngDragEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
return {
restrict: 'A',
compile: function($element, attr) {
var fn = $parse(attr[directiveName], null, true);
return function ngDragEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
fn(scope, {$event: event});
};
scope.$apply(callback);
});
};
}
};
}];
}
);
angular.module('ngDrag', []).directive(ngDragEventDirectives);
app.controller('FileManagerController', ['$scope', 'Client', function ($scope, 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; }, {});
@@ -44,6 +73,80 @@ app.controller('FileManagerController', ['$scope', 'Client', function ($scope, C
return '/' + filePath;
}
$scope.dropToBody = false;
$scope.dragEnter = function ($event, entry) {
$event.originalEvent.stopPropagation();
$event.originalEvent.preventDefault();
if (entry && entry.isDirectory) entry.hovered = true;
else $scope.dropToBody = true;
$event.originalEvent.dataTransfer.dropEffect = 'copy';
}
$scope.dragExit = function ($event, entry) {
$event.originalEvent.stopPropagation();
$event.originalEvent.preventDefault();
if (entry && entry.isDirectory) entry.hovered = false;
$scope.dropToBody = false;
$event.originalEvent.dataTransfer.dropEffect = 'copy';
}
$scope.drop = function (event, entry) {
event.originalEvent.stopPropagation();
event.originalEvent.preventDefault();
if (!event.originalEvent.dataTransfer.items[0]) return;
var targetFolder = entry && entry.isDirectory ? entry.fileName : '';
// figure if a folder was dropped on a modern browser, in this case the first would have to be a directory
var folderItem;
try {
folderItem = event.originalEvent.dataTransfer.items[0].webkitGetAsEntry();
if (folderItem.isFile) return uploadFiles(event.originalEvent.dataTransfer.files, targetFolder);
} catch (e) {
return uploadFiles(event.originalEvent.dataTransfer.files, targetFolder);
}
// if we got here we have a folder drop and a modern browser
// now traverse the folder tree and create a file list
$scope.uploadStatus.busy = true;
$scope.uploadStatus.count = 0;
var fileList = [];
function traverseFileTree(item, path, callback) {
if (item.isFile) {
// Get file
item.file(function (file) {
fileList.push(file);
++$scope.uploadStatus.count;
callback();
});
} else if (item.isDirectory) {
// Get folder contents
var dirReader = item.createReader();
dirReader.readEntries(function (entries) {
async.each(entries, function (entry, callback) {
traverseFileTree(entry, path + item.name + '/', callback);
}, callback);
});
}
}
traverseFileTree(folderItem, '', function (error) {
$scope.uploadStatus.busy = false;
$scope.uploadStatus.count = 0;
if (error) return console.error(error);
uploadFiles(fileList, targetFolder);
});
}
$scope.refresh = function () {
Client.filesGet($scope.appId, $scope.cwd, function (error, result) {
if (error) return Client.error(error);
@@ -102,9 +205,11 @@ app.controller('FileManagerController', ['$scope', 'Client', function ($scope, C
percentDone: 0
};
function uploadFiles(files) {
function uploadFiles(files, targetFolder) {
if (!files || !files.length) return;
targetFolder = targetFolder || '';
// prevent it from getting closed
$('#uploadModal').modal({
backdrop: 'static',
@@ -123,7 +228,7 @@ app.controller('FileManagerController', ['$scope', 'Client', function ($scope, C
}
async.eachSeries(files, function (file, callback) {
var filePath = sanitize($scope.cwd + '/' + (file.webkitRelativePath || file.name));
var filePath = sanitize($scope.cwd + '/' + targetFolder + '/' + (file.webkitRelativePath || file.name));
$scope.uploadStatus.fileName = file.name;

View File

@@ -1681,6 +1681,27 @@ tag-input {
}
// ----------------------------
// FileManager
// ----------------------------
.filemanager {
// those are for drag'n'drop
table {
border: 2px solid transparent;
}
.entry-hovered {
border: 2px solid $brand-primary;
}
.entry-hovered > td {
border: none !important;
}
}
// ----------------------------
// Dark mode overrides
// ----------------------------