Merge pull request #11573 from nextcloud/gridview-table

Files grid view
This commit is contained in:
Morris Jobke 2018-10-24 15:31:23 +02:00 committed by GitHub
commit 37782b1084
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 483 additions and 29 deletions

View File

@ -61,6 +61,16 @@ $application->registerRoutes(
'url' => '/api/v1/showhidden',
'verb' => 'POST'
],
[
'name' => 'API#showGridView',
'url' => '/api/v1/showgridview',
'verb' => 'POST'
],
[
'name' => 'API#getGridView',
'url' => '/api/v1/showgridview',
'verb' => 'GET'
],
[
'name' => 'view#index',
'url' => '/',

View File

@ -172,7 +172,7 @@ table th, table th a {
color: var(--color-text-maxcontrast);
}
table.multiselect th a {
color: #000;
color: var(--color-main-text);
}
table th .columntitle {
display: block;
@ -262,8 +262,7 @@ table.multiselect thead {
}
table.multiselect thead th {
background-color: rgba(255, 255, 255, 0.95); /* like controls bar */
color: #000;
background-color: var(--color-main-background);
font-weight: bold;
border-bottom: 0;
}
@ -595,7 +594,13 @@ a.action > img {
.summary {
opacity: .3;
/* add whitespace to bottom of files list to correctly show dropdowns */
height: 300px;
height: 250px;
}
/* Less whitespace needed on link share page
* as there is a footer and action menus have fewer entries.
*/
#body-public .summary {
height: 180px;
}
.summary:hover,
.summary:focus,
@ -723,3 +728,277 @@ table.dragshadow td.size {
height: 30px;
line-height: 30px;
}
/* GRID */
#filestable.view-grid:not(.hidden) {
$grid-size: 160px;
$grid-pad: 14px;
/* HEADER and MULTISELECT */
thead {
tr {
display: block;
border-bottom: 1px solid var(--color-border);
background-color: var(--color-main-background);
th {
width: auto;
border: none;
}
}
}
/* MAIN FILE LIST */
tbody {
display: grid;
grid-template-columns: repeat(auto-fill, $grid-size);
justify-content: space-around;
row-gap: 15px;
margin: 15px 0;
tr {
display: block;
position: relative;
height: $grid-size + 44px - $grid-pad;
border-radius: var(--border-radius);
&:hover, &:focus, &:active,
&.selected,
&.searchresult,
.name:focus,
&.highlighted {
background-color: transparent;
.thumbnail-wrapper,
.nametext,
.fileactions {
transition: background-color 0.3s ease;
background-color: var(--color-background-dark);
}
}
}
td {
display: inline;
border-bottom: none;
&.filename {
.thumbnail-wrapper {
min-width: 0;
max-width: none;
position: absolute;
width: $grid-size;
height: $grid-size;
padding: $grid-pad; // same as action icon bottom and right padding
top: 0;
left: 0;
z-index: -1; // make sure the default click is the link
.thumbnail {
width: calc(100% - 2 * #{$grid-pad});
height: calc(100% - 2 * #{$grid-pad}); //action icon padding
background-size: contain;
margin: 0;
border-radius: var(--border-radius);
background-repeat: no-repeat;
background-position: center;
/* Position favorite star related to checkbox to left and 3-dot menu below
* Position is inherited from the selection while in grid view
*/
.favorite-mark {
padding: $grid-pad;
left: auto;
top: -22px; // center in corner of thumbnail
right: -22px; // center in corner of thumbnail
}
}
}
.name {
height: 100%;
border-radius: var(--border-radius);
// since we're using thumbnail, name and actions bg
// we need to hide the overflow for the radius to show
// luckily the popovermenu is outside .name
overflow: hidden;
// we but the thumbnail in background to ensure
// the name is the default click handler
// force back the cursor which have been overrided
// and disabled for some reason...
cursor: pointer !important;
.nametext {
display: flex;
height: 44px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: $grid-size - $grid-pad;
padding-right: 0;
text-align: right;
line-height: 44px;
padding-left: $grid-pad; // same as action icon right padding
.innernametext {
display: inline-block;
max-width: 80px;
}
/* No space for extension in grid view */
.extension {
display: none;
}
}
.fileactions {
height: initial;
margin-top: $grid-size - $grid-pad;
display: flex;
align-items: center;
.action {
padding: $grid-pad;
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
&.action-share.permanent.shared-style span {
/* Do not show "Shared" text next to icon as there is no space */
&:not(.icon) {
display: none;
}
/* If an avatar is present, show that instead of the icon */
&.avatar {
display: inline-block;
position: absolute;
}
}
/* In "Deleted files", do not show "Restore" text next to icon as there is no space */
&.action-restore.permanent span {
&:not(.icon) {
display: none;
}
}
/* If there is a comment, show it instead of the share icon */
&.action-comment ~ .action-share {
display: none;
}
}
}
}
}
/* No space for filesize and date in grid view */
&.filesize,
&.date {
display: none;
}
&.selection,
&.filename .favorite-mark {
position: absolute;
top: -8px; // half the checkbox width, center on corner of thumbnail
left: -8px; // half the checkbox width, center on corner of thumbnail
display: flex;
width: 44px;
height: 44px;
z-index: 10;
background: transparent;
label {
width: 44px;
height: 44px;
display: inline-flex;
padding: $grid-pad; // like any action icon
&::before {
margin: 0;
width: $grid-pad; // 16px - border
height: $grid-pad; // 16px - border
}
}
}
/* Position actions menu below file */
.popovermenu {
left: 0;
width: $grid-size - 10px; // 2 * margin
margin: 0 5px;
/* Ellipsize long entries, normally menu width is adjusted but for grid we use fixed width. */
.menuitem span:not(.icon) {
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
/* Center align the footer file number & size summary */
tfoot {
display: grid;
.summary:not(.hidden) {
display: inline-block;
margin: 0 auto;
td {
padding-top: 50px;
&:first-child,
&.date {
display: none;
}
.info {
margin-left: 0;
}
}
}
}
}
/* Grid view toggle */
#view-toggle {
background-color: transparent;
border: none;
margin: 0;
padding: 22px;
opacity: .5;
&:hover,
&:focus,
#showgridview:focus + & {
opacity: 1;
}
}
/* Adjustments for link share page */
#body-public {
#filestable.view-grid:not(.hidden) tbody td {
/* More space for filename since there is no share icon */
&.filename .name .nametext .innernametext {
max-width: 124px;
}
/* Position actions menu correctly below 3-dot-menu */
.popovermenu {
left: -80px;
}
}
/* Right-align view toggle on link share page */
#view-toggle {
position: absolute;
right: 0;
}
}
/* Hide legacy Gallery toggle */
#gallery-button {
display: none;
}

View File

@ -331,6 +331,11 @@
this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));
// Toggle for grid view
this.$showGridView = $('input#showgridview');
this.$showGridView.on('change', _.bind(this._onGridviewChange, this));
$('#view-toggle').tooltip({placement: 'bottom', trigger: 'hover'});
this._onResize = _.debounce(_.bind(this._onResize, this), 250);
$('#app-content').on('appresized', this._onResize);
$(window).resize(this._onResize);
@ -591,6 +596,26 @@
this.$table.find('>thead').width($('#app-content').width() - OC.Util.getScrollBarWidth());
},
/**
* Toggle showing gridview by default or not
*
* @returns {undefined}
*/
_onGridviewChange: function() {
var show = this.$showGridView.is(':checked');
// only save state if user is logged in
if (OC.currentUser) {
$.post(OC.generateUrl('/apps/files/api/v1/showgridview'), {
show: show
});
}
this.$showGridView.next('#view-toggle')
.removeClass('icon-toggle-filelist icon-toggle-pictures')
.addClass(show ? 'icon-toggle-filelist' : 'icon-toggle-pictures')
$('.list-container').toggleClass('view-grid', show);
},
/**
* Event handler when leaving previously hidden state
*/
@ -2776,7 +2801,9 @@
var permissions = this.getDirectoryPermissions();
var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
this.$el.find('#emptycontent .uploadmessage').toggleClass('hidden', !isCreatable || !this.isEmpty);
this.$el.find('#filestable').toggleClass('hidden', this.isEmpty);
this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
},
/**

View File

@ -38,6 +38,7 @@ use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IRequest;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\Response;
use OCA\Files\Service\TagService;
@ -267,6 +268,28 @@ class ApiController extends Controller {
return new Response();
}
/**
* Toggle default for files grid view
*
* @NoAdminRequired
*
* @param bool $show
*/
public function showGridView($show) {
$this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_grid', (int)$show);
return new Response();
}
/**
* Get default settings for the grid view
*
* @NoAdminRequired
*/
public function getGridView() {
$status = $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'show_grid', '1') === '1';
return new JSONResponse(['gridview' => $status]);
}
/**
* Toggle default for showing/hiding xxx folder
*

View File

@ -22,11 +22,14 @@
*/
$config = \OC::$server->getConfig();
$userSession = \OC::$server->getUserSession();
// TODO: move this to the generated config.js
$publicUploadEnabled = $config->getAppValue('core', 'shareapi_allow_public_upload', 'yes');
$showgridview = $config->getUserValue($userSession->getUser()->getUID(), 'files', 'show_grid', true);
// renders the controls and table headers template
$tmpl = new OCP\Template('files', 'list', '');
$tmpl->assign('publicUploadEnabled', $publicUploadEnabled);
$tmpl->assign('showgridview', $showgridview);
$tmpl->printPage();

View File

@ -24,6 +24,10 @@
<?php endif;?>
<input type="hidden" class="max_human_file_size"
value="(max <?php isset($_['uploadMaxHumanFilesize']) ? p($_['uploadMaxHumanFilesize']) : ''; ?>)">
<input type="checkbox" class="hidden-visually" id="showgridview"
<?php if($_['showgridview']) { ?>checked="checked" <?php } ?>/>
<label id="view-toggle" for="showgridview" class="button <?php p($_['showgridview'] ? 'icon-toggle-filelist' : 'icon-toggle-pictures') ?>"
title="<?php p($l->t('Toggle grid view'))?>"></label>
</div>
<div id="emptycontent" class="hidden">
@ -38,7 +42,7 @@
<p></p>
</div>
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="32" data-preview-y="32">
<table id="filestable" class="list-container <?php p($_['showgridview'] ? 'view-grid' : '') ?>" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="250" data-preview-y="250">
<thead>
<tr>
<th id="headerSelection" class="hidden column-selection">

View File

@ -11,7 +11,7 @@
<p></p>
</div>
<table id="filestable">
<table id="filestable" class="list-container view-grid">
<thead>
<tr>
<th id='headerName' class="hidden column-name">

View File

@ -13,7 +13,7 @@
<h2><?php p($l->t('No entries found in this folder')); ?></h2>
<p></p>
</div>
<table id="filestable">
<table id="filestable" class="list-container view-grid">
<thead>
<tr>
<th id='headerName' class="hidden column-name">

View File

@ -25,7 +25,7 @@ describe('OCA.Files.FavoritesFileList tests', function() {
'</div>' +
// dummy table
// TODO: at some point this will be rendered by the fileList class itself!
'<table id="filestable">' +
'<table id="filestable" class="list-container view-grid">' +
'<thead><tr>' +
'<th id="headerName" class="hidden column-name">' +
'<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' +

View File

@ -117,7 +117,7 @@ describe('OC.Upload tests', function() {
beforeEach(function() {
$('#testArea').append(
'<div id="tableContainer">' +
'<table id="filestable">' +
'<table id="filestable" class="list-container view-grid">' +
'<thead><tr>' +
'<th id="headerName" class="hidden column-name">' +
'<input type="checkbox" id="select_all_files" class="select-all">' +

View File

@ -28,7 +28,7 @@ describe('OCA.Files.FileActions tests', function() {
var $body = $('#testArea');
$body.append('<input type="hidden" id="dir" value="/subdir"></input>');
$body.append('<input type="hidden" id="permissions" value="31"></input>');
$body.append('<table id="filestable"><tbody id="fileList"></tbody></table>');
$body.append('<table id="filestable" class="list-container view-grid"><tbody id="fileList"></tbody></table>');
// dummy files table
fileActions = new OCA.Files.FileActions();
fileActions.registerAction({

View File

@ -88,7 +88,7 @@ describe('OCA.Files.FileList tests', function() {
'<input type="file" id="file_upload_start" name="files[]" multiple="multiple">' +
// dummy table
// TODO: at some point this will be rendered by the fileList class itself!
'<table id="filestable">' +
'<table id="filestable" class="list-container view-grid">' +
'<thead><tr>' +
'<th id="headerName" class="hidden column-name">' +
'<input type="checkbox" id="select_all_files" class="select-all checkbox">' +

View File

@ -11,7 +11,7 @@
<input type="hidden" name="dir" value="" id="dir">
<table id="filestable">
<table id="filestable" class="list-container view-grid">
<thead>
<tr>
<th id='headerName' class="hidden column-name">

View File

@ -1,5 +1,4 @@
#preview {
background: var(--color-main-background);
text-align: center;
}

View File

@ -229,10 +229,10 @@ OCA.Sharing.PublicApp = {
this.fileList.generatePreviewUrl = function (urlSpec) {
urlSpec = urlSpec || {};
if (!urlSpec.x) {
urlSpec.x = 32;
urlSpec.x = this.$table.data('preview-x') || 32;
}
if (!urlSpec.y) {
urlSpec.y = 32;
urlSpec.y = this.$table.data('preview-y') || 32;
}
urlSpec.x *= window.devicePixelRatio;
urlSpec.y *= window.devicePixelRatio;

View File

@ -357,6 +357,7 @@ class ShareController extends AuthPublicShareController {
$folder->assign('isPublic', true);
$folder->assign('hideFileList', $hideFileList);
$folder->assign('publicUploadEnabled', 'no');
$folder->assign('showgridview', true);
$folder->assign('uploadMaxFilesize', $maxUploadFilesize);
$folder->assign('uploadMaxHumanFilesize', \OCP\Util::humanFileSize($maxUploadFilesize));
$folder->assign('freeSpace', $freeSpace);

View File

@ -10,7 +10,7 @@
<h2><?php p($l->t('No entries found in this folder')); ?></h2>
</div>
<table id="filestable">
<table id="filestable" class="list-container view-grid">
<thead>
<tr>
<th id='headerName' class="hidden column-name">

View File

@ -60,7 +60,7 @@ describe('OCA.Sharing.PublicApp tests', function() {
'<input type="file" id="file_upload_start" name="files[]" multiple="multiple">' +
// dummy table
// TODO: at some point this will be rendered by the fileList class itself!
'<table id="filestable">' +
'<table id="filestable" class="list-container view-grid">' +
'<thead><tr>' +
'<th id="headerName" class="hidden column-name">' +
'<input type="checkbox" id="select_all_files" class="select-all">' +

View File

@ -28,7 +28,7 @@ describe('OCA.Sharing.Util tests', function() {
// dummy file list
var $div = $(
'<div id="listContainer">' +
'<table id="filestable">' +
'<table id="filestable" class="list-container view-grid">' +
'<thead></thead>' +
'<tbody id="fileList"></tbody>' +
'</table>' +
@ -510,8 +510,8 @@ describe('OCA.Sharing.Util tests', function() {
OCA.Sharing.Util.attach(fileList);
fileList.setFiles(testFiles);
});
afterEach(function() {
shareTabSpy.restore();
afterEach(function() {
shareTabSpy.restore();
});
it('updates fileInfoModel when shares changed', function() {

View File

@ -28,7 +28,7 @@ describe('OCA.Sharing.FileList tests', function() {
'</div>' +
// dummy table
// TODO: at some point this will be rendered by the fileList class itself!
'<table id="filestable">' +
'<table id="filestable" class="list-container view-grid">' +
'<thead><tr>' +
'<th id="headerName" class="hidden column-name">' +
'<input type="checkbox" id="select_all_files" class="select-all">' +
@ -701,7 +701,7 @@ describe('OCA.Sharing.FileList tests', function() {
// dummy file list
var $div = $(
'<div>' +
'<table id="filestable">' +
'<table id="filestable" class="list-container view-grid">' +
'<thead></thead>' +
'<tbody id="fileList"></tbody>' +
'</table>' +

View File

@ -18,7 +18,7 @@
<p></p>
</div>
<table id="filestable">
<table id="filestable" class="list-container view-grid">
<thead>
<tr>
<th id="headerSelection" class="hidden column-selection">

View File

@ -48,7 +48,7 @@ describe('OCA.Trashbin.FileList tests', function () {
'</div>' +
// dummy table
// TODO: at some point this will be rendered by the fileList class itself!
'<table id="filestable">' +
'<table id="filestable" class="list-container view-grid">' +
'<thead><tr><th id="headerName" class="hidden">' +
'<input type="checkbox" id="select_all_trash" class="select-all">' +
'<span class="name">Name</span>' +

View File

@ -5,6 +5,7 @@
:root {
--color-main-text: $color-main-text;
--color-main-background: $color-main-background;
--color-main-background-translucent: $color-main-background-translucent;
--color-background-dark: $color-background-dark;
--color-background-darker: $color-background-darker;

View File

@ -297,6 +297,7 @@ img, object, video, button, textarea, input, select, div[contenteditable='true']
@include icon-black-white('toggle', 'actions', 1, true);
@include icon-black-white('toggle-background', 'actions', 1, true);
@include icon-black-white('toggle-pictures', 'actions', 1, true);
@include icon-black-white('toggle-filelist', 'actions', 1, true);
@include icon-black-white('triangle-e', 'actions', 1, true);
@include icon-black-white('triangle-n', 'actions', 1, true);
@include icon-black-white('triangle-s', 'actions', 1, true);

View File

@ -167,7 +167,7 @@ body {
height: 44px;
padding: 0;
margin: 0;
background-color: var(--color-main-background);
background-color: var(--color-main-background-translucent);
z-index: 60;
-webkit-user-select: none;
-moz-user-select: none;
@ -697,12 +697,15 @@ code {
}
/* ---- DIALOGS ---- */
#oc-dialog-filepicker-content {
position: relative;
.dirtree {
width: 96%;
width: 100%;
flex-wrap: wrap;
padding-left: 12px;
padding-right: 44px;
box-sizing: border-box;
div:first-child a {
background-image: url('../img/places/home.svg?v=1');
@ -722,6 +725,28 @@ code {
}
}
}
/* Grid view toggle */
#picker-view-toggle {
position: absolute;
background-color: transparent;
border: none;
margin: 0;
padding: 22px;
opacity: .5;
right: 0;
top: 0;
&:hover,
&:focus {
opacity: 1;
}
}
// keyboard focus
#picker-showgridview:focus + #picker-view-toggle {
opacity: 1;
}
.filelist-container {
box-sizing: border-box;
display: inline-block;
@ -771,9 +796,54 @@ code {
.filesize {
text-align: right;
}
&.view-grid {
$grid-size: 120px;
$grid-pad: 10px;
$name-height: 20px;
display: flex;
flex-direction: column;
tbody {
display: grid;
grid-template-columns: repeat(auto-fill, $grid-size);
justify-content: space-around;
row-gap: 15px;
margin: 15px 0;
tr {
display: block;
position: relative;
border-radius: var(--border-radius);
padding: $grid-pad;
display: flex;
flex-direction: column;
width: $grid-size - 2 * $grid-pad;
td {
border: none;
padding: 0;
&.filename {
padding: #{$grid-size - 2 * $grid-pad} 0 0 0;
background-position: center top;
background-size: contain;
line-height: $name-height;
height: $name-height;
}
&.filesize {
line-height: $name-height;
text-align: left;
}
&.date {
display: none;
}
}
}
}
}
}
.filepicker_element_selected {
background-color: lightblue;
background-color: var(--color-background-darker);
}
}

View File

@ -33,6 +33,7 @@
// DEPRECATED, please use CSS4 vars
$color-main-text: #222 !default; // Not #000 for better readability
$color-main-background: #fff !default;
$color-main-background-translucent: rgba($color-main-background, .97) !default;
// used for different active/disabled states
$color-background-dark: nc-darken($color-main-background, 7%) !default;

View File

@ -208,6 +208,7 @@ var OCdialogs = {
this.filepicker.loading = true;
this.filepicker.filesClient = (OCA.Sharing && OCA.Sharing.PublicApp && OCA.Sharing.PublicApp.fileList)? OCA.Sharing.PublicApp.fileList.filesClient: OC.Files.getClient();
$.when(this._getFilePickerTemplate()).then(function($tmpl) {
self.filepicker.loading = false;
var dialogName = 'oc-dialog-filepicker-content';
@ -237,6 +238,11 @@ var OCdialogs = {
$('body').append(self.$filePicker);
self.$showGridView = $('input#picker-showgridview');
self.$showGridView.on('change', _.bind(self._onGridviewChange, self));
self._getGridSettings();
self.$filePicker.ready(function() {
self.$filelist = self.$filePicker.find('.filelist tbody');
self.$dirTree = self.$filePicker.find('.dirtree');
@ -779,6 +785,31 @@ var OCdialogs = {
//}
return dialogDeferred.promise();
},
// get the gridview setting and set the input accordingly
_getGridSettings: function() {
var self = this;
$.get(OC.generateUrl('/apps/files/api/v1/showgridview'), function(response) {
self.$showGridView.checked = response.gridview;
self.$showGridView.next('#picker-view-toggle')
.removeClass('icon-toggle-filelist icon-toggle-pictures')
.addClass(response.gridview ? 'icon-toggle-filelist' : 'icon-toggle-pictures')
$('.list-container').toggleClass('view-grid', response.gridview);
});
},
_onGridviewChange: function() {
var show = this.$showGridView.is(':checked');
// only save state if user is logged in
if (OC.currentUser) {
$.post(OC.generateUrl('/apps/files/api/v1/showgridview'), {
show: show
});
}
this.$showGridView.next('#picker-view-toggle')
.removeClass('icon-toggle-filelist icon-toggle-pictures')
.addClass(show ? 'icon-toggle-filelist' : 'icon-toggle-pictures')
$('.list-container').toggleClass('view-grid', show);
},
_getFilePickerTemplate: function() {
var defer = $.Deferred();
if(!this.$filePickerTemplate) {
@ -899,6 +930,8 @@ var OCdialogs = {
if (entry.type === 'file') {
var urlSpec = {
file: dir + '/' + entry.name,
x: 100,
y: 100
};
var img = new Image();
var previewUrl = OC.generateUrl('/core/preview.png?') + $.param(urlSpec);

View File

@ -1,11 +1,13 @@
<div id="{dialog_name}" title="{title}">
<span class="dirtree breadcrumb"></span>
<input type="checkbox" class="hidden-visually" id="picker-showgridview" checked="checked" />
<label id="picker-view-toggle" for="picker-showgridview" class="button icon-toggle-filelist"></label>
<div class="filelist-container">
<div class="emptycontent">
<div class="icon-folder"></div>
<h2>{emptytext}</h2>
</div>
<table id="filestable" class="filelist">
<table id="filestable" class="filelist list-container view-grid">
<tbody>
<tr data-entryname="{filename}" data-type="{type}">
<td class="filename"