diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index 34d44daf5..a6e5d5128 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -230,9 +230,15 @@ define([ if (!Visible.currently()) { to = window.setTimeout(interval, Thumb.UPDATE_FIRST); } }; + 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 = thumb.slice(0,5) === 'data:' ? thumb : 'data:image/png;base64,'+thumb; + img.src = url; $span.find('.cp-icon').hide(); $span.prepend(img); cb($(img)); diff --git a/www/drive/app-drive.less b/www/drive/app-drive.less index 70b03ae92..d04abbd08 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 */ @@ -157,7 +158,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 b5e851891..4bef3e827 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -41,7 +41,10 @@ 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: function () { + if (window.matchMedia) { return !window.matchMedia('(any-pointer:fine)').matches; } + else { return $('body').width() <= 600; } + }, isMac: navigator.platform === "MacIntel", }; @@ -297,6 +300,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', { @@ -563,7 +593,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(''); @@ -673,7 +704,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(); }); @@ -708,7 +741,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; } @@ -721,7 +756,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 : @@ -840,12 +875,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)) { @@ -867,14 +902,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(); }); } @@ -895,7 +937,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 @@ -1115,8 +1156,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 || $element.length === 0) { return paths; } + if (findSelectedElements().length > 1) { + var $selected = findSelectedElements(); $selected.each(function (idx, elmt) { var ePath = $(elmt).data('path'); if (ePath) { @@ -1145,7 +1187,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')); } @@ -1212,6 +1254,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 @@ -1248,23 +1291,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(); @@ -1298,7 +1341,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; @@ -1351,6 +1401,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) { @@ -1359,8 +1420,8 @@ define([ return false; } - if (!$element.hasClass('cp-app-drive-element-selected')) { - onElementClick(undefined, $element); + if (!isElementSelected($element)) { + selectElement($element); } paths = getSelectedPaths($element); @@ -1430,6 +1491,7 @@ define([ if (!res) { return; } manager.delete(pathsList, function () { pathsList.forEach(removeFoldersOpened); + removeSelected(); refresh(); }); }, null, true); @@ -1440,7 +1502,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) { @@ -1457,7 +1519,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 = [{ @@ -1476,7 +1538,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)) { @@ -1604,7 +1672,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); @@ -1613,7 +1682,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'}); @@ -1633,25 +1702,38 @@ 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'); + $(img).attr("draggable", false); + } + 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'); + $thumb.attr("draggable", false); + 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); @@ -1661,7 +1743,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) { @@ -1737,12 +1819,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) { @@ -1758,11 +1837,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')); @@ -1913,6 +1991,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 { @@ -2557,22 +2637,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'); @@ -2699,8 +2776,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 () { @@ -2791,7 +2868,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'); @@ -3015,7 +3092,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')); } @@ -3025,11 +3102,6 @@ define([ return; } // Open the menu - $('.cp-contextmenu').css({ - top: ($context.offset().top + 32) + 'px', - right: '0px', - left: '' - }); $li.contextmenu(); }); } else { @@ -3097,7 +3169,7 @@ define([ } });*/ - var $sel = $content.find('.cp-app-drive-element-selected'); + var $sel = findSelectedElements(); if ($sel.length) { $sel[0].scrollIntoView(); } elseĀ { @@ -3109,6 +3181,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 () { @@ -3459,6 +3534,7 @@ define([ if (!res) { return; } manager.delete(pathsList, function () { pathsList.forEach(removeFoldersOpened); + removeSelected(); refresh(); }); }); @@ -3705,16 +3781,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); } @@ -3726,10 +3801,8 @@ define([ e.preventDefault(); }); $appContainer.on('mouseup', function (e) { - //if (sel.down) { return; } if (e.which !== 1) { return ; } APP.hideMenu(e); - //removeSelected(e); }); $appContainer.on('click', function (e) { if (e.which !== 1) { return ; } @@ -3748,7 +3821,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]);