Add drag'n'drop to filemanager
This commit is contained in:
@@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
</head>
|
</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>
|
<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/>
|
<br/>
|
||||||
|
|
||||||
<table class="table table-hover" style="margin: 0;">
|
<table class="table table-hover" style="margin: 0;" ng-class="{ 'entry-hovered': dropToBody }">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 3%;"> </th>
|
<th style="width: 3%;"> </th>
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
<tr ng-show="entries.length === 0">
|
<tr ng-show="entries.length === 0">
|
||||||
<td colspan="5" class="text-center">No files</td>
|
<td colspan="5" class="text-center">No files</td>
|
||||||
</tr>
|
</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">
|
<td ng-click="open(entry)" class="hand text-center">
|
||||||
<i class="fas fa-folder fa-lg" ng-show="entry.isDirectory"></i>
|
<i class="fas fa-folder fa-lg" ng-show="entry.isDirectory"></i>
|
||||||
<i class="far fa-file fa-lg" ng-show="entry.isFile"></i>
|
<i class="far fa-file fa-lg" ng-show="entry.isFile"></i>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
/* global angular, $, async */
|
/* global angular, $, async */
|
||||||
|
|
||||||
// create main application module
|
// 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 () {
|
angular.module('Application').filter('prettyOwner', function () {
|
||||||
return function (uid) {
|
return function (uid) {
|
||||||
@@ -21,6 +21,35 @@ app.config(function ($sceProvider) {
|
|||||||
$sceProvider.enabled(false);
|
$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) {
|
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; }, {});
|
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;
|
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 () {
|
$scope.refresh = function () {
|
||||||
Client.filesGet($scope.appId, $scope.cwd, function (error, result) {
|
Client.filesGet($scope.appId, $scope.cwd, function (error, result) {
|
||||||
if (error) return Client.error(error);
|
if (error) return Client.error(error);
|
||||||
@@ -102,9 +205,11 @@ app.controller('FileManagerController', ['$scope', 'Client', function ($scope, C
|
|||||||
percentDone: 0
|
percentDone: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
function uploadFiles(files) {
|
function uploadFiles(files, targetFolder) {
|
||||||
if (!files || !files.length) return;
|
if (!files || !files.length) return;
|
||||||
|
|
||||||
|
targetFolder = targetFolder || '';
|
||||||
|
|
||||||
// prevent it from getting closed
|
// prevent it from getting closed
|
||||||
$('#uploadModal').modal({
|
$('#uploadModal').modal({
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
@@ -123,7 +228,7 @@ app.controller('FileManagerController', ['$scope', 'Client', function ($scope, C
|
|||||||
}
|
}
|
||||||
|
|
||||||
async.eachSeries(files, function (file, callback) {
|
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;
|
$scope.uploadStatus.fileName = file.name;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// Dark mode overrides
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user