123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547 |
- /*
- * Copyright (c) 2014
- *
- * This file is licensed under the Affero General Public License version 3
- * or later.
- *
- * See the COPYING-README file.
- *
- */
- /* global getURLParameter */
- /**
- * Utility class for file related operations
- */
- (function() {
- var Files = {
- // file space size sync
- _updateStorageStatistics: function(currentDir) {
- var state = Files.updateStorageStatistics;
- if (state.dir){
- if (state.dir === currentDir) {
- return;
- }
- // cancel previous call, as it was for another dir
- state.call.abort();
- }
- state.dir = currentDir;
- state.call = $.getJSON(OC.generateUrl('apps/files/ajax/getstoragestats?dir={dir}', {
- dir: currentDir,
- }), function(response) {
- state.dir = null;
- state.call = null;
- Files.updateMaxUploadFilesize(response);
- });
- },
- // update quota
- updateStorageQuotas: function() {
- Files._updateStorageQuotasThrottled();
- },
- _updateStorageQuotas: function() {
- var state = Files.updateStorageQuotas;
- state.call = $.getJSON(OC.generateUrl('apps/files/ajax/getstoragestats'), function(response) {
- Files.updateQuota(response);
- });
- },
- /**
- * Update storage statistics such as free space, max upload,
- * etc based on the given directory.
- *
- * Note this function is debounced to avoid making too
- * many ajax calls in a row.
- *
- * @param dir directory
- * @param force whether to force retrieving
- */
- updateStorageStatistics: function(dir, force) {
- if (!OC.currentUser) {
- return;
- }
- if (force) {
- Files._updateStorageStatistics(dir);
- }
- else {
- Files._updateStorageStatisticsDebounced(dir);
- }
- },
- updateMaxUploadFilesize:function(response) {
- if (response === undefined) {
- return;
- }
- if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) {
- $('#free_space').val(response.data.freeSpace);
- $('#upload.button').attr('title', response.data.maxHumanFilesize);
- $('#usedSpacePercent').val(response.data.usedSpacePercent);
- $('#usedSpacePercent').data('mount-type', response.data.mountType);
- $('#usedSpacePercent').data('mount-point', response.data.mountPoint);
- $('#owner').val(response.data.owner);
- $('#ownerDisplayName').val(response.data.ownerDisplayName);
- Files.displayStorageWarnings();
- OCA.Files.App.fileList._updateDirectoryPermissions();
- }
- if (response[0] === undefined) {
- return;
- }
- if (response[0].uploadMaxFilesize !== undefined) {
- $('#upload.button').attr('title', response[0].maxHumanFilesize);
- $('#usedSpacePercent').val(response[0].usedSpacePercent);
- Files.displayStorageWarnings();
- }
- },
- updateQuota:function(response) {
- if (response === undefined) {
- return;
- }
- if (response.data !== undefined
- && response.data.quota !== undefined
- && response.data.total !== undefined
- && response.data.used !== undefined
- && response.data.usedSpacePercent !== undefined) {
- var humanUsed = OC.Util.humanFileSize(response.data.used, true);
- var humanTotal = OC.Util.humanFileSize(response.data.total, true);
- if (response.data.quota > 0) {
- $('#quota').attr('title', t('files', '{used}%', {used: Math.round(response.data.usedSpacePercent)}));
- $('#quota progress').val(response.data.usedSpacePercent);
- $('#quotatext').html(t('files', '{used} of {quota} used', {used: humanUsed, quota: humanTotal}));
- } else {
- $('#quotatext').html(t('files', '{used} used', {used: humanUsed}));
- }
- if (response.data.usedSpacePercent > 80) {
- $('#quota progress').addClass('warn');
- } else {
- $('#quota progress').removeClass('warn');
- }
- }
- },
- /**
- * Fix path name by removing double slash at the beginning, if any
- */
- fixPath: function(fileName) {
- if (fileName.substr(0, 2) == '//') {
- return fileName.substr(1);
- }
- return fileName;
- },
- /**
- * Checks whether the given file name is valid.
- * @param name file name to check
- * @return true if the file name is valid.
- * Throws a string exception with an error message if
- * the file name is not valid
- *
- * NOTE: This function is duplicated in the filepicker inside core/src/OC/dialogs.js
- */
- isFileNameValid: function (name) {
- var trimmedName = name.trim();
- if (trimmedName === '.' || trimmedName === '..')
- {
- throw t('files', '"{name}" is an invalid file name.', {name: name});
- } else if (trimmedName.length === 0) {
- throw t('files', 'File name cannot be empty.');
- } else if (trimmedName.indexOf('/') !== -1) {
- throw t('files', '"/" is not allowed inside a file name.');
- } else if (!!(trimmedName.match(OC.config.blacklist_files_regex))) {
- throw t('files', '"{name}" is not an allowed filetype', {name: name});
- }
- return true;
- },
- displayStorageWarnings: function() {
- if (!OC.Notification.isHidden()) {
- return;
- }
- var usedSpacePercent = $('#usedSpacePercent').val(),
- owner = $('#owner').val(),
- ownerDisplayName = $('#ownerDisplayName').val(),
- mountType = $('#usedSpacePercent').data('mount-type'),
- mountPoint = $('#usedSpacePercent').data('mount-point');
- if (usedSpacePercent > 98) {
- if (owner !== OC.getCurrentUser().uid) {
- OC.Notification.show(t('files', 'Storage of {owner} is full, files cannot be updated or synced anymore!',
- {owner: ownerDisplayName}), {type: 'error'}
- );
- } else if (mountType === 'group') {
- OC.Notification.show(t('files',
- 'Group folder "{mountPoint}" is full, files cannot be updated or synced anymore!',
- {mountPoint: mountPoint}),
- {type: 'error'}
- );
- } else if (mountType === 'external') {
- OC.Notification.show(t('files',
- 'External storage "{mountPoint}" is full, files cannot be updated or synced anymore!',
- {mountPoint: mountPoint}),
- {type : 'error'}
- );
- } else {
- OC.Notification.show(t('files',
- 'Your storage is full, files cannot be updated or synced anymore!'),
- {type: 'error'}
- );
- }
- } else if (usedSpacePercent > 90) {
- if (owner !== OC.getCurrentUser().uid) {
- OC.Notification.show(t('files', 'Storage of {owner} is almost full ({usedSpacePercent}%).',
- {
- usedSpacePercent: usedSpacePercent,
- owner: ownerDisplayName
- }),
- {
- type: 'error'
- }
- );
- } else if (mountType === 'group') {
- OC.Notification.show(t('files',
- 'Group folder "{mountPoint}" is almost full ({usedSpacePercent}%).',
- {mountPoint: mountPoint, usedSpacePercent: usedSpacePercent}),
- {type : 'error'}
- );
- } else if (mountType === 'external') {
- OC.Notification.show(t('files',
- 'External storage "{mountPoint}" is almost full ({usedSpacePercent}%).',
- {mountPoint: mountPoint, usedSpacePercent: usedSpacePercent}),
- {type : 'error'}
- );
- } else {
- OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%).',
- {usedSpacePercent: usedSpacePercent}),
- {type : 'error'}
- );
- }
- }
- },
- /**
- * Returns the download URL of the given file(s)
- * @param {string} filename string or array of file names to download
- * @param {string} [dir] optional directory in which the file name is, defaults to the current directory
- * @param {boolean} [isDir=false] whether the given filename is a directory and might need a special URL
- */
- getDownloadUrl: function(filename, dir, isDir) {
- if (!_.isArray(filename) && !isDir) {
- var pathSections = dir.split('/');
- pathSections.push(filename);
- var encodedPath = '';
- _.each(pathSections, function(section) {
- if (section !== '') {
- encodedPath += '/' + encodeURIComponent(section);
- }
- });
- return OC.linkToRemoteBase('webdav') + encodedPath;
- }
- if (_.isArray(filename)) {
- filename = JSON.stringify(filename);
- }
- var params = {
- dir: dir,
- files: filename
- };
- return this.getAjaxUrl('download', params);
- },
- /**
- * Returns the ajax URL for a given action
- * @param action action string
- * @param params optional params map
- */
- getAjaxUrl: function(action, params) {
- var q = '';
- if (params) {
- q = '?' + OC.buildQueryString(params);
- }
- return OC.filePath('files', 'ajax', action + '.php') + q;
- },
- /**
- * Fetch the icon url for the mimetype
- * @param {string} mime The mimetype
- * @param {Files~mimeicon} ready Function to call when mimetype is retrieved
- * @deprecated use OC.MimeType.getIconUrl(mime)
- */
- getMimeIcon: function(mime, ready) {
- ready(OC.MimeType.getIconUrl(mime));
- },
- /**
- * Generates a preview URL based on the URL space.
- * @param urlSpec attributes for the URL
- * @param {number} urlSpec.x width
- * @param {number} urlSpec.y height
- * @param {String} urlSpec.file path to the file
- * @return preview URL
- * @deprecated used OCA.Files.FileList.generatePreviewUrl instead
- */
- generatePreviewUrl: function(urlSpec) {
- console.warn('DEPRECATED: please use generatePreviewUrl() from an OCA.Files.FileList instance');
- return OCA.Files.App.fileList.generatePreviewUrl(urlSpec);
- },
- /**
- * Lazy load preview
- * @deprecated used OCA.Files.FileList.lazyLoadPreview instead
- */
- lazyLoadPreview : function(path, mime, ready, width, height, etag) {
- console.warn('DEPRECATED: please use lazyLoadPreview() from an OCA.Files.FileList instance');
- return FileList.lazyLoadPreview({
- path: path,
- mime: mime,
- callback: ready,
- width: width,
- height: height,
- etag: etag
- });
- },
- /**
- * Initialize the files view
- */
- initialize: function() {
- Files.bindKeyboardShortcuts(document, $);
- // drag&drop support using jquery.fileupload
- // TODO use OC.dialogs
- $(document).bind('drop dragover', function (e) {
- e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone
- });
- // display storage warnings
- setTimeout(Files.displayStorageWarnings, 100);
- // only possible at the moment if user is logged in or the files app is loaded
- if (OC.currentUser && OCA.Files.App && OC.config.session_keepalive) {
- // start on load - we ask the server every 5 minutes
- var func = _.bind(OCA.Files.App.fileList.updateStorageStatistics, OCA.Files.App.fileList);
- var updateStorageStatisticsInterval = 5*60*1000;
- var updateStorageStatisticsIntervalId = setInterval(func, updateStorageStatisticsInterval);
- // TODO: this should also stop when switching to another view
- // Use jquery-visibility to de-/re-activate file stats sync
- if ($.support.pageVisibility) {
- $(document).on({
- 'show': function() {
- if (!updateStorageStatisticsIntervalId) {
- updateStorageStatisticsIntervalId = setInterval(func, updateStorageStatisticsInterval);
- }
- },
- 'hide': function() {
- clearInterval(updateStorageStatisticsIntervalId);
- updateStorageStatisticsIntervalId = 0;
- }
- });
- }
- }
- $('#webdavurl').on('click touchstart', function () {
- this.focus();
- this.setSelectionRange(0, this.value.length);
- });
- //FIXME scroll to and highlight preselected file
- /*
- if (getURLParameter('scrollto')) {
- FileList.scrollTo(getURLParameter('scrollto'));
- }
- */
- },
- /**
- * Handles the download and calls the callback function once the download has started
- * - browser sends download request and adds parameter with a token
- * - server notices this token and adds a set cookie to the download response
- * - browser now adds this cookie for the domain
- * - JS periodically checks for this cookie and then knows when the download has started to call the callback
- *
- * @param {string} url download URL
- * @param {Function} callback function to call once the download has started
- */
- handleDownload: function(url, callback) {
- var randomToken = Math.random().toString(36).substring(2),
- checkForDownloadCookie = function() {
- if (!OC.Util.isCookieSetToValue('ocDownloadStarted', randomToken)){
- return false;
- } else {
- callback();
- return true;
- }
- };
- if (url.indexOf('?') >= 0) {
- url += '&';
- } else {
- url += '?';
- }
- OC.redirect(url + 'downloadStartSecret=' + randomToken);
- OC.Util.waitFor(checkForDownloadCookie, 500);
- }
- };
- Files._updateStorageStatisticsDebounced = _.debounce(Files._updateStorageStatistics, 250);
- Files._updateStorageQuotasThrottled = _.throttle(Files._updateStorageQuotas, 30000);
- OCA.Files.Files = Files;
- })();
- // TODO: move to FileList
- var createDragShadow = function(event) {
- // FIXME: inject file list instance somehow
- /* global FileList, Files */
- //select dragged file
- var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
- if (!isDragSelected) {
- //select dragged file
- FileList._selectFileEl($(event.target).parents('tr:first'), true, false);
- }
- // do not show drag shadow for too many files
- var selectedFiles = _.first(FileList.getSelectedFiles(), FileList.pageSize());
- selectedFiles = _.sortBy(selectedFiles, FileList._fileInfoCompare);
- if (!isDragSelected && selectedFiles.length === 1) {
- //revert the selection
- FileList._selectFileEl($(event.target).parents('tr:first'), false, false);
- }
- // build dragshadow
- var dragshadow = $('<table class="dragshadow"></table>');
- var tbody = $('<tbody></tbody>');
- dragshadow.append(tbody);
- var dir = FileList.getCurrentDirectory();
- $(selectedFiles).each(function(i,elem) {
- // TODO: refactor this with the table row creation code
- var newtr = $('<tr></tr>')
- .attr('data-dir', dir)
- .attr('data-file', elem.name)
- .attr('data-origin', elem.origin);
- newtr.append($('<td class="filename"></td>').text(elem.name).css('background-size', 32));
- newtr.append($('<td class="size"></td>').text(OC.Util.humanFileSize(elem.size)));
- tbody.append(newtr);
- if (elem.type === 'dir') {
- newtr.find('td.filename')
- .css('background-image', 'url(' + OC.MimeType.getIconUrl('folder') + ')');
- } else {
- var path = dir + '/' + elem.name;
- Files.lazyLoadPreview(path, elem.mimetype, function(previewpath) {
- newtr.find('td.filename')
- .css('background-image', 'url(' + previewpath + ')');
- }, null, null, elem.etag);
- }
- });
- return dragshadow;
- };
- //options for file drag/drop
- //start&stop handlers needs some cleaning up
- // TODO: move to FileList class
- var dragOptions={
- revert: 'invalid',
- revertDuration: 300,
- opacity: 0.7,
- cursorAt: { left: 24, top: 18 },
- helper: createDragShadow,
- cursor: 'move',
- start: function(event, ui){
- var $selectedFiles = $('td.filename input:checkbox:checked');
- if (!$selectedFiles.length) {
- $selectedFiles = $(this);
- }
- $selectedFiles.closest('tr').addClass('animate-opacity dragging');
- $selectedFiles.closest('tr').filter('.ui-droppable').droppable( 'disable' );
- // Show breadcrumbs menu
- $('.crumbmenu').addClass('canDropChildren');
- },
- stop: function(event, ui) {
- var $selectedFiles = $('td.filename input:checkbox:checked');
- if (!$selectedFiles.length) {
- $selectedFiles = $(this);
- }
- var $tr = $selectedFiles.closest('tr');
- $tr.removeClass('dragging');
- $tr.filter('.ui-droppable').droppable( 'enable' );
- setTimeout(function() {
- $tr.removeClass('animate-opacity');
- }, 300);
- // Hide breadcrumbs menu
- $('.crumbmenu').removeClass('canDropChildren');
- },
- drag: function(event, ui) {
- // Prevent scrolling when hovering .files-controls
- if ($(event.originalEvent.target).parents('.files-controls').length > 0) {
- return
- }
- /** @type {JQuery<HTMLDivElement>} */
- const scrollingArea = FileList.$container;
- // Get the top and bottom scroll trigger y positions
- const containerHeight = scrollingArea.innerHeight() ?? 0
- const scrollTriggerArea = Math.min(Math.floor(containerHeight / 2), 100);
- const bottomTriggerY = containerHeight - scrollTriggerArea;
- const topTriggerY = scrollTriggerArea;
- // Get the cursor position relative to the container
- const containerOffset = scrollingArea.offset() ?? {left: 0, top: 0}
- const cursorPositionY = event.pageY - containerOffset.top
- const currentScrollTop = scrollingArea.scrollTop() ?? 0
- if (cursorPositionY < topTriggerY) {
- scrollingArea.scrollTop(currentScrollTop - 10)
- } else if (cursorPositionY > bottomTriggerY) {
- scrollingArea.scrollTop(currentScrollTop + 10)
- }
- }
- };
- // sane browsers support using the distance option
- if ( $('html.ie').length === 0) {
- dragOptions['distance'] = 20;
- }
- // TODO: move to FileList class
- var folderDropOptions = {
- hoverClass: "canDrop",
- drop: function( event, ui ) {
- // don't allow moving a file into a selected folder
- /* global FileList */
- if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) {
- return false;
- }
- var $tr = $(this).closest('tr');
- if (($tr.data('permissions') & OC.PERMISSION_CREATE) === 0) {
- FileList._showPermissionDeniedNotification();
- return false;
- }
- var targetPath = FileList.getCurrentDirectory() + '/' + $tr.data('file');
- var files = FileList.getSelectedFiles();
- if (files.length === 0) {
- // single one selected without checkbox?
- files = _.map(ui.helper.find('tr'), function(el) {
- return FileList.elementToFile($(el));
- });
- }
- FileList.move(_.pluck(files, 'name'), targetPath);
- },
- tolerance: 'pointer'
- };
- // for backward compatibility
- window.Files = OCA.Files.Files;
|