diff --git a/src/Components/LeftSidebar/LeftSidebar.vue b/src/Components/LeftSidebar/LeftSidebar.vue
index c3915253c5..7e92e8eb7a 100644
--- a/src/Components/LeftSidebar/LeftSidebar.vue
+++ b/src/Components/LeftSidebar/LeftSidebar.vue
@@ -39,6 +39,14 @@
+
+
+
+
+
({
+ grid_view: true,
+ }),
+ actions: {
+ async update(key, value) {
+ Vue.set(this, key, value)
+ },
+ },
+})
diff --git a/src/views/FilesList/FileEntry/FileEntry.vue b/src/views/FilesList/FileEntry/FileEntry.vue
new file mode 100644
index 0000000000..dc93584abf
--- /dev/null
+++ b/src/views/FilesList/FileEntry/FileEntry.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/src/views/FilesList/FileEntry/FileEntryGrid.vue b/src/views/FilesList/FileEntry/FileEntryGrid.vue
new file mode 100644
index 0000000000..e578746397
--- /dev/null
+++ b/src/views/FilesList/FileEntry/FileEntryGrid.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ |
+
+
+
+
diff --git a/src/views/FilesList/FileEntry/FileEntryPreview.vue b/src/views/FilesList/FileEntry/FileEntryPreview.vue
new file mode 100644
index 0000000000..5ea56f7725
--- /dev/null
+++ b/src/views/FilesList/FileEntry/FileEntryPreview.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/FilesList/FileListFilter/FileListFilter.vue b/src/views/FilesList/FileListFilter/FileListFilter.vue
index 776461d3e3..d235074b5e 100644
--- a/src/views/FilesList/FileListFilter/FileListFilter.vue
+++ b/src/views/FilesList/FileListFilter/FileListFilter.vue
@@ -19,8 +19,8 @@
+
+
diff --git a/src/views/FilesList/FilesListTableHeaderButton.vue b/src/views/FilesList/FilesListTableHeaderButton.vue
new file mode 100644
index 0000000000..982d41617d
--- /dev/null
+++ b/src/views/FilesList/FilesListTableHeaderButton.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+ {{ name }}
+
+
+
+
+
+
diff --git a/src/views/FilesList/FilesListVirtual.vue b/src/views/FilesList/FilesListVirtual.vue
index 2a0e385d6f..50e175fb81 100644
--- a/src/views/FilesList/FilesListVirtual.vue
+++ b/src/views/FilesList/FilesListVirtual.vue
@@ -1,19 +1,34 @@
-
+
+
+
+
+
@@ -40,6 +69,35 @@ export default {
height: 100%;
will-change: scroll-position;
& :deep() {
+ // Table head, body and footer
+ tbody {
+ will-change: padding;
+ contain: layout paint style;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ // Necessary for virtual scrolling absolute
+ position: relative;
+
+ /* Hover effect on tbody lines only */
+ tr {
+ contain: strict;
+ &:hover,
+ &:focus {
+ background-color: var(--color-background-dark);
+ }
+ }
+ }
+
+ .files-list__table {
+ display: block;
+
+ &.files-list__table--with-thead-overlay {
+ // Hide the table header below the overlay
+ margin-block-start: calc(-1 * var(--row-height));
+ }
+ }
+
.files-list__filters {
// Pinned on top when scrolling above table header
position: sticky;
@@ -52,6 +110,331 @@ export default {
height: var(--fixed-block-start-position);
width: 100%;
}
+
+ .files-list__thead,
+ .files-list__tfoot {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ background-color: var(--color-main-background);
+
+ }
+
+ // Table header
+ .files-list__thead {
+ // Pinned on top when scrolling
+ position: sticky;
+ z-index: 10;
+ top: var(--fixed-block-start-position);
+ }
+
+ tr {
+ position: relative;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ user-select: none;
+ border-block-end: 1px solid var(--color-border);
+ box-sizing: border-box;
+ user-select: none;
+ height: var(--row-height);
+ }
+
+ td, th {
+ display: flex;
+ align-items: center;
+ flex: 0 0 auto;
+ justify-content: start;
+ width: var(--row-height);
+ height: var(--row-height);
+ margin: 0;
+ padding: 0;
+ color: var(--color-text-maxcontrast);
+ border: none;
+
+ // Columns should try to add any text
+ // node wrapped in a span. That should help
+ // with the ellipsis on overflow.
+ span {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+ }
+
+ .files-list__row-checkbox {
+ justify-content: center;
+
+ .checkbox-radio-switch {
+ display: flex;
+ justify-content: center;
+
+ --icon-size: var(--checkbox-size);
+
+ label.checkbox-radio-switch__label {
+ width: var(--clickable-area);
+ height: var(--clickable-area);
+ margin: 0;
+ padding: calc((var(--clickable-area) - var(--checkbox-size)) / 2);
+ }
+
+ .checkbox-radio-switch__icon {
+ margin: 0 !important;
+ }
+ }
+ }
+
+ .files-list__row {
+ &:hover, &:focus, &:active, &--active, &--dragover {
+ // WCAG AA compliant
+ background-color: var(--color-background-hover);
+ // text-maxcontrast have been designed to pass WCAG AA over
+ // a white background, we need to adjust then.
+ --color-text-maxcontrast: var(--color-main-text);
+ > * {
+ --color-border: var(--color-border-dark);
+ }
+
+ // Hover state of the row should also change the favorite markers background
+ .favorite-marker-icon svg path {
+ stroke: var(--color-background-hover);
+ }
+ }
+
+ &--dragover * {
+ // Prevent dropping on row children
+ pointer-events: none;
+ }
+ }
+
+ // Entry preview or mime icon
+ .files-list__row-icon {
+ position: relative;
+ display: flex;
+ overflow: visible;
+ align-items: center;
+ // No shrinking or growing allowed
+ flex: 0 0 var(--icon-preview-size);
+ justify-content: center;
+ width: var(--icon-preview-size);
+ height: 100%;
+ // Show same padding as the checkbox right padding for visual balance
+ margin-inline-end: var(--checkbox-padding);
+ color: var(--color-primary-element);
+
+ // Icon is also clickable
+ * {
+ cursor: pointer;
+ }
+
+ & > span {
+ justify-content: flex-start;
+
+ &:not(.files-list__row-icon-favorite) svg {
+ width: var(--icon-preview-size);
+ height: var(--icon-preview-size);
+ }
+
+ // Slightly increase the size of the folder icon
+ &.folder-icon,
+ &.folder-open-icon {
+ margin: -3px;
+ svg {
+ width: calc(var(--icon-preview-size) + 6px);
+ height: calc(var(--icon-preview-size) + 6px);
+ }
+ }
+ }
+
+ &-preview-container {
+ position: relative; // Needed for the blurshash to be positioned correctly
+ overflow: hidden;
+ width: var(--icon-preview-size);
+ height: var(--icon-preview-size);
+ border-radius: var(--border-radius);
+ }
+
+ &-blurhash {
+ position: absolute;
+ inset-block-start: 0;
+ inset-inline-start: 0;
+ height: 100%;
+ width: 100%;
+ object-fit: cover;
+ }
+
+ &-preview {
+ // Center and contain the preview
+ object-fit: contain;
+ object-position: center;
+
+ height: 100%;
+ width: 100%;
+
+ /* Preview not loaded animation effect */
+ &:not(.files-list__row-icon-preview--loaded) {
+ background: var(--color-loading-dark);
+ // animation: preview-gradient-fade 1.2s ease-in-out infinite;
+ }
+ }
+
+ &-favorite {
+ position: absolute;
+ top: 0px;
+ inset-inline-end: -10px;
+ }
+
+ // File and folder overlay
+ &-overlay {
+ position: absolute;
+ max-height: calc(var(--icon-preview-size) * 0.5);
+ max-width: calc(var(--icon-preview-size) * 0.5);
+ color: var(--color-primary-element-text);
+ // better alignment with the folder icon
+ margin-block-start: 2px;
+
+ // Improve icon contrast with a background for files
+ &--file {
+ color: var(--color-main-text);
+ background: var(--color-main-background);
+ border-radius: 100%;
+ }
+ }
+ }
+
+ // Entry link
+ .files-list__row-name {
+ // Prevent link from overflowing
+ overflow: hidden;
+ // Take as much space as possible
+ flex: 1 1 auto;
+ }
+
+ .files-list__row-actions {
+ // take as much space as necessary
+ width: auto;
+
+ // Add margin to all cells after the actions
+ & ~ td,
+ & ~ th {
+ margin: 0 var(--cell-margin);
+ }
+
+ button {
+ .button-vue__text {
+ // Remove bold from default button styling
+ font-weight: normal;
+ }
+ }
+ }
+
+ .files-list__row-column-custom {
+ width: calc(var(--row-height) * 2);
+ }
+ }
+
+}
+
+
+
diff --git a/src/views/FilesList/VirtualList.vue b/src/views/FilesList/VirtualList.vue
index 941ddf47b3..fddcaa33ac 100644
--- a/src/views/FilesList/VirtualList.vue
+++ b/src/views/FilesList/VirtualList.vue
@@ -3,11 +3,44 @@
+
+