From 1f9635a9dea06cb26096770590427876553c112f Mon Sep 17 00:00:00 2001 From: ClemDee Date: Wed, 31 Jul 2019 17:33:43 +0200 Subject: [PATCH 1/7] Keep selected elements when refresh drive --- www/drive/inner.js | 149 ++++++++++++++++++++++++++++++--------------- 1 file changed, 99 insertions(+), 50 deletions(-) diff --git a/www/drive/inner.js b/www/drive/inner.js index f1350414b..6ff116d0b 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -299,6 +299,33 @@ define([ }); }; + + APP.selectedFiles = []; + + var isElementSelected = function ($element) { + var elementId = $element.data("path").slice(-1)[0]; + return APP.selectedFiles.indexOf(elementId) !== -1; + }; + var selectElement = function ($element) { + var elementId = $element.data("path").slice(-1)[0]; + if (APP.selectedFiles.indexOf(elementId) === -1) { + APP.selectedFiles.push(elementId); + } + $element.addClass("cp-app-drive-element-selected"); + }; + var unselectElement = function ($element) { + var elementId = $element.data("path").slice(-1)[0]; + var index = APP.selectedFiles.indexOf(elementId); + if (index !== -1) { + APP.selectedFiles.splice(index, 1); + } + $element.removeClass("cp-app-drive-element-selected"); + }; + var findSelectedElements = function () { + return $(".cp-app-drive-element-selected"); + }; + + var createContextMenu = function () { var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [ h('ul.dropdown-menu', { @@ -564,7 +591,8 @@ define([ var sel = {}; var removeSelected = function (keepObj) { - $('.cp-app-drive-element-selected').removeClass("cp-app-drive-element-selected"); + APP.selectedFiles = []; + findSelectedElements().removeClass("cp-app-drive-element-selected"); var $container = $driveToolbar.find('#cp-app-drive-toolbar-contextbuttons'); if (!$container.length) { return; } $container.html(''); @@ -674,7 +702,9 @@ define([ delete sel.move; $content.find('.cp-app-drive-element-selected-tmp') .removeClass('cp-app-drive-element-selected-tmp') - .addClass('cp-app-drive-element-selected'); + .each(function (idx, element) { + selectElement($(element)); + }); e.stopPropagation(); }); @@ -709,7 +739,9 @@ define([ // Ctrl+A select all if (e.which === 65 && (e.ctrlKey || (e.metaKey && APP.isMac))) { $content.find('.cp-app-drive-element:not(.cp-app-drive-element-selected)') - .addClass('cp-app-drive-element-selected'); + .each(function (idx, element) { + selectElement($(element)); + }); return; } @@ -722,7 +754,7 @@ define([ APP.onElementClick(ev, $(el)); }; - var $selection = $content.find('.cp-app-drive-element.cp-app-drive-element-selected'); + var $selection = findSelectedElements(); if ($selection.length === 0) { return void click($elements.first()[0]); } var lastIndex = typeof sel.endSelected === "number" ? sel.endSelected : @@ -841,12 +873,12 @@ define([ return; } removeInput(); - removeSelected(); var $name = $element.find('.cp-app-drive-element-name'); if (!$name.length) { $name = $element.find('> .cp-app-drive-element'); } $name.hide(); + var isFolder = $element.is(".cp-app-drive-element-folder:not(.cp-app-drive-element-sharedf)"); var el = manager.find(path); var name = manager.isFile(el) ? manager.getTitle(el) : path[path.length - 1]; if (manager.isSharedFolder(el)) { @@ -868,14 +900,21 @@ define([ var newName = $input.val(); if (JSON.stringify(path) === JSON.stringify(currentPath)) { manager.rename(path, $input.val(), function () { - renameFoldersOpened(path, newName); - path[path.length - 1] = newName; + if (isFolder) { + renameFoldersOpened(path, newName); + path[path.length - 1] = newName; + } APP.displayDirectory(path); }); } else { manager.rename(path, $input.val(), function () { - renameFoldersOpened(path, newName); + if (isFolder) { + renameFoldersOpened(path, newName); + unselectElement($element); + $element.data("path", $element.data("path").slice(0, -1).concat(newName)); + selectElement($element); + } refresh(); }); } @@ -896,7 +935,6 @@ define([ // We don't want to open the file/folder when clicking on the input $input.on('click dblclick', function (e) { - removeSelected(); e.stopPropagation(); }); // Remove the browser ability to drag text from the input to avoid @@ -1116,8 +1154,9 @@ define([ var getSelectedPaths = function ($element) { var paths = []; - if ($('.cp-app-drive-element-selected').length > 1) { - var $selected = $('.cp-app-drive-element-selected'); + if ($element.length === 0) { return paths; } + if (findSelectedElements().length > 1) { + var $selected = findSelectedElements(); $selected.each(function (idx, elmt) { var ePath = $(elmt).data('path'); if (ePath) { @@ -1146,7 +1185,7 @@ define([ } else { $driveToolbar.find('cp-app-drive-toolbar-emptytrash').hide(); } - var $li = $content.find('.cp-app-drive-element-selected'); + var $li = findSelectedElements(); if ($li.length === 0) { $li = findDataHolder($tree.find('.cp-app-drive-element-active')); } @@ -1213,6 +1252,7 @@ define([ if (pos+eh <= h && pos >= 0) { return; } $content.scrollTop(v); }; + // Add the "selected" class to the "li" corresponding to the clicked element var onElementClick = APP.onElementClick = function (e, $element) { // If "Ctrl" is pressed, do not remove the current selection @@ -1249,23 +1289,23 @@ define([ var $el; removeSelected(true); sel.oldSelection.forEach(function (el) { - if (!$(el).hasClass("cp-app-drive-element-selected")) { - $(el).addClass("cp-app-drive-element-selected"); + if (!isElementSelected($(el))) { + selectElement($(el)); } }); for (var i = Math.min(sel.startSelected, sel.endSelected); i <= Math.max(sel.startSelected, sel.endSelected); i++) { $el = $($elements.get(i)); - if (!$el.hasClass("cp-app-drive-element-selected")) { - $el.addClass("cp-app-drive-element-selected"); + if (!isElementSelected($el)) { + selectElement($el); } } } else { - if (!$element.hasClass("cp-app-drive-element-selected")) { - $element.addClass("cp-app-drive-element-selected"); + if (!isElementSelected($element)) { + selectElement($element); } else { - $element.removeClass("cp-app-drive-element-selected"); + unselectElement($element); } } updateContextButton(); @@ -1352,6 +1392,17 @@ define([ } else { var $element = findDataHolder($(e.target)); + // if clicked from tree + var fromTree = $element.closest("#cp-app-drive-tree").length; + if (fromTree) { + removeSelected(); + } + + // if clicked on non selected element + if (!isElementSelected($element)) { + removeSelected(); + } + if (type === 'trash' && !$element.data('path')) { return; } if (!$element.length) { @@ -1360,8 +1411,8 @@ define([ return false; } - if (!$element.hasClass('cp-app-drive-element-selected')) { - onElementClick(undefined, $element); + if (!isElementSelected($element)) { + selectElement($element); } paths = getSelectedPaths($element); @@ -1398,6 +1449,7 @@ define([ var newCb = function () { paths.forEach(function (path) { moveFoldersOpened(path, newPath); + // removeSelected(); }); cb(); }; @@ -1431,6 +1483,7 @@ define([ if (!res) { return; } manager.delete(pathsList, function () { pathsList.forEach(removeFoldersOpened); + removeSelected(); refresh(); }); }, null, true); @@ -1441,7 +1494,7 @@ define([ var paths = []; var $element = findDataHolder($(ev.target)); if ($element.hasClass('cp-app-drive-element-selected')) { - var $selected = $('.cp-app-drive-element-selected'); + var $selected = findSelectedElements(); $selected.each(function (idx, elmt) { var ePath = $(elmt).data('path'); if (ePath) { @@ -1458,7 +1511,7 @@ define([ }); } else { removeSelected(); - $element.addClass('cp-app-drive-element-selected'); + selectElement($element); var val = manager.find(path); if (!val) { return; } // The element is not in the object paths = [{ @@ -1738,12 +1791,9 @@ define([ draggable: true, 'class': 'cp-app-drive-element-row' }); - if (!isFolder && Array.isArray(APP.selectedFiles)) { - var idx = APP.selectedFiles.indexOf(element); - if (idx !== -1) { - $element.addClass('cp-app-drive-element-selected'); - APP.selectedFiles.splice(idx, 1); - } + $element.data('path', newPath); + if (isElementSelected($element)) { + selectElement($element); } $element.prepend($icon).dblclick(function () { if (isFolder) { @@ -1759,11 +1809,10 @@ define([ addFileData(element, $element); } $element.addClass(liClass); - $element.data('path', newPath); addDragAndDropHandlers($element, newPath, isFolder, !isTrash); $element.click(function(e) { e.stopPropagation(); - onElementClick(e, $element, newPath); + onElementClick(e, $element); }); if (!isTrash) { $element.contextmenu(openContextMenu('tree')); @@ -2551,22 +2600,19 @@ define([ 'class': 'cp-app-drive-element cp-app-drive-element-file cp-app-drive-element-row' + roClass, draggable: draggable }); - if (Array.isArray(APP.selectedFiles)) { - var sidx = APP.selectedFiles.indexOf(id); - if (sidx !== -1) { - $element.addClass('cp-app-drive-element-selected'); - APP.selectedFiles.splice(sidx, 1); - } + + var path = [rootName, idx]; + $element.data('path', path); + if (isElementSelected($element)) { + selectElement($element); } $element.prepend($icon).dblclick(function () { openFile(id); }); addFileData(id, $element); - var path = [rootName, idx]; - $element.data('path', path); $element.click(function(e) { e.stopPropagation(); - onElementClick(e, $element, path); + onElementClick(e, $element); }); $element.contextmenu(openContextMenu('default')); $element.data('context', 'default'); @@ -2693,8 +2739,8 @@ define([ e.preventDefault(); if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; } else { parentPath.pop(); } - APP.selectedFiles = [r.id]; APP.displayDirectory(parentPath); + APP.selectedFiles = path.slice(-1); }).appendTo($openDir); } $('').text(Messages.fc_prop).click(function () { @@ -2785,7 +2831,7 @@ define([ $element.data('path', path); $element.click(function(e) { e.stopPropagation(); - onElementClick(e, $element, path); + onElementClick(e, $element); }); $element.contextmenu(openContextMenu('default')); $element.data('context', 'default'); @@ -3009,7 +3055,7 @@ define([ $context.click(function (e) { e.preventDefault(); e.stopPropagation(); - var $li = $content.find('.cp-app-drive-element-selected'); + var $li = findSelectedElements(); if ($li.length !== 1) { $li = findDataHolder($tree.find('.cp-app-drive-element-active')); } @@ -3091,7 +3137,7 @@ define([ } });*/ - var $sel = $content.find('.cp-app-drive-element-selected'); + var $sel = findSelectedElements(); if ($sel.length) { $sel[0].scrollIntoView(); } elseĀ { @@ -3103,6 +3149,9 @@ define([ if (history.isHistoryMode) { return void _displayDirectory(path, force); } + if (!manager.comparePath(currentPath, path)) { + removeSelected(); + } updateObject(sframeChan, proxy, function () { copyObjectValue(files, proxy.drive); updateSharedFolders(sframeChan, manager, files, folders, function () { @@ -3451,6 +3500,7 @@ define([ if (!res) { return; } manager.delete(pathsList, function () { pathsList.forEach(removeFoldersOpened); + removeSelected(); refresh(); }); }); @@ -3694,16 +3744,15 @@ define([ var parentPath = paths[0].path.slice(); if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; } else { parentPath.pop(); } - el = manager.find(paths[0].path); - APP.selectedFiles = [el]; APP.displayDirectory(parentPath); + APP.selectedFiles = paths[0].path.slice(-1); } APP.hideMenu(); }); - $content.on("keydown", function (e) { - if (e.which === 113) { - var paths = $contextMenu.data('paths'); + $(window).on("keydown", function (e) { + if (e.which === 113) { // if F2 key pressed + var paths = getSelectedPaths(findSelectedElements().first()); if (paths.length !== 1) { return; } displayRenameInput(paths[0].element, paths[0].path); } @@ -3737,7 +3786,7 @@ define([ if (manager.isPathIn(currentPath, [FILES_DATA]) && APP.loggedIn) { return; // We can't remove elements directly from filesData } - var $selected = $('.cp-app-drive-element-selected'); + var $selected = findSelectedElements(); if (!$selected.length) { return; } var paths = []; var isTrash = manager.isPathIn(currentPath, [TRASH]); From 1395c1a8ac2d6e0dce98b0f6ed7868c639caf191 Mon Sep 17 00:00:00 2001 From: ClemDee Date: Thu, 1 Aug 2019 11:38:47 +0200 Subject: [PATCH 2/7] Fix image flicker when the drive is refreshed --- www/common/common-thumbnail.js | 13 ++++++++++- www/drive/inner.js | 42 ++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index 2a03715cd..4dfab1457 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -230,9 +230,20 @@ define([ if (!Visible.currently()) { to = window.setTimeout(interval, Thumb.UPDATE_FIRST); } }; + var stringToBlobToUrl = function (b64) { + var byteString = atob(b64.split(',')[1]); + var ab = new ArrayBuffer(byteString.length); + var ia = new Uint8Array(ab); + for (var i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + var blob = new Blob([ab], {type: "image/png"}); + var url = URL.createObjectURL(blob); + return url; + }; var addThumbnail = function (err, thumb, $span, cb) { var img = new Image(); - img.src = thumb.slice(0,5) === 'data:' ? thumb : 'data:image/png;base64,'+thumb; + img.src = stringToBlobToUrl(thumb); $span.find('.cp-icon').hide(); $span.prepend(img); cb($(img)); diff --git a/www/drive/inner.js b/www/drive/inner.js index 6ff116d0b..ae7dc60df 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1658,7 +1658,8 @@ define([ $owner.attr('title', Messages.fm_padIsOwnedOther); } }; - var addFileData = function (element, $span) { + var thumbsUrls = {}; + var addFileData = function (element, $element) { if (!manager.isFile(element)) { return; } var data = manager.getFileData(element); @@ -1667,7 +1668,7 @@ define([ var hrefData = Hash.parsePadUrl(href); if (hrefData.type) { - $span.addClass('cp-border-color-'+hrefData.type); + $element.addClass('cp-border-color-'+hrefData.type); } var $state = $('', {'class': 'cp-app-drive-element-state'}); @@ -1687,25 +1688,36 @@ define([ var $expire = $expirableIcon.clone().appendTo($state); $expire.attr('title', Messages._getKey('fm_expirablePad', [new Date(data.expire).toLocaleString()])); } - _addOwnership($span, $state, data); + _addOwnership($element, $state, data); var name = manager.getTitle(element); // The element with the class '.name' is underlined when the 'li' is hovered var $name = $('', {'class': 'cp-app-drive-element-name'}).text(name); - $span.append($name); - $span.append($state); - $span.attr('title', name); + $element.append($name); + $element.append($state); + $element.attr('title', name); + + // display the thumbnail + // if the thumbnail has already been displayed once, do not reload it, keep the same url + if (thumbsUrls[element]) { + var img = new Image(); + img.src = thumbsUrls[element]; + $element.find('.cp-icon').addClass('cp-app-drive-element-list'); + $element.prepend(img); + $(img).addClass('cp-app-drive-element-grid cp-app-drive-element-thumbnail'); + } + else { + common.displayThumbnail(href || data.roHref, data.channel, data.password, $element, function ($thumb) { + // Called only if the thumbnail exists + // Remove the .hide() added by displayThumnail() because it hides the icon in list mode too + $element.find('.cp-icon').removeAttr('style').addClass('cp-app-drive-element-list'); + $thumb.addClass('cp-app-drive-element-grid cp-app-drive-element-thumbnail'); + thumbsUrls[element] = $thumb[0].src; + }); + } var type = Messages.type[hrefData.type] || hrefData.type; - common.displayThumbnail(href || data.roHref, data.channel, data.password, $span, function ($thumb) { - // Called only if the thumbnail exists - // Remove the .hide() added by displayThumnail() because it hides the icon in - // list mode too - $span.find('.cp-icon').removeAttr('style').addClass('cp-app-drive-element-list'); - $thumb.addClass('cp-app-drive-element-grid') - .addClass('cp-app-drive-element-thumbnail'); - }); var $type = $('', { 'class': 'cp-app-drive-element-type cp-app-drive-element-list' }).text(type); @@ -1715,7 +1727,7 @@ define([ var $cdate = $('', { 'class': 'cp-app-drive-element-ctime cp-app-drive-element-list' }).text(getDate(data.ctime)); - $span.append($type).append($adate).append($cdate); + $element.append($type).append($adate).append($cdate); }; var addFolderData = function (element, key, $span) { From aae71c43d2972dbac1833a1339e1f9470aafdcf3 Mon Sep 17 00:00:00 2001 From: ClemDee Date: Thu, 1 Aug 2019 14:18:17 +0200 Subject: [PATCH 3/7] Add possibility to drop files on the path bar to move them in drive --- www/drive/app-drive.less | 11 ++++++++--- www/drive/inner.js | 10 +++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/www/drive/app-drive.less b/www/drive/app-drive.less index c85cd3c88..f4415e7f6 100644 --- a/www/drive/app-drive.less +++ b/www/drive/app-drive.less @@ -30,6 +30,7 @@ @drive_content-bg-ro: darken(@drive_content-bg, 10%); @drive_selected-bg: #888; + @drive_droppable-bg: #FE9A2E; /* PAGE */ @@ -156,7 +157,7 @@ } .cp-app-drive-element-droppable { - background-color: #FE9A2E; + background-color: @drive_droppable-bg; color: #222; } @@ -939,6 +940,7 @@ overflow: hidden; text-overflow: ellipsis; transition: all 0.15s; + cursor: pointer; &:first-child { flex-shrink: 1; @@ -946,17 +948,20 @@ &.cp-app-drive-path-separator { color: #ccc; + cursor: default; } &.cp-app-drive-path-collapse { position: relative; } - &:hover { + &.cp-app-drive-element-droppable { + background-color: @drive_droppable-bg; + } + &:not(.cp-app-drive-element-droppable):hover { &:not(.cp-app-drive-path-separator) { background-color: darken(@colortheme_drive-bg, 15%); text-decoration: underline; - cursor: pointer; } & ~ .cp-app-drive-path-element { background-color: darken(@colortheme_drive-bg, 15%); diff --git a/www/drive/inner.js b/www/drive/inner.js index ae7dc60df..8c8b5a4bf 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1530,7 +1530,13 @@ define([ var findDropPath = function (target) { var $target = $(target); - var $el = findDataHolder($target); + var $el; + if ($target.is(".cp-app-drive-path-element")) { + $el = $target; + } + else { + $el = findDataHolder($target); + } var newPath = $el.data('path'); var dropEl = newPath && manager.find(newPath); if (newPath && manager.isSharedFolder(dropEl)) { @@ -1975,6 +1981,8 @@ define([ } else if (idx > 0 && manager.isFile(el)) { name = getElementName(path); } + $span.data("path", path.slice(0, idx + 1)); + addDragAndDropHandlers($span, path.slice(0, idx), true, true); if (idx === 0) { name = p === SHARED_FOLDER ? name : getPrettyName(p); } else { From 08c006e68236dc386da13962eca2922aaac3b96b Mon Sep 17 00:00:00 2001 From: ClemDee Date: Fri, 2 Aug 2019 12:21:37 +0200 Subject: [PATCH 4/7] Use Nacl to decode base64 thumbnail data --- www/common/common-thumbnail.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index 4dfab1457..62771223a 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -230,20 +230,15 @@ define([ if (!Visible.currently()) { to = window.setTimeout(interval, Thumb.UPDATE_FIRST); } }; - var stringToBlobToUrl = function (b64) { - var byteString = atob(b64.split(',')[1]); - var ab = new ArrayBuffer(byteString.length); - var ia = new Uint8Array(ab); - for (var i = 0; i < byteString.length; i++) { - ia[i] = byteString.charCodeAt(i); - } - var blob = new Blob([ab], {type: "image/png"}); - var url = URL.createObjectURL(blob); - return url; - }; + var addThumbnail = function (err, thumb, $span, cb) { + var u8 = Nacl.util.decodeBase64(thumb.split(',')[1]); + var blob = new Blob([u8], { + type: 'image/png' + }); + var url = URL.createObjectURL(blob); var img = new Image(); - img.src = stringToBlobToUrl(thumb); + img.src = url; $span.find('.cp-icon').hide(); $span.prepend(img); cb($(img)); From 925b147e6cb1b7c15800ad6ec1fca2e02672a0fe Mon Sep 17 00:00:00 2001 From: ClemDee Date: Fri, 2 Aug 2019 15:02:00 +0200 Subject: [PATCH 5/7] Fix contextmenu disappear on narrow layout --- www/drive/inner.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/www/drive/inner.js b/www/drive/inner.js index 8c8b5a4bf..a6f669d2e 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -43,7 +43,7 @@ define([ { var APP = window.APP = { editable: false, - mobile: function () { return $('body').width() <= 600; }, // Menu and content area are not inline-block anymore for mobiles + mobile: $('body').width() <= 600, // Menu and content area are not inline-block anymore for mobiles isMac: navigator.platform === "MacIntel", }; @@ -1339,7 +1339,14 @@ define([ }); }); $menu.css({ display: "block" }); - if (APP.mobile()) { return; } + if (APP.mobile) { + $menu.css({ + top: ($("#cp-app-drive-toolbar-context-mobile").offset().top + 32) + 'px', + right: '0px', + left: '' + }); + return; + } var h = $menu.outerHeight(); var w = $menu.outerWidth(); var wH = window.innerHeight; @@ -1945,7 +1952,7 @@ define([ var createTitle = function ($container, path, noStyle) { if (!path || path.length === 0) { return; } var isTrash = manager.isPathIn(path, [TRASH]); - if (APP.mobile() && !noStyle) { // noStyle means title in search result + if (APP.mobile && !noStyle) { // noStyle means title in search result return $container; } var isVirtual = virtualCategories.indexOf(path[0]) !== -1; @@ -3023,7 +3030,7 @@ define([ APP.resetTree(); if (displayedCategories.indexOf(SEARCH) !== -1 && $tree.find('#cp-app-drive-tree-search-input').length) { // in history mode we want to focus the version number input - if (!history.isHistoryMode && !APP.mobile()) { + if (!history.isHistoryMode && !APP.mobile) { var st = $tree.scrollTop() || 0; $tree.find('#cp-app-drive-tree-search-input').focus(); $tree.scrollTop(st); @@ -3066,7 +3073,7 @@ define([ createTitle($toolbar.find('.cp-app-drive-path'), path); - if (APP.mobile()) { + if (APP.mobile) { var $context = $('