diff --git a/bower.json b/bower.json index af5560bf6..0cabd2b5f 100644 --- a/bower.json +++ b/bower.json @@ -42,7 +42,8 @@ "diff-dom": "2.1.1", "nthen": "^0.1.5", "open-sans-fontface": "^1.4.2", - "bootstrap-tokenfield": "^0.12.1" + "bootstrap-tokenfield": "^0.12.1", + "localforage": "^1.5.2" }, "resolutions": { "bootstrap": "v4.0.0-alpha.6" diff --git a/www/common/sframe-common-interface.js b/www/common/sframe-common-interface.js index 4393fd7c7..8d817995c 100644 --- a/www/common/sframe-common-interface.js +++ b/www/common/sframe-common-interface.js @@ -3,14 +3,19 @@ define([ '/api/config', '/common/cryptpad-common.js', '/common/common-util.js', + '/common/common-hash.js', '/common/media-tag.js', '/common/tippy.min.js', '/customize/application_config.js', + '/file/file-crypto.js', + '/bower_components/localforage/dist/localforage.min.js', + '/bower_components/tweetnacl/nacl-fast.min.js', 'css!/common/tippy.css', -], function ($, Config, Cryptpad, Util, MediaTag, Tippy, AppConfig) { +], function ($, Config, Cryptpad, Util, Hash, MediaTag, Tippy, AppConfig, FileCrypto, localForage) { var UI = {}; var Messages = Cryptpad.Messages; + var Nacl = window.nacl; /** * Requirements from cryptpad-common.js @@ -28,6 +33,40 @@ define([ * - createDropdown */ + var addThumbnail = function (err, thumb, $span, cb) { + var img = new Image(); + img.src = 'data:;base64,'+thumb; + $span.find('.cp-icon').hide(); + $span.prepend(img); + cb($(img)); + }; + UI.displayThumbnail = function (href, $container, cb) { + cb = cb || $.noop; + var parsed = Hash.parsePadUrl(href); + if (parsed.type !== 'file') { return; } + var k ='thumbnail-' + href; + var whenNewThumb = function () { + var secret = Hash.getSecrets('file', parsed.hash); + var hexFileName = Util.base64ToHex(secret.channel); + var src = Hash.getBlobPathFromHex(hexFileName); + var cryptKey = secret.keys && secret.keys.fileKeyStr; + var key = Nacl.util.decodeBase64(cryptKey); + FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { + if (!metadata.thumbnail) { + return void localForage.setItem(k, 'EMPTY'); + } + localForage.setItem(k, metadata.thumbnail, function (err) { + addThumbnail(err, metadata.thumbnail, $container, cb); + }); + }); + }; + localForage.getItem(k, function (err, v) { + if (!v) { return void whenNewThumb(); } + if (v === 'EMPTY') { return; } + addThumbnail(err, v, $container, cb); + }); + }; + UI.updateTags = function (common, href) { var sframeChan = common.getSframeChannel(); sframeChan.query('Q_TAGS_GET', href || null, function (err, res) { @@ -92,7 +131,7 @@ define([ target: data.target }; if (data.filter && !data.filter(file)) { - Cryptpad.log('TODO: invalid avatar (type or size)'); + Cryptpad.log('Invalid avatar (type or size)'); return; } data.FM.handleFile(file, ev); @@ -398,9 +437,6 @@ define([ }, LIMIT_REFRESH_RATE * 3); updateUsage(); - /*getProxy().on('change', ['drive'], function () { - updateUsage(); - }); TODO*/ cb(null, $container); }; diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 5a062a0f5..0b76dca49 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -80,6 +80,7 @@ define([ funcs.createButton = callWithCommon(UI.createButton); funcs.createUsageBar = callWithCommon(UI.createUsageBar); funcs.updateTags = callWithCommon(UI.updateTags); + funcs.displayThumbnail = UI.displayThumbnail; // History funcs.getHistory = callWithCommon(History.create); diff --git a/www/drive/app-drive.less b/www/drive/app-drive.less index 7b3adf77e..8def8763a 100644 --- a/www/drive/app-drive.less +++ b/www/drive/app-drive.less @@ -51,6 +51,7 @@ min-height: auto; text-overflow: ellipsis; padding-top: 5px; padding-bottom: 5px; + border: 1px solid transparent; &:not(.cp-app-drive-element-selected):not(.cp-app-drive-element-selected-tmp) { border: 1px solid #CCC; @@ -516,6 +517,13 @@ span { font-size: 18px; } } + .cp-app-drive-element-thumbnail { + max-width: 64px; + max-height: 64px; + & ~ .fa { + display: none; + } + } } .cp-app-drive-element-list { display: none; diff --git a/www/drive/inner.js b/www/drive/inner.js index 3f5320377..2c180edc9 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1159,11 +1159,18 @@ define([ // The element with the class '.name' is underlined when the 'li' is hovered var $name = $('', {'class': 'cp-app-drive-element-name'}).text(name); - $span.html(''); $span.append($name); $span.append($state); var type = Messages.type[hrefData.type] || hrefData.type; + common.displayThumbnail(data.href, $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); @@ -1181,7 +1188,6 @@ define([ var addFolderData = function (element, key, $span) { if (!element || !filesOp.isFolder(element)) { return; } - $span.html(''); // The element with the class '.name' is underlined when the 'li' is hovered var sf = filesOp.hasSubfolder(element); var files = filesOp.hasFile(element); @@ -1239,11 +1245,6 @@ define([ APP.selectedFiles.splice(idx, 1); } } - if (isFolder) { - addFolderData(element, key, $element); - } else { - addFileData(element, $element); - } $element.prepend($icon).dblclick(function () { if (isFolder) { APP.displayDirectory(newPath); @@ -1252,6 +1253,11 @@ define([ if (isTrash) { return; } openFile(root[key]); }); + if (isFolder) { + addFolderData(element, key, $element); + } else { + addFileData(element, $element); + } $element.addClass(liClass); $element.data('path', newPath); addDragAndDropHandlers($element, newPath, isFolder, !isTrash); @@ -1850,10 +1856,10 @@ define([ APP.selectedFiles.splice(sidx, 1); } } - addFileData(id, $element); $element.prepend($icon).dblclick(function () { openFile(id); }); + addFileData(id, $element); var path = [rootName, idx]; $element.data('path', path); $element.click(function(e) { @@ -1886,12 +1892,12 @@ define([ var $element = $('
  • ', { 'class': 'cp-app-drive-element cp-app-drive-element-row' + roClass }); - addFileData(id, $element); - $element.data('path', [FILES_DATA, id]); - $element.data('element', id); $element.prepend($icon).dblclick(function () { openFile(id); }); + addFileData(id, $element); + $element.data('path', [FILES_DATA, id]); + $element.data('element', id); $element.click(function(e) { e.stopPropagation(); onElementClick(e, $element); @@ -2018,10 +2024,10 @@ define([ var $element = $('
  • ', { 'class': 'cp-app-drive-element cp-app-drive-element-file cp-app-drive-element-row' + roClass, }); - addFileData(id, $element); $element.prepend($icon).dblclick(function () { openFile(id); }); + addFileData(id, $element); $element.data('path', path); $element.click(function(e) { e.stopPropagation(); diff --git a/www/filepicker/app-filepicker.less b/www/filepicker/app-filepicker.less index 2aca46cb5..69e92d69d 100644 --- a/www/filepicker/app-filepicker.less +++ b/www/filepicker/app-filepicker.less @@ -21,11 +21,15 @@ .cp-filepicker-content-element { @darker: darken(@colortheme_modal-fg, 30%); - width: 200px; - min-width: 200px; - height: 1em; - padding: 0.5em; + width: 125px; + //min-width: 200px; + //height: 1em; + padding: 10px; margin: 5px; + + display: inline-flex; + flex-flow: column; + box-sizing: content-box; text-align: left; @@ -41,15 +45,24 @@ color: @colortheme_modal-fg; } - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - align-items: center; + .cp-filepicker-content-element-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + height: 20px; + line-height: 20px; + margin-top: 5px; + max-width: 100%; + } .fa { cursor: pointer; - margin-right: 0.5em; + width: 100px; + height: 100px; + font-size: 70px; + text-align: center; + line-height: 100px; } } } diff --git a/www/filepicker/inner.js b/www/filepicker/inner.js index 29b6e4f3f..292ba32dc 100644 --- a/www/filepicker/inner.js +++ b/www/filepicker/inner.js @@ -114,6 +114,7 @@ define([ } var $container = $('', {'class': 'cp-filepicker-content'}).appendTo($block); + // Update the files list when needed updateContainer = function () { $container.html(''); @@ -132,10 +133,14 @@ define([ 'title': name, }).appendTo($container); $span.append(Cryptpad.getFileIcon(data)); - $span.append(name); + $('', {'class': 'cp-filepicker-content-element-name'}).text(name) + .appendTo($span); $span.click(function () { if (typeof onSelect === "function") { onSelect(data.href); } }); + + // Add thumbnail if it exists + common.displayThumbnail(data.href, $span); }); $input.focus(); };