aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/components/FilesListTableHeader.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/components/FilesListTableHeader.vue')
-rw-r--r--apps/files/src/components/FilesListTableHeader.vue237
1 files changed, 237 insertions, 0 deletions
diff --git a/apps/files/src/components/FilesListTableHeader.vue b/apps/files/src/components/FilesListTableHeader.vue
new file mode 100644
index 00000000000..23e631199eb
--- /dev/null
+++ b/apps/files/src/components/FilesListTableHeader.vue
@@ -0,0 +1,237 @@
+<!--
+ - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+<template>
+ <tr class="files-list__row-head">
+ <th class="files-list__column files-list__row-checkbox"
+ @keyup.esc.exact="resetSelection">
+ <NcCheckboxRadioSwitch v-bind="selectAllBind" data-cy-files-list-selection-checkbox @update:checked="onToggleAll" />
+ </th>
+
+ <!-- Columns display -->
+
+ <!-- Link to file -->
+ <th class="files-list__column files-list__row-name files-list__column--sortable"
+ :aria-sort="ariaSortForMode('basename')">
+ <!-- Icon or preview -->
+ <span class="files-list__row-icon" />
+
+ <!-- Name -->
+ <FilesListTableHeaderButton :name="t('files', 'Name')" mode="basename" />
+ </th>
+
+ <!-- Actions -->
+ <th class="files-list__row-actions" />
+
+ <!-- Mime -->
+ <th v-if="isMimeAvailable"
+ class="files-list__column files-list__row-mime"
+ :class="{ 'files-list__column--sortable': isMimeAvailable }"
+ :aria-sort="ariaSortForMode('mime')">
+ <FilesListTableHeaderButton :name="t('files', 'File type')" mode="mime" />
+ </th>
+
+ <!-- Size -->
+ <th v-if="isSizeAvailable"
+ class="files-list__column files-list__row-size"
+ :class="{ 'files-list__column--sortable': isSizeAvailable }"
+ :aria-sort="ariaSortForMode('size')">
+ <FilesListTableHeaderButton :name="t('files', 'Size')" mode="size" />
+ </th>
+
+ <!-- Mtime -->
+ <th v-if="isMtimeAvailable"
+ class="files-list__column files-list__row-mtime"
+ :class="{ 'files-list__column--sortable': isMtimeAvailable }"
+ :aria-sort="ariaSortForMode('mtime')">
+ <FilesListTableHeaderButton :name="t('files', 'Modified')" mode="mtime" />
+ </th>
+
+ <!-- Custom views columns -->
+ <th v-for="column in columns"
+ :key="column.id"
+ :class="classForColumn(column)"
+ :aria-sort="ariaSortForMode(column.id)">
+ <FilesListTableHeaderButton v-if="!!column.sort" :name="column.title" :mode="column.id" />
+ <span v-else>
+ {{ column.title }}
+ </span>
+ </th>
+ </tr>
+</template>
+
+<script lang="ts">
+import type { Node } from '@nextcloud/files'
+import type { PropType } from 'vue'
+import type { FileSource } from '../types.ts'
+
+import { translate as t } from '@nextcloud/l10n'
+import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
+import { defineComponent } from 'vue'
+import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
+
+import { useFilesStore } from '../store/files.ts'
+import { useNavigation } from '../composables/useNavigation'
+import { useSelectionStore } from '../store/selection.ts'
+import FilesListTableHeaderButton from './FilesListTableHeaderButton.vue'
+import filesSortingMixin from '../mixins/filesSorting.ts'
+import logger from '../logger.ts'
+
+export default defineComponent({
+ name: 'FilesListTableHeader',
+
+ components: {
+ FilesListTableHeaderButton,
+ NcCheckboxRadioSwitch,
+ },
+
+ mixins: [
+ filesSortingMixin,
+ ],
+
+ props: {
+ isMimeAvailable: {
+ type: Boolean,
+ default: false,
+ },
+ isMtimeAvailable: {
+ type: Boolean,
+ default: false,
+ },
+ isSizeAvailable: {
+ type: Boolean,
+ default: false,
+ },
+ nodes: {
+ type: Array as PropType<Node[]>,
+ required: true,
+ },
+ filesListWidth: {
+ type: Number,
+ default: 0,
+ },
+ },
+
+ setup() {
+ const filesStore = useFilesStore()
+ const selectionStore = useSelectionStore()
+ const { currentView } = useNavigation()
+
+ return {
+ filesStore,
+ selectionStore,
+
+ currentView,
+ }
+ },
+
+ computed: {
+ columns() {
+ // Hide columns if the list is too small
+ if (this.filesListWidth < 512) {
+ return []
+ }
+ return this.currentView?.columns || []
+ },
+
+ dir() {
+ // Remove any trailing slash but leave root slash
+ return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
+ },
+
+ selectAllBind() {
+ const label = t('files', 'Toggle selection for all files and folders')
+ return {
+ 'aria-label': label,
+ checked: this.isAllSelected,
+ indeterminate: this.isSomeSelected,
+ title: label,
+ }
+ },
+
+ selectedNodes() {
+ return this.selectionStore.selected
+ },
+
+ isAllSelected() {
+ return this.selectedNodes.length === this.nodes.length
+ },
+
+ isNoneSelected() {
+ return this.selectedNodes.length === 0
+ },
+
+ isSomeSelected() {
+ return !this.isAllSelected && !this.isNoneSelected
+ },
+ },
+
+ created() {
+ // ctrl+a selects all
+ useHotKey('a', this.onToggleAll, {
+ ctrl: true,
+ stop: true,
+ prevent: true,
+ })
+
+ // Escape key cancels selection
+ useHotKey('Escape', this.resetSelection, {
+ stop: true,
+ prevent: true,
+ })
+ },
+
+ methods: {
+ ariaSortForMode(mode: string): 'ascending'|'descending'|null {
+ if (this.sortingMode === mode) {
+ return this.isAscSorting ? 'ascending' : 'descending'
+ }
+ return null
+ },
+
+ classForColumn(column) {
+ return {
+ 'files-list__column': true,
+ 'files-list__column--sortable': !!column.sort,
+ 'files-list__row-column-custom': true,
+ [`files-list__row-${this.currentView?.id}-${column.id}`]: true,
+ }
+ },
+
+ onToggleAll(selected = true) {
+ if (selected) {
+ const selection = this.nodes.map(node => node.source).filter(Boolean) as FileSource[]
+ logger.debug('Added all nodes to selection', { selection })
+ this.selectionStore.setLastIndex(null)
+ this.selectionStore.set(selection)
+ } else {
+ logger.debug('Cleared selection')
+ this.selectionStore.reset()
+ }
+ },
+
+ resetSelection() {
+ if (this.isNoneSelected) {
+ return
+ }
+ this.selectionStore.reset()
+ },
+
+ t,
+ },
+})
+</script>
+
+<style scoped lang="scss">
+.files-list__column {
+ user-select: none;
+ // Make sure the cell colors don't apply to column headers
+ color: var(--color-text-maxcontrast) !important;
+
+ &--sortable {
+ cursor: pointer;
+ }
+}
+
+</style>