From 16a1044cba6ea4037dab1b9c1bd7589b995f49bc Mon Sep 17 00:00:00 2001
From: Jan-Christoph Borchardt <hey@jancborchardt.net>
Date: Tue, 4 Sep 2018 16:18:35 +0200
Subject: [PATCH 1/2] Basic start of grid view, move files from table to divs

Signed-off-by: Jan-Christoph Borchardt <hey@jancborchardt.net>
---
 apps/files/css/files.scss     | 198 +++++++++++++++++++---------------
 apps/files/js/filelist.js     |  86 +++++++--------
 apps/files/templates/list.php |  36 +++----
 3 files changed, 172 insertions(+), 148 deletions(-)

diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss
index c41255b9868bf..0a03a2e6b1b4c 100644
--- a/apps/files/css/files.scss
+++ b/apps/files/css/files.scss
@@ -52,8 +52,10 @@
 	min-width: 500px;
 }
 
-#filestable tbody tr {
+#filestable .list-element {
 	height: 51px;
+	display: flex;
+	flex-direction: row;
 }
 
 /* fit app list view heights */
@@ -65,7 +67,9 @@
 	transition: background-color 0.3s ease;
 }
 
-.file-drag, .file-drag #filestable tbody tr, .file-drag #filestable tbody tr:hover {
+.file-drag,
+.file-drag #filestable .list-element,
+.file-drag #filestable .list-element:hover {
 	transition: background-color 0.3s ease!important;
 	background-color: rgb(179, 230, 255) !important;
 }
@@ -74,11 +78,12 @@
 	background-color: var(--color-main-background) !important;
 }
 
-.file-drag #filestable tbody tr, .file-drag #filestable tbody tr:hover{
+.file-drag #filestable .list-element,
+.file-drag #filestable .list-element:hover{
 	background-color: transparent !important;
 }
 
-.app-files #app-content.dir-drop #filestable tbody tr.dropping-to-dir{
+.app-files #app-content.dir-drop #filestable .list-element.dropping-to-dir{
 	background-color: rgb(179, 230, 255) !important;
 }
 
@@ -130,49 +135,52 @@
 	opacity: 0.3;
 }
 
-#filestable tbody tr {
+#filestable .list-element {
 	transition: background-color 0.3s ease;
-	height: 40px;
-}
-#filestable tbody tr:hover,
-#filestable tbody tr:focus,
-#filestable tbody .name:focus,
-#filestable tbody tr:active,
-#filestable tbody tr.highlighted,
-#filestable tbody tr.highlighted .name:focus,
-#filestable tbody tr.selected,
-#filestable tbody tr.searchresult,
-table tr.mouseOver td {
+}
+#filestable .list-element:hover,
+#filestable .list-element:focus,
+#filestable .name:focus,
+#filestable .list-element:active,
+#filestable .list-element.highlighted,
+#filestable .list-element.highlighted .name:focus,
+#filestable .list-element.selected,
+#filestable .list-element.searchresult,
+#filestable .list-element.mouseOver div {
 	transition: background-color 0.3s ease;
 	background-color: var(--color-background-dark);
 }
-tbody a { color: var(--color-main-text); }
+#filestable a { color: var(--color-main-text); }
 
-span.conflict-path, span.extension, span.uploading, td.date {
+#filestable .conflict-path,
+#filestable .extension,
+#filestable .uploading,
+#filestable .date {
 	color: var(--color-text-maxcontrast);
 }
-span.conflict-path, span.extension {
+#filestable .conflict-path,
+#filestable .extension {
 	-webkit-transition: opacity 300ms;
 	-moz-transition: opacity 300ms;
 	-o-transition: opacity 300ms;
 	transition: opacity 300ms;
 	vertical-align: top;
 }
-tr:hover span.conflict-path,
-tr:focus span.conflict-path,
-tr:hover span.extension,
-tr:focus span.extension {
+.list-element:hover .conflict-path,
+.list-element:focus .conflict-path,
+.list-element:hover .extension,
+.list-element:focus .extension {
 	opacity: 1;
 	color: var(--color-text-maxcontrast);
 }
 
-table th, table th a {
+#filestable .list-column, #filestable .list-column a {
 	color: var(--color-text-maxcontrast);
 }
-table.multiselect th a {
+#filestable.multiselect .list-header a {
 	color: #000;
 }
-table th .columntitle {
+#filestable .list-column .columntitle {
 	display: block;
 	padding: 15px;
 	height: 50px;
@@ -180,15 +188,15 @@ table th .columntitle {
 	-moz-box-sizing: border-box;
 	vertical-align: middle;
 }
-table.multiselect th .columntitle {
+#filestable.multiselect .list-header .columntitle {
     display: inline-block;
 }
-table th .columntitle.name {
+#filestable .list-column .columntitle.name {
 	padding-left: 5px;
 	margin-left: 50px;
 }
 
-table th .sort-indicator {
+#filestable .list-column .sort-indicator {
 	width: 10px;
 	height: 8px;
 	margin-left: 5px;
@@ -198,31 +206,31 @@ table th .sort-indicator {
 }
 .sort-indicator.hidden,
 .multiselect .sort-indicator,
-table.multiselect th:hover .sort-indicator.hidden,
-table.multiselect th:focus .sort-indicator.hidden {
+#filestable.multiselect .list-header div:hover .sort-indicator.hidden,
+#filestable.multiselect .list-header div:focus .sort-indicator.hidden {
 	visibility: hidden;
 }
 .multiselect .sort, .multiselect .sort span {
 	cursor: default;
 }
-table th:hover .sort-indicator.hidden,
-table th:focus .sort-indicator.hidden {
+#filestable .list-column:hover .sort-indicator.hidden,
+#filestable .list-column:focus .sort-indicator.hidden {
 	visibility: visible;
 }
 
-table th,
-table td {
+#filestable .list-column,
+#filestable .list-element {
 	border-bottom: 1px solid var(--color-border);
 	text-align: left;
 	font-weight: normal;
 }
-table td {
+#filestable .list-element {
 	padding: 0 15px;
 	font-style: normal;
 	background-position: 8px center;
 	background-repeat: no-repeat;
 }
-table th#headerName {
+#filestable .list-column#headerName {
 	position: relative;
 	width: 9999px; /* not really sure why this works better than 100% … table styling */
 	padding: 0;
@@ -233,11 +241,16 @@ table th#headerName {
 	height: 50px;
 }
 
-table th#headerSize, table td.filesize {
+#filestable .list-column#headerSize,
+#filestable .list-element .filesize {
 	text-align: right;
+	white-space: nowrap;
+	padding: 15px;
 }
-table th#headerDate, table td.date,
-table th.column-last, table td.column-last {
+#filestable .list-column#headerDate,
+#filestable .list-element .date,
+#filestable .list-column.column-last,
+#filestable .list-element.column-last {
 	-moz-box-sizing: border-box;
 	box-sizing: border-box;
 	position: relative;
@@ -250,7 +263,7 @@ table th.column-last, table td.column-last {
 #filestable.multiselect {
 	top: 51px;
 }
-table.multiselect thead {
+#filestable.multiselect .list-header {
 	position: fixed;
 	top: 94px;
 	z-index: 55;
@@ -259,32 +272,31 @@ table.multiselect thead {
 	left: $navigation-width;
 }
 
-table.multiselect thead th {
+#filestable.multiselect .list-header div {
 	background-color: rgba(255, 255, 255, 0.95); /* like controls bar */
 	color: #000;
 	font-weight: bold;
 	border-bottom: 0;
 }
 
-#app-content.with-app-sidebar table.multiselect thead{
+#app-content.with-app-sidebar #filestable.multiselect .list-header {
 	margin-right: 27%;
 }
 
-table.multiselect #headerName {
+#filestable.multiselect #headerName {
 	position: relative;
 	width: 9999px; /* when we use 100%, the styling breaks on mobile … table styling */
 }
-table.multiselect #modified {
+#filestable.multiselect #modified {
 	display: none;
 }
 
-table td.selection,
-table th.selection,
-table td.fileaction {
+#filestable .selection,
+#filestable .fileaction {
 	width: 32px;
 	text-align: center;
 }
-table td.filename a.name {
+#filestable .filename a.name {
 	display: flex;
 	position:relative; /* Firefox needs to explicitly have this default set … */
 	-moz-box-sizing: border-box;
@@ -293,14 +305,14 @@ table td.filename a.name {
 	line-height: 50px;
 	padding: 0;
 }
-table td.filename .thumbnail-wrapper {
+#filestable .filename .thumbnail-wrapper {
 	/* we need this to make sure flex is working inside a table cell */
 	width: 0;
 	min-width: 50px;
 	max-width: 50px;
 	height: 50px;
 }
-table td.filename .thumbnail-wrapper.icon-loading-small {
+#filestable .filename .thumbnail-wrapper.icon-loading-small {
 	&:after {
 		z-index: 10;
 	}
@@ -308,7 +320,7 @@ table td.filename .thumbnail-wrapper.icon-loading-small {
 		opacity: 0.2;
 	}
 }
-table td.filename .thumbnail {
+#filestable .filename .thumbnail {
 	display: inline-block;
 	width: 32px;
 	height: 32px;
@@ -319,17 +331,30 @@ table td.filename .thumbnail {
 	position: absolute;
 	z-index: 4;
 }
-table td.filename input.filename {
+#filestable .filename input.filename {
 	width: 70%;
 	margin-top: 9px;
 	margin-left: 48px;
 	cursor: text;
 }
 
-table td.filename a, table td.login, table td.logout, table td.download, table td.upload, table td.create, table td.delete { padding:3px 8px 8px 3px; }
-table td.filename .nametext, .modified, .column-last>span:first-child { float:left; padding:15px 0; }
+#filestable .list-element.filename a,
+#filestable .list-element.login,
+#filestable .list-element.logout,
+#filestable .list-element.download,
+#filestable .list-element.upload,
+#filestable .list-element.create,
+#filestable .list-element.delete {
+	padding: 3px 8px 8px 3px;
+}
+#filestable .list-element.filename .nametext,
+#filestable .modified,
+#filestable .column-last>span:first-child {
+	float: left;
+	padding: 15px 0;
+}
 
-.modified, .column-last>span:first-child {
+#filestable .modified, .column-last>span:first-child {
 	position: relative;
 	overflow: hidden;
 	text-overflow: ellipsis;
@@ -337,9 +362,8 @@ table td.filename .nametext, .modified, .column-last>span:first-child { float:le
 }
 
 /* TODO fix usability bug (accidental file/folder selection) */
-table {
-	td.filename {
-		max-width: 0;
+#filestable {
+	.filename {
 		.nametext {
 			width: 0;
 			flex-grow: 1;
@@ -360,22 +384,22 @@ table {
 	}
 }
 
-.hide-hidden-files #fileList tr.hidden-file,
-.hide-hidden-files #fileList tr.hidden-file.dragging {
+.hide-hidden-files #fileList .list-element.hidden-file,
+.hide-hidden-files #fileList .list-element.hidden-file.dragging {
 	display: none;
 }
 
-#fileList tr.animate-opacity {
+#fileList .animate-opacity {
 	-webkit-transition:opacity 250ms;
 	-moz-transition:opacity 250ms;
 	-o-transition:opacity 250ms;
 	transition:opacity 250ms;
 }
-#fileList tr.dragging {
+#fileList .dragging {
 	opacity: 0.2;
 }
 
-table td.filename .nametext .innernametext {
+#filestable .filename .nametext .innernametext {
 	text-overflow: ellipsis;
 	overflow: hidden;
 	position: relative;
@@ -384,7 +408,7 @@ table td.filename .nametext .innernametext {
 
 /* for smaller resolutions - see mobile.css */
 
-table td.filename .uploadtext {
+#filestable .filename .uploadtext {
 	font-weight: normal;
 	margin-left: 55px;
 	margin-top: 5px;
@@ -394,30 +418,30 @@ table td.filename .uploadtext {
 	opacity: .5;
 }
 
-table td.selection {
+#filestable .selection {
 	padding: 0;
 }
 
 /* File checkboxes */
-#fileList tr td.selection>.selectCheckBox + label:before {
+#fileList .selection>.selectCheckBox + label:before {
 	opacity: 0.3;
 }
 
 /* Show checkbox with full opacity when hovering, checked, or selected */
-#fileList tr:hover td.selection>.selectCheckBox + label:before,
-#fileList tr:focus td.selection>.selectCheckBox + label:before,
-#fileList tr td.selection>.selectCheckBox:checked + label:before,
-#fileList tr.selected td.selection>.selectCheckBox + label:before {
+#fileList .list-element:hover .selection>.selectCheckBox + label:before,
+#fileList .list-element:focus .selection>.selectCheckBox + label:before,
+#fileList .list-element .selection>.selectCheckBox:checked + label:before,
+#fileList .list-element.selected .selection>.selectCheckBox + label:before {
 	opacity: 1;
 }
 
 /* Use label to have bigger clickable size for checkbox */
-#fileList tr td.selection>.selectCheckBox + label,
+#fileList .selection>.selectCheckBox + label,
 .select-all + label {
 	padding: 16px;
 }
 
-#fileList tr td.filename {
+#fileList .filename {
 	position: relative;
 	width: 100%;
 	padding-left: 0;
@@ -425,17 +449,17 @@ table td.selection {
 	-webkit-transition:background-image 500ms; -moz-transition:background-image 500ms; -o-transition:background-image 500ms; transition:background-image 500ms;
 }
 
-#fileList tr td.filename a.name label {
+#fileList .filename a.name label {
 	position: absolute;
 	width: 80%;
 	height: 50px;
 }
 
-#fileList tr td.filename .favorite {
+#fileList .filename .favorite {
 	display: inline-block;
 	float: left;
 }
-#fileList tr td.filename .favorite-mark {
+#fileList .filename .favorite-mark {
 	position: absolute;
 	display: block;
 	top: -6px;
@@ -506,7 +530,7 @@ a.action > img {
 	margin-top: -3px;
 }
 
-#fileList td a {
+#fileList .list-element a {
 	a.action {
 		display: inline;
 		padding: 17px 8px;
@@ -577,16 +601,16 @@ a.action > img {
 	opacity: 1;
 }
 
-#fileList tr a.action.disabled {
+#fileList .list-element a.action.disabled {
 	background: none;
 }
 
 #selectedActionsList a.download.disabled,
-#fileList tr a.action.action-download.disabled {
+#fileList .list-element a.action.action-download.disabled {
 	color: #000000;
 }
 
-#fileList tr:hover a.action.disabled:hover * {
+#fileList .list-element:hover a.action.disabled:hover * {
 	cursor: default;
 }
 
@@ -598,23 +622,23 @@ a.action > img {
 .summary:hover,
 .summary:focus,
 .summary,
-table tr.summary td {
+#filestable .list-element.summary div {
 	background-color: transparent;
 }
-.summary td {
+.summary div {
 	border-bottom: none;
 	vertical-align: top;
 	padding-top: 20px;
 }
 .summary .info {
-	margin-left: 35px; /* td has padding of 15, col width is 50 */
+	margin-left: 35px; /* element has padding of 15, col width is 50 */
 }
 
-table.dragshadow {
+#filestable.dragshadow {
 	width:auto;
 	z-index: 2000;
 }
-table.dragshadow td.filename {
+#filestable.dragshadow .filename {
 	padding-left:60px;
 	padding-right:16px;
 	height: 36px;
@@ -622,7 +646,7 @@ table.dragshadow td.filename {
 	/* Override "max-width: 0" to prevent file name and size from overlapping */
 	max-width: unset;
 }
-table.dragshadow td.size {
+#filestable.dragshadow .size {
 	padding-right:8px;
 }
 .mask {
@@ -696,7 +720,7 @@ table.dragshadow td.size {
 }
 
 .breadcrumb .canDrop > a,
-#filestable tbody tr.canDrop {
+#filestable .list-element.canDrop {
 	background-color: rgb(179, 230, 255);
 }
 
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 8fb8a02181127..7f1e937b3727b 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -254,7 +254,7 @@
 				this.id = options.id;
 			}
 			this.$container = options.scrollContainer || $(window);
-			this.$table = $el.find('table:first');
+			this.$table = $el.find('#filestable:first');
 			this.$fileList = $el.find('#fileList');
 
 			if (!_.isUndefined(this._filesConfig)) {
@@ -314,10 +314,10 @@
 			if (this._folderDropOptions) {
 				breadcrumbOptions.onDrop = _.bind(this._onDropOnBreadCrumb, this);
 				breadcrumbOptions.onOver = function() {
-					self.$el.find('td.filename.ui-droppable').droppable('disable');
+					self.$el.find('.filename.ui-droppable').droppable('disable');
 				}
 				breadcrumbOptions.onOut = function() {
-					self.$el.find('td.filename.ui-droppable').droppable('enable');
+					self.$el.find('.filename.ui-droppable').droppable('enable');
 				}
 			}
 			this.breadcrumb = new OCA.Files.BreadCrumb(breadcrumbOptions);
@@ -330,7 +330,7 @@
 
 			this._renderNewButton();
 
-			this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));
+			this.$el.find('.list-header .columntitle').click(_.bind(this._onClickHeader, this));
 
 			this._onResize = _.debounce(_.bind(this._onResize, this), 250);
 			$('#app-content').on('appresized', this._onResize);
@@ -340,9 +340,9 @@
 
 			this.updateSearch();
 
-			this.$fileList.on('click','td.filename>a.name, td.filesize, td.date', _.bind(this._onClickFile, this));
+			this.$fileList.on('click','.filename>a.name, .filesize, .date', _.bind(this._onClickFile, this));
 
-			this.$fileList.on('change', 'td.selection>.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
+			this.$fileList.on('change', '.selection>.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
 			this.$el.on('show', _.bind(this._onShow, this));
 			this.$el.on('urlChanged', _.bind(this._onUrlChanged, this));
 			this.$el.find('.select-all').click(_.bind(this._onClickSelectAll, this));
@@ -617,7 +617,7 @@
 		 * @param {bool} state true to select, false to deselect
 		 */
 		_selectFileEl: function($tr, state, showDetailsView) {
-			var $checkbox = $tr.find('td.selection>.selectCheckBox');
+			var $checkbox = $tr.find('.selection>.selectCheckBox');
 			var oldData = !!this._selectedFiles[$tr.data('id')];
 			var data;
 			$checkbox.prop('checked', state);
@@ -646,7 +646,7 @@
 		 * Event handler for when clicking on files to select them
 		 */
 		_onClickFile: function(event) {
-			var $tr = $(event.target).closest('tr');
+			var $tr = $(event.target).closest('.list-element');
 			if ($tr.hasClass('dragging')) {
 				return;
 			}
@@ -656,7 +656,7 @@
 					var $lastTr = $(this._lastChecked);
 					var lastIndex = $lastTr.index();
 					var currentIndex = $tr.index();
-					var $rows = this.$fileList.children('tr');
+					var $rows = this.$fileList.children('.list-element');
 
 					// last clicked checkbox below current one ?
 					if (lastIndex > currentIndex) {
@@ -673,7 +673,7 @@
 				else {
 					this._lastChecked = $tr;
 				}
-				var $checkbox = $tr.find('td.selection>.selectCheckBox');
+				var $checkbox = $tr.find('.selection>.selectCheckBox');
 				this._selectFileEl($tr, !$checkbox.prop('checked'));
 				this.updateSelectionSummary();
 			} else {
@@ -682,7 +682,7 @@
 					var filename = $tr.attr('data-file');
 					var renaming = $tr.data('renaming');
 					if (!renaming) {
-						this.fileActions.currentFile = $tr.find('td');
+						this.fileActions.currentFile = $tr.find('.filename');
 						var mime = this.fileActions.getCurrentMimeType();
 						var type = this.fileActions.getCurrentType();
 						var permissions = this.fileActions.getCurrentPermissions();
@@ -709,7 +709,7 @@
 					// the details to be shown.
 					event.preventDefault();
 					var filename = $tr.attr('data-file');
-					this.fileActions.currentFile = $tr.find('td');
+					this.fileActions.currentFile = $tr.find('.filename');
 					var mime = this.fileActions.getCurrentMimeType();
 					var type = this.fileActions.getCurrentType();
 					var permissions = this.fileActions.getCurrentPermissions();
@@ -732,7 +732,7 @@
 		 * Event handler for when clicking on a file's checkbox
 		 */
 		_onClickFileCheckbox: function(e) {
-			var $tr = $(e.target).closest('tr');
+			var $tr = $(e.target).closest('.list-element');
 			var state = !$tr.hasClass('selected');
 			this._selectFileEl($tr, state);
 			this._lastChecked = $tr;
@@ -749,8 +749,8 @@
 		_onClickSelectAll: function(e) {
 			var checked = $(e.target).prop('checked');
 			// Select only visible checkboxes to filter out unmatched file in search
-			this.$fileList.find('td.selection > .selectCheckBox:visible').prop('checked', checked)
-				.closest('tr').toggleClass('selected', checked);
+			this.$fileList.find('.selection > .selectCheckBox:visible').prop('checked', checked)
+				.closest('.list-element').toggleClass('selected', checked);
 
 			if (checked) {
 				for (var i = 0; i < this.files.length; i++) {
@@ -767,9 +767,9 @@
 			} else {
 				// if we have some hidden row, then we're in a search
 				// Let's only deselect the visible ones
-				var hiddenFiles = this.$fileList.find('tr.hidden');
+				var hiddenFiles = this.$fileList.find('.list-element.hidden');
 				if (hiddenFiles.length > 0) {
-					var visibleFiles = this.$fileList.find('tr:not(.hidden)');
+					var visibleFiles = this.$fileList.find('.list-element:not(.hidden)');
 					var self = this;
 					visibleFiles.each(function() {
 						var id = parseInt($(this).data('id'));
@@ -952,7 +952,7 @@
 			var files = this.getSelectedFiles();
 			if (files.length === 0) {
 				// single one selected without checkbox?
-				files = _.map(ui.helper.find('tr'), function(el) {
+				files = _.map(ui.helper.find('.list-element'), function(el) {
 					return self.elementToFile($(el));
 				});
 			}
@@ -963,7 +963,7 @@
 			// sometimes the filename drop handler is still called after re-enable,
 			// it seems that waiting for a short time before re-enabling solves the problem
 			setTimeout(function() {
-				self.$el.find('td.filename.ui-droppable').droppable('enable');
+				self.$el.find('.filename.ui-droppable').droppable('enable');
 			}, 10);
 		},
 
@@ -1003,7 +1003,7 @@
 		 */
 		findFileEl: function(fileName){
 			// use filterAttr to avoid escaping issues
-			return this.$fileList.find('tr').filterAttr('data-file', fileName);
+			return this.$fileList.find('.list-element').filterAttr('data-file', fileName);
 		},
 
 		/**
@@ -1109,13 +1109,13 @@
 		 */
 		_onFileActionsUpdated: function() {
 			var self = this;
-			var $files = this.$fileList.find('tr');
+			var $files = this.$fileList.find('.list-element');
 			if (!$files.length) {
 				return;
 			}
 
 			$files.each(function() {
-				self.fileActions.display($(this).find('td.filename'), false, self);
+				self.fileActions.display($(this).find('.filename'), false, self);
 			});
 			this.$fileList.trigger($.Event('fileActionsReady', {fileList: this, $files: $files}));
 
@@ -1239,7 +1239,7 @@
 			}
 
 			//containing tr
-			var tr = $('<tr></tr>').attr({
+			var tr = $('<div class="list-element"></div>').attr({
 				"data-id" : fileData.id,
 				"data-type": type,
 				"data-size": fileData.size,
@@ -1281,7 +1281,7 @@
 
 			// selection td
 			if (this._allowSelection) {
-				td = $('<td class="selection"></td>');
+				td = $('<div class="selection"></div>');
 
 				td.append(
 					'<input id="select-' + this.id + '-' + fileData.id +
@@ -1294,7 +1294,7 @@
 			}
 
 			// filename td
-			td = $('<td class="filename"></td>');
+			td = $('<div class="filename"></div>');
 
 
 			// linkUrl
@@ -1340,7 +1340,7 @@
 						firstConflictPath = firstConflictPath.substr(1);
 					}
 					if (firstConflictPath && firstConflictPath !== '/') {
-						$firstConflict.find('td.filename span.innernametext').prepend($('<span></span>').addClass('conflict-path').text(firstConflictPath));
+						$firstConflict.find('.filename span.innernametext').prepend($('<span></span>').addClass('conflict-path').text(firstConflictPath));
 					}
 				}
 
@@ -1385,7 +1385,7 @@
 				simpleSize = t('files', 'Pending');
 			}
 
-			td = $('<td></td>').attr({
+			td = $('<div></div>').attr({
 				"class": "filesize",
 				"style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')'
 			}).text(simpleSize);
@@ -1409,7 +1409,7 @@
 				formatted = t('files', 'Unable to determine date');
 				text = '?';
 			}
-			td = $('<td></td>').attr({ "class": "date" });
+			td = $('<div></div>').attr({ "class": "date" });
 			td.append($('<span></span>').attr({
 				"class": "modified live-relative-timestamp",
 				"title": formatted,
@@ -1539,7 +1539,7 @@
 				fileData,
 				options
 			);
-			var filenameTd = tr.find('td.filename');
+			var filenameTd = tr.find('.filename');
 
 			// TODO: move dragging to FileActions ?
 			// enable drag only for deletable files
@@ -2134,7 +2134,7 @@
 							var oldSize = oldFile.data('size');
 							var newSize = oldSize + newFile.data('size');
 							oldFile.data('size', newSize);
-							oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
+							oldFile.find('.filesize').text(OC.Util.humanFileSize(newSize));
 
 							// TODO: also update entry in FileList.files
 							self.remove(fileName);
@@ -2203,7 +2203,7 @@
 							var oldSize = oldFile.data('size');
 							var newSize = oldSize + newFile.data('size');
 							oldFile.data('size', newSize);
-							oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
+							oldFile.find('.filesize').text(OC.Util.humanFileSize(newSize));
 						}
 					})
 					.fail(function(status) {
@@ -2300,7 +2300,7 @@
 			tr = this.findFileEl(oldName);
 			var oldFileInfo = this.files[tr.index()];
 			tr.data('renaming',true);
-			td = tr.children('td.filename');
+			td = tr.children('.filename');
 			input = $('<input type="text" class="filename"/>').val(oldName);
 			form = $('<form></form>');
 			form.append(input);
@@ -2744,7 +2744,7 @@
 			if (this._allowSelection) {
 				// Dummy column for selection, as all rows must have the same
 				// number of columns.
-				$tr.append('<td></td>');
+				$tr.append('<div></div>');
 			}
 
 			this.$el.find('tfoot').append($tr);
@@ -2756,7 +2756,7 @@
 			var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
 			this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
 			this.$el.find('#emptycontent .uploadmessage').toggleClass('hidden', !isCreatable || !this.isEmpty);
-			this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
+			this.$el.find('#filestable .list-header').toggleClass('hidden', this.isEmpty);
 		},
 		/**
 		 * Shows the loading mask.
@@ -2844,7 +2844,7 @@
 				}
 			}
 
-			var $trs = this.$fileList.find('tr');
+			var $trs = this.$fileList.find('.list-element');
 			do {
 				_.each($trs, filterRows);
 				if (visibleCount < total) {
@@ -2856,7 +2856,7 @@
 		},
 		hideIrrelevantUIWhenNoFilesMatch:function() {
 			if (this._filter && this.fileSummary.summary.totalDirs + this.fileSummary.summary.totalFiles === 0) {
-				this.$el.find('#filestable thead th').addClass('hidden');
+				this.$el.find('#filestable .list-header').addClass('hidden');
 				this.$el.find('#emptycontent').addClass('hidden');
 				$('#searchresults').addClass('filter-empty');
 				$('#searchresults .emptycontent').addClass('emptycontent-search');
@@ -2868,7 +2868,7 @@
 			} else {
 				$('#searchresults').removeClass('filter-empty');
 				$('#searchresults .emptycontent').removeClass('emptycontent-search');
-				this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
+				this.$el.find('#filestable .list-header').toggleClass('hidden', this.isEmpty);
 				if (!this.$el.find('.mask').exists()) {
 					this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
 				}
@@ -2905,7 +2905,7 @@
 				this.$el.find('#headerName a.name>span:first').text(t('files','Name'));
 				this.$el.find('#headerSize a>span:first').text(t('files','Size'));
 				this.$el.find('#modified a>span:first').text(t('files','Modified'));
-				this.$el.find('table').removeClass('multiselect');
+				this.$el.find('#filestable').removeClass('multiselect');
 				this.$el.find('.selectedActions').addClass('hidden');
 			}
 			else {
@@ -2934,7 +2934,7 @@
 
 				this.$el.find('#headerName a.name>span:first').text(selection);
 				this.$el.find('#modified a>span:first').text('');
-				this.$el.find('table').addClass('multiselect');
+				this.$el.find('#filestable').addClass('multiselect');
 
 				if (this.fileMultiSelectMenu) {
 					this.fileMultiSelectMenu.toggleItemVisibility('download', this.isSelectedDownloadable());
@@ -3137,7 +3137,7 @@
 
 					var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
 					if (currentUploads === 1) {
-						self.showFileBusyState(uploadText.closest('tr'), true);
+						self.showFileBusyState(uploadText.closest('.list-element'), true);
 						uploadText.text(translatedText);
 						uploadText.show();
 					} else {
@@ -3174,7 +3174,7 @@
 				}
 
 				var uploadText = self.$fileList.find('tr .uploadtext');
-				self.showFileBusyState(uploadText.closest('tr'), false);
+				self.showFileBusyState(uploadText.closest('.list-element'), false);
 				uploadText.fadeOut();
 				uploadText.attr('currentUploads', 0);
 
@@ -3200,7 +3200,7 @@
 				});
 
 				var uploadText = self.$fileList.find('tr .uploadtext');
-				self.showFileBusyState(uploadText.closest('tr'), false);
+				self.showFileBusyState(uploadText.closest('.list-element'), false);
 				uploadText.fadeOut();
 				uploadText.attr('currentUploads', 0);
 			});
@@ -3211,7 +3211,7 @@
 				//if user pressed cancel hide upload chrome
 				//cleanup uploading to a dir
 				var uploadText = self.$fileList.find('tr .uploadtext');
-				self.showFileBusyState(uploadText.closest('tr'), false);
+				self.showFileBusyState(uploadText.closest('.list-element'), false);
 				uploadText.fadeOut();
 				uploadText.attr('currentUploads', 0);
 				self.updateStorageStatistics();
diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php
index 9cae72a176f5b..53b3199502864 100644
--- a/apps/files/templates/list.php
+++ b/apps/files/templates/list.php
@@ -38,16 +38,16 @@
 	<p></p>
 </div>
 
-<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="32" data-preview-y="32">
-	<thead>
-		<tr>
-			<th id="headerSelection" class="hidden column-selection">
+<div id="filestable" class="list-container" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="32" data-preview-y="32">
+	<div class="list-header">
+		<div>
+			<div id="headerSelection" class="hidden list-column column-selection">
 				<input type="checkbox" id="select_all_files" class="select-all checkbox"/>
 				<label for="select_all_files">
 					<span class="hidden-visually"><?php p($l->t('Select all'))?></span>
 				</label>
-			</th>
-			<th id='headerName' class="hidden column-name">
+			</div>
+			<div id='headerName' class="hidden list-column column-name">
 				<div id="headerName-container">
 					<a class="name sort columntitle" data-sort="name">
                         <span><?php p($l->t( 'Name' )); ?></span>
@@ -61,20 +61,20 @@
                         </a>
 					</span>
 				</div>
-			</th>
-			<th id="headerSize" class="hidden column-size">
+			</div>
+			<div id="headerSize" class="hidden list-column column-size">
 				<a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a>
-			</th>
-			<th id="headerDate" class="hidden column-mtime">
+			</div>
+			<div id="headerDate" class="hidden list-column column-mtime">
 				<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a>
-			</th>
-		</tr>
-	</thead>
-	<tbody id="fileList">
-	</tbody>
-	<tfoot>
-	</tfoot>
-</table>
+			</div>
+		</div>
+	</div>
+	<div id="fileList" class="list-body">
+	</div>
+	<div class="list-footer">
+	</div>
+</div>
 <input type="hidden" name="dir" id="dir" value="" />
 <div class="hiddenuploadfield">
 	<input type="file" id="file_upload_start" class="hiddenuploadfield" name="files[]" />

From d483ce9f7e0c25b807b4bd5c2118c42804943497 Mon Sep 17 00:00:00 2001
From: Jan-Christoph Borchardt <hey@jancborchardt.net>
Date: Tue, 4 Sep 2018 17:11:56 +0200
Subject: [PATCH 2/2] First basic grid view layout with toggle

Signed-off-by: Jan-Christoph Borchardt <hey@jancborchardt.net>
---
 apps/files/css/files.scss     | 61 +++++++++++++++++++++++++++++++++++
 apps/files/js/filelist.js     | 10 ++++++
 apps/files/templates/list.php |  3 +-
 3 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss
index 0a03a2e6b1b4c..1393779fec83b 100644
--- a/apps/files/css/files.scss
+++ b/apps/files/css/files.scss
@@ -52,12 +52,73 @@
 	min-width: 500px;
 }
 
+#filestable #fileList {
+	display: flex;
+	flex-direction: column;
+}
+
 #filestable .list-element {
 	height: 51px;
 	display: flex;
 	flex-direction: row;
 }
 
+#filestable.view-grid {
+	#fileList {
+		display: flex;
+		flex-direction: row;
+		flex-wrap: wrap;
+
+		.list-element {
+			position: relative;
+			display: inline-block;
+			height: 200px;
+			width: 200px;
+			border-bottom: none;
+
+			.filename a.name {
+				flex-direction: column;
+				height: 100%;
+
+				.thumbnail-wrapper {
+					width: 100%;
+					max-width: 100%;
+					height: 150px;
+
+					.thumbnail {
+						width: 150px;
+						height: 150px;
+						background-size: 150px;
+						border-radius: 10px;
+					}
+				}
+
+				.nametext {
+					width: auto;
+					max-width: 100%;
+					margin: auto;
+				}
+			}
+
+			.selection {
+				position: absolute;
+				top: 0;
+				left: 0;
+				z-index: 100;
+			}
+
+			.fileactions {
+
+			}
+
+			.filesize,
+			.date {
+				display: none;
+			}
+		}
+	}
+}
+
 /* fit app list view heights */
 .app-files #app-content>.viewcontainer {
 	min-height: 0%;
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 7f1e937b3727b..08b132e28df13 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -332,6 +332,9 @@
 
 			this.$el.find('.list-header .columntitle').click(_.bind(this._onClickHeader, this));
 
+			// Toggle for grid view
+			$('#view-button').on('click', this._onGridToggle);
+
 			this._onResize = _.debounce(_.bind(this._onResize, this), 250);
 			$('#app-content').on('appresized', this._onResize);
 			$(window).resize(this._onResize);
@@ -584,6 +587,13 @@
 			this.$table.find('>thead').width($('#app-content').width() - OC.Util.getScrollBarWidth());
 		},
 
+		/**
+		 * Event handler for grid view toggle
+		 */
+		_onGridToggle: function() {
+			$('.list-container').toggleClass('view-grid');
+		},
+
 		/**
 		 * Event handler when leaving previously hidden state
 		 */
diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php
index 53b3199502864..df2cd484bc797 100644
--- a/apps/files/templates/list.php
+++ b/apps/files/templates/list.php
@@ -24,6 +24,7 @@
 	<?php endif;?>
 	<input type="hidden" class="max_human_file_size"
 		   value="(max <?php isset($_['uploadMaxHumanFilesize']) ? p($_['uploadMaxHumanFilesize']) : ''; ?>)">
+	<button id="view-button" class="button view-switcher icon-toggle-pictures"><span class="hidden-visually"><?php p($l->t('Toggle grid view'))?></button>
 </div>
 
 <div id="emptycontent" class="hidden">
@@ -38,7 +39,7 @@
 	<p></p>
 </div>
 
-<div id="filestable" class="list-container" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="32" data-preview-y="32">
+<div id="filestable" class="view-grid list-container" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="32" data-preview-y="32">
 	<div class="list-header">
 		<div>
 			<div id="headerSelection" class="hidden list-column column-selection">
