Files
cloudron-box/src/components/filetree.js
T

988 lines
35 KiB
JavaScript
Raw Normal View History

'use strict';
/* global angular */
/* global sanitize */
angular.module('Application').component('filetree', {
bindings: {
backendId: '<',
2022-08-25 12:23:38 +02:00
backendType: '<',
view: '<'
},
templateUrl: 'components/filetree.html?<%= revision %>',
controller: [ '$scope', '$translate', '$timeout', 'Client', FileTreeController ]
});
function FileTreeController($scope, $translate, $timeout, Client) {
$scope.backendId = this.backendId;
$scope.backendType = this.backendType;
2022-08-25 12:23:38 +02:00
$scope.view = this.view;
$scope.busy = true;
$scope.client = Client;
$scope.cwd = null;
$scope.cwdParts = [];
$scope.rootDirLabel = '';
$scope.entries = [];
$scope.selected = []; // holds selected entries
$scope.clipboard = []; // holds cut or copied entries
$scope.clipboardCut = false; // if action is cut or copy
$scope.dropToBody = false;
$scope.applicationLink = '';
$scope.owners = [
{ name: 'cloudron', value: 1000 },
{ name: 'www-data', value: 33 },
{ name: 'git', value: 1001 },
{ name: 'root', value: 0 }
];
function isArchive(f) {
return f.match(/\.tgz$/) ||
f.match(/\.tar$/) ||
f.match(/\.7z$/) ||
f.match(/\.zip$/) ||
f.match(/\.tar\.gz$/) ||
f.match(/\.tar\.xz$/) ||
f.match(/\.tar\.bz2$/);
}
$scope.menuOptions = []; // shown for entries
$scope.menuOptionsBlank = []; // shown for empty space in folder
function sort() {
return $scope.entries.sort(function (a, b) {
if (a.fileName.toLowerCase() < b.fileName.toLowerCase()) return -1;
return 1;
}).sort(function (a, b) {
if (a.isDirectory !== b.isDirectory) return 1;
return -1;
});
}
function openPath(path) {
path = sanitize(path);
// we always show the parent path, even if overlayed by a mediaviewer
var parentPath = sanitize(path + '/..');
$scope.busy = true;
// nothing changes here, mostly triggered when editor is closed
if ($scope.cwd === path) {
$scope.busy = false;
return;
}
if ($scope.cwd === parentPath) {
var entry = $scope.entries.find(function (e) { return path === sanitize(parentPath + '/' + e.fileName); });
if (!entry) return Client.error('No such file or folder: ' + path);
if (entry.isDirectory) {
$scope.cwd = path;
$scope.selected = [];
$scope.cwdParts = path.split('/').filter(function (p) { return !!p; }).map(function (p, i) { return { name: decodeURIComponent(p), path: path.split('/').slice(0, i+1).join('/') }; });
// refresh will set busy to false once done
$scope.refresh();
} else if (entry.isFile) {
var mimeType = Mimer().get(entry.fileName);
var mimeGroup = mimeType.split('/')[0];
if (mimeGroup === 'video' || mimeGroup === 'image') {
$scope.mediaViewer.show(entry);
} else if (mimeType === 'application/pdf') {
Client.filesGet($scope.backendId, $scope.backendType, path, 'open', function (error) { if (error) return Client.error(error); });
} else if (mimeGroup === 'text' || mimeGroup === 'application') {
2022-08-25 10:26:22 +02:00
$scope.$parent.textEditor.show($scope.cwd, entry);
} else {
Client.filesGet($scope.backendId, $scope.backendType, path, 'open', function (error) { if (error) return Client.error(error); });
}
$scope.busy = false;
}
return;
}
Client.filesGet($scope.backendId, $scope.backendType, parentPath, 'data', function (error, result) {
if (error) return setTimeout(function () { openPath(path); }, 2000); // try again in some time
// amend icons
result.entries.forEach(function (e) {
e.icon = 'fa-file';
if (e.isDirectory) e.icon = 'fa-folder';
if (e.isSymbolicLink) e.icon = 'fa-link';
if (e.isFile) {
var mimeType = Mimer().get(e.fileName);
var mimeGroup = mimeType.split('/')[0];
if (mimeGroup === 'text') e.icon = 'fa-file-alt';
if (mimeGroup === 'image') e.icon = 'fa-file-image';
if (mimeGroup === 'video') e.icon = 'fa-file-video';
if (mimeGroup === 'audio') e.icon = 'fa-file-audio';
if (mimeType === 'text/csv') e.icon = 'fa-file-csv';
if (mimeType === 'application/pdf') e.icon = 'fa-file-pdf';
}
});
$scope.entries = result.entries;
sort();
// call itself now that we know
$scope.cwd = parentPath;
openPath(path);
});
}
$scope.isSelected = function (entry) {
return $scope.selected.indexOf(entry) !== -1;
};
$scope.extractStatus = {
error: null,
busy: false,
fileName: ''
};
function extract(entry) {
var filePath = sanitize($scope.cwd + '/' + entry.fileName);
if (entry.isDirectory) return;
// prevent it from getting closed
$('#extractModal').modal({
backdrop: 'static',
keyboard: false
});
$scope.extractStatus.fileName = entry.fileName;
$scope.extractStatus.error = null;
$scope.extractStatus.busy = true;
Client.filesExtract($scope.backendId, $scope.backendType, filePath, function (error) {
$scope.extractStatus.busy = false;
if (error) {
console.error(error);
$scope.extractStatus.error = $translate.instant('filemanager.extract.error', error.message);
return;
}
$('#extractModal').modal('hide');
$scope.refresh();
});
}
function download(entry) {
var filePath = sanitize($scope.cwd + '/' + entry.fileName);
Client.filesGet($scope.backendId, $scope.backendType, filePath, 'download', function (error) {
if (error) return Client.error(error);
});
}
$scope.dragStart = function ($event, entry) {
$event.originalEvent.dataTransfer.setData('text/plain', entry.fileName);
$event.originalEvent.dataTransfer.setData('application/cloudron-filemanager', entry.fileName);
};
$scope.dragEnter = function ($event, entry) {
$event.originalEvent.stopPropagation();
$event.originalEvent.preventDefault();
// if entry is string, we come from breadcrumb
if (entry && typeof entry === 'string') $event.currentTarget.classList.add('entry-hovered');
else if (entry && entry.isDirectory) entry.hovered = true;
else $scope.dropToBody = true;
$event.originalEvent.dataTransfer.dropEffect = 'move';
};
$scope.dragExit = function ($event, entry) {
$event.originalEvent.stopPropagation();
$event.originalEvent.preventDefault();
// if entry is string, we come from breadcrumb
if (entry && typeof entry === 'string') $event.currentTarget.classList.remove('entry-hovered');
else if (entry && entry.isDirectory) entry.hovered = false;
$scope.dropToBody = false;
$event.originalEvent.dataTransfer.dropEffect = 'move';
};
$scope.drop = function (event, entry) {
event.originalEvent.stopPropagation();
event.originalEvent.preventDefault();
$scope.dropToBody = false;
if (!event.originalEvent.dataTransfer.items[0]) return;
var targetFolder;
if (typeof entry === 'string') targetFolder = sanitize(entry);
else targetFolder = sanitize($scope.cwd + '/' + (entry && entry.isDirectory ? entry.fileName : ''));
var dataTransfer = event.originalEvent.dataTransfer;
// check if we have internal drag'n'drop
if (dataTransfer.getData('application/cloudron-filemanager')) {
if ($scope.selected.length === 0) return;
var moved = 0;
// move files
async.eachLimit($scope.selected, 5, function (entry, callback) {
var oldFilePath = sanitize($scope.cwd + '/' + entry.fileName);
var newFilePath = sanitize(targetFolder + '/' + entry.fileName);
// if we drop the item on itself
if (oldFilePath === targetFolder) return callback();
// if nothing changes
if (newFilePath === oldFilePath) return callback();
moved++;
// TODO this will overwrite files in destination!
Client.filesRename($scope.backendId, $scope.backendType, oldFilePath, newFilePath, callback);
}, function (error) {
if (error) return Client.error(error);
$scope.selected = [];
// only refresh if anything has changed
if (moved) $scope.refresh();
});
return;
}
// 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 = dataTransfer.items[0].webkitGetAsEntry();
if (folderItem.isFile) return uploadFiles(event.originalEvent.dataTransfer.files, targetFolder, false);
} catch (e) {
return uploadFiles(event.originalEvent.dataTransfer.files, targetFolder, false);
}
// 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, false);
});
};
$scope.refresh = function () {
$scope.selected = [];
Client.filesGet($scope.backendId, $scope.backendType, $scope.cwd, 'data', function (error, result) {
if (error) return Client.error(error);
// amend icons
result.entries.forEach(function (e) {
e.icon = 'fa-file';
e.previewUrl = null;
if (e.isDirectory) e.icon = 'fa-folder';
if (e.isSymbolicLink) e.icon = 'fa-link';
if (e.isFile) {
var mimeType = Mimer().get(e.fileName);
var mimeGroup = mimeType.split('/')[0];
if (mimeGroup === 'text') e.icon = 'fa-file-alt';
// if (mimeGroup === 'image') e.icon = 'fa-file-image';
if (mimeGroup === 'image') {
e.icon = 'fa-file-image';
e.previewUrl = Client.filesGetLink($scope.backendId, $scope.backendType, sanitize($scope.cwd + '/' + e.fileName));
}
if (mimeGroup === 'video') e.icon = 'fa-file-video';
if (mimeGroup === 'audio') e.icon = 'fa-file-audio';
if (mimeType === 'text/csv') e.icon = 'fa-file-csv';
if (mimeType === 'application/pdf') e.icon = 'fa-file-pdf';
}
});
$scope.entries = result.entries;
sort();
$scope.busy = false;
});
};
$scope.open = function (entry) {
openPath(sanitize($scope.cwd + '/' + entry.fileName));
};
$scope.goDirectoryUp = function () {
openPath(sanitize($scope.cwd + '/..'));
};
$scope.changeDirectory = function (path) {
openPath(sanitize(path));
};
$scope.select = function ($event, entry) {
// we don't stop propagation for context menu closing, but if targets don't match we got the whole list click event
if ($event.currentTarget !== $event.target) return;
if (!entry) {
$scope.selected = [];
return;
}
var i = $scope.selected.indexOf(entry);
var multi = ($event.ctrlKey || $event.metaKey);
if ($event.button === 0) {
// left click
if (multi) {
if (i === -1) $scope.selected.push(entry);
else $scope.selected.splice(i, 1);
} else {
$scope.selected = [ entry ];
}
} else if ($event.button === 2) {
// right click
if (i === -1) {
if (multi) $scope.selected.push(entry);
else $scope.selected = [ entry ];
}
}
};
$scope.onEntryContextMenu = function ($event, entry) {
if ($scope.selected.indexOf(entry) !== -1) return;
$scope.selected.push(entry);
};
$scope.actionCut = function () {
$scope.clipboard = $scope.selected.slice();
$scope.clipboard.forEach(function (entry) {
entry.fullFilePath = sanitize($scope.cwd + '/' + entry.fileName);
});
$scope.clipboardCut = true;
};
$scope.actionCopy = function () {
$scope.clipboard = $scope.selected.slice();
$scope.clipboard.forEach(function (entry) {
entry.fullFilePath = sanitize($scope.cwd + '/' + entry.fileName);
entry.pathFrom = $scope.cwd; // we stash the original path for pasting
});
$scope.clipboardCut = false;
};
function collectFiles(entry, callback) {
var pathFrom = entry.pathFrom;
if (entry.isDirectory) {
Client.filesGet($scope.backendId, $scope.backendType, entry.fullFilePath, 'data', function (error, result) {
if (error) return callback(error);
if (!result.entries) return callback(new Error('not a folder'));
// amend fullFilePath
result.entries.forEach(function (e) {
e.fullFilePath = sanitize(entry.fullFilePath + '/' + e.fileName);
e.pathFrom = pathFrom; // we stash the original path for pasting
});
var collectedFiles = [];
async.eachLimit(result.entries, 5, function (entry, callback) {
collectFiles(entry, function (error, result) {
if (error) return callback(error);
collectedFiles = collectedFiles.concat(result);
callback();
});
}, function (error) {
if (error) return callback(error);
callback(null, collectedFiles);
});
});
return;
}
callback(null, [ entry ]);
}
$scope.actionPaste = function (destinationEntry) {
if ($scope.clipboardCut) {
// move files
async.eachLimit($scope.clipboard, 5, function (entry, callback) {
var newFilePath = sanitize($scope.cwd + '/' + ((destinationEntry && destinationEntry.isDirectory) ? destinationEntry.fileName : '') + '/' + entry.fileName);
// TODO this will overwrite files in destination!
Client.filesRename($scope.backendId, $scope.backendType, entry.fullFilePath, newFilePath, callback);
}, function (error) {
if (error) return Client.error(error);
// clear clipboard
$scope.clipboard = [];
$scope.refresh();
});
} else {
// copy files
// first collect all files recursively
var collectedFiles = [];
async.eachLimit($scope.clipboard, 5, function (entry, callback) {
collectFiles(entry, function (error, result) {
if (error) return callback(error);
collectedFiles = collectedFiles.concat(result);
callback();
});
}, function (error) {
if (error) return Client.error(error);
async.eachLimit(collectedFiles, 5, function (entry, callback) {
var newFilePath = sanitize($scope.cwd + '/' + ((destinationEntry && destinationEntry.isDirectory) ? destinationEntry.fileName : '') + '/' + entry.fullFilePath.slice(entry.pathFrom.length));
// This will NOT overwrite but finds a unique new name to copy to
// we prefix with a / to ensure we don't do relative target paths
Client.filesCopy($scope.backendId, $scope.backendType, entry.fullFilePath, '/' + newFilePath, callback);
}, function (error) {
if (error) return Client.error(error);
// clear clipboard
$scope.clipboard = [];
$scope.refresh();
});
});
}
};
$scope.actionSelectAll = function () {
$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(); };
$scope.restartBusy = false;
$scope.onRestartApp = function () {
$scope.restartBusy = true;
function waitUntilRestarted(callback) {
Client.getApp($scope.backendId, function (error, result) {
if (error) return callback(error);
if (result.installationState === ISTATES.INSTALLED) return callback();
setTimeout(waitUntilRestarted.bind(null, callback), 2000);
});
}
Client.restartApp($scope.backendId, function (error) {
if (error) console.error('Failed to restart app.', error);
waitUntilRestarted(function (error) {
if (error) console.error('Failed wait for restart.', error);
$scope.restartBusy = false;
});
});
};
$scope.onRestartMail = function () {
$scope.restartBusy = true;
function waitUntilRestarted(callback) {
Client.getService('mail', function (error, result) {
if (error) return callback(error);
if (result.status === 'active') return callback();
setTimeout(waitUntilRestarted.bind(null, callback), 2000);
});
}
Client.restartService('mail', function (error) {
if (error) console.error('Failed to restart mail.', error);
waitUntilRestarted(function (error) {
if (error) console.error('Failed wait for restart.', error);
$scope.restartBusy = false;
});
});
};
$scope.newDirectory = {
busy: false,
error: null,
name: '',
show: function () {
$scope.newDirectory.error = null;
$scope.newDirectory.name = '';
$scope.newDirectory.busy = false;
$scope.newDirectoryForm.$setUntouched();
$scope.newDirectoryForm.$setPristine();
$('#newDirectoryModal-' + $scope.$id).modal('show');
},
submit: function () {
$scope.newDirectory.busy = true;
$scope.newDirectory.error = null;
var filePath = sanitize($scope.cwd + '/' + $scope.newDirectory.name);
Client.filesCreateDirectory($scope.backendId, $scope.backendType, filePath, function (error) {
$scope.newDirectory.busy = false;
if (error && error.statusCode === 409) return $scope.newDirectory.error = 'exists';
if (error) return Client.error(error);
$scope.refresh();
$('#newDirectoryModal-' + $scope.$id).modal('hide');
});
}
};
$scope.renameEntry = {
busy: false,
error: null,
entry: null,
newName: '',
show: function (entry) {
$scope.renameEntry.error = null;
$scope.renameEntry.entry = entry;
$scope.renameEntry.newName = entry.fileName;
$scope.renameEntry.busy = false;
$('#renameEntryModal-' + $scope.$id).modal('show');
},
submit: function () {
$scope.renameEntry.busy = true;
var oldFilePath = sanitize($scope.cwd + '/' + $scope.renameEntry.entry.fileName);
var newFilePath = sanitize(($scope.renameEntry.newName[0] === '/' ? '' : ($scope.cwd + '/')) + $scope.renameEntry.newName);
Client.filesRename($scope.backendId, $scope.backendType, oldFilePath, newFilePath, function (error) {
$scope.renameEntry.busy = false;
if (error) return Client.error(error);
$scope.refresh();
$('#renameEntryModal-' + $scope.$id).modal('hide');
});
}
};
$scope.mediaViewer = {
type: '',
src: '',
entry: null,
show: function (entry) {
var filePath = sanitize($scope.cwd + '/' + entry.fileName);
$scope.mediaViewer.entry = entry;
$scope.mediaViewer.type = Mimer().get(entry.fileName).split('/')[0];
$scope.mediaViewer.src = Client.filesGetLink($scope.backendId, $scope.backendType, filePath);
$('#mediaViewerModal-' + $scope.$id).modal('show');
},
close: function () {
// set an empty pixel image to bust the cached img to avoid flickering on slow load
$scope.mediaViewer.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==';
$('#mediaViewerModal-' + $scope.$id).modal('hide');
}
};
$scope.chownEntry = {
busy: false,
error: null,
newOwner: 0,
recursive: false,
showRecursiveOption: false,
show: function () {
$scope.chownEntry.error = null;
// set default uid from first file
$scope.chownEntry.newOwner = $scope.selected[0].uid;
$scope.chownEntry.busy = false;
// default for directories is recursive
$scope.chownEntry.recursive = !!$scope.selected.find(function (entry) { return entry.isDirectory; });
$scope.chownEntry.showRecursiveOption = !!$scope.chownEntry.recursive;
$('#chownEntryModal-' + $scope.$id).modal('show');
},
submit: function () {
$scope.chownEntry.busy = true;
async.eachLimit($scope.selected, 5, function (entry, callback) {
var filePath = sanitize($scope.cwd + '/' + entry.fileName);
Client.filesChown($scope.backendId, $scope.backendType, filePath, $scope.chownEntry.newOwner, $scope.chownEntry.recursive, callback);
}, function (error) {
$scope.chownEntry.busy = false;
if (error) return Client.error(error);
$scope.refresh();
$('#chownEntryModal-' + $scope.$id).modal('hide');
});
}
};
$scope.newFile = {
busy: false,
error: null,
name: '',
show: function () {
$scope.newFile.error = null;
$scope.newFile.name = '';
$scope.newFile.busy = false;
$scope.newFileForm.$setUntouched();
$scope.newFileForm.$setPristine();
$('#newFileModal-' + $scope.$id).modal('show');
},
submit: function () {
$scope.newFile.busy = true;
$scope.newFile.error = null;
var filePath = sanitize($scope.cwd + '/' + $scope.newFile.name);
Client.filesUpload($scope.backendId, $scope.backendType, filePath, new File([], $scope.newFile.name), false, function () {}, function (error) {
$scope.newFile.busy = false;
if (error && error.statusCode === 409) return $scope.newFile.error = 'exists';
if (error) return Client.error(error);
$scope.refresh();
$('#newFileModal-' + $scope.$id).modal('hide');
});
}
};
$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 = [
{
text: tr['filemanager.list.menu.edit'],
displayed: function ($itemScope, $event, entry) { return !entry.isDirectory && !entry.isSymbolicLink; },
enabled: function () { return $scope.selected.length === 1; },
hasBottomDivider: true,
click: function ($itemScope, $event, entry) { $scope.open(entry); }
}, {
text: tr['filemanager.list.menu.cut'],
click: function ($itemScope, $event, entry) { $scope.actionCut(); }
}, {
text: tr['filemanager.list.menu.copy'],
click: function ($itemScope, $event, entry) { $scope.actionCopy(); }
}, {
text: tr['filemanager.list.menu.paste'],
hasBottomDivider: true,
enabled: function () { return $scope.clipboard.length; },
click: function ($itemScope, $event, entry) { $scope.actionPaste(entry); }
}, {
text: tr['filemanager.list.menu.rename'],
enabled: function () { return $scope.selected.length === 1; },
click: function ($itemScope, $event, entry) { $scope.renameEntry.show(entry); }
}, {
text: tr['filemanager.list.menu.chown'],
click: function ($itemScope, $event, entry) { $scope.chownEntry.show(); }
}, {
text: tr['filemanager.list.menu.extract'],
displayed: function ($itemScope, $event, entry) { return !entry.isDirectory && isArchive(entry.fileName); },
click: function ($itemScope, $event, entry) { extract(entry); }
}, {
text: tr['filemanager.list.menu.download'],
enabled: function () { return $scope.selected.length === 1; },
click: function ($itemScope, $event, entry) { download(entry); }
}, {
text: tr['filemanager.list.menu.delete'],
hasTopDivider: true,
click: function ($itemScope, $event, entry) { $scope.entryRemove.show(); }
}
];
});
$translate(['filemanager.toolbar.newFile', 'filemanager.toolbar.newFolder', 'filemanager.list.menu.paste', 'filemanager.list.menu.selectAll' ]).then(function (tr) {
$scope.menuOptionsBlank = [
{
text: tr['filemanager.toolbar.newFile'],
click: function ($itemScope, $event) { $scope.newFile.show(); }
}, {
text: tr['filemanager.toolbar.newFolder'],
click: function ($itemScope, $event) { $scope.newDirectory.show(); }
}, {
text: tr['filemanager.list.menu.paste'],
hasTopDivider: true,
hasBottomDivider: true,
enabled: function () { return $scope.clipboard.length; },
click: function ($itemScope, $event) { $scope.actionPaste(); }
}, {
text: tr['filemanager.list.menu.selectAll'],
click: function ($itemScope, $event) { $scope.actionSelectAll(); }
}
];
});
$('.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;
// This assumes the DOM tree being that rigid
function isVisible(ele) {
var container = ele.parentElement.parentElement.parentElement;
var eleTop = ele.offsetTop;
var eleBottom = eleTop + ele.clientHeight;
var containerTop = container.scrollTop;
var containerBottom = containerTop + container.clientHeight;
// The element is fully visible in the container
return (
(eleTop >= containerTop && eleBottom <= containerBottom) ||
// Some part of the element is visible in the container
(eleTop < containerTop && containerTop < eleBottom) ||
(eleTop < containerBottom && containerBottom < eleBottom)
);
}
if (!isVisible(element)) element.scrollIntoView();
}
function openSelected() {
if (!$scope.selected.length) return;
$scope.open($scope.selected[0]);
}
function selectNext() {
var entries = sort();
if (!$scope.selected.length) return $scope.selected = [ entries[0] ];
var curIndex = $scope.entries.indexOf($scope.selected[0]);
if (curIndex !== -1 && curIndex < $scope.entries.length-1) {
var entry = entries[++curIndex];
$scope.selected = [ entry ];
scrollInView(document.querySelector('[entry-hashkey="' + entry['$$hashKey'] + '"]'));
}
}
function selectPrev() {
var entries = sort();
if (!$scope.selected.length) return $scope.selected = [ entries.slice(-1) ];
var curIndex = $scope.entries.indexOf($scope.selected[0]);
if (curIndex !== -1 && curIndex !== 0) {
var entry = entries[--curIndex];
$scope.selected = [ entry ];
scrollInView(document.querySelector('[entry-hashkey="' + entry['$$hashKey'] + '"]'));
}
}
openPath('.');
2022-08-25 12:23:38 +02:00
// handle save shortcuts
window.addEventListener('keydown', function (event) {
if ($scope.$parent.activeView !== $scope.view || $scope.$parent.viewerOpen) return;
if (event.key === 'ArrowDown') {
$scope.$apply(selectNext);
} else if (event.key === 'ArrowUp') {
$scope.$apply(selectPrev);
} else if (event.key === 'Enter') {
$scope.$apply(openSelected);
} else if (event.key === 'Backspace') {
if ($scope.view === 'fileTree') $scope.goDirectoryUp();
}
});
}