aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/components
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2023-01-13 17:32:57 +0100
committerJohn Molakvoæ <skjnldsv@protonmail.com>2023-04-06 14:49:29 +0200
commit29a7f7f6efd2a9791fdcfb9f9f7e862bafd8da82 (patch)
tree720d2c59461777dd8a4a4d57d06738ce55066f22 /apps/files/src/components
parent8eb95052945c478a71d910090c7b1105f9256a4e (diff)
downloadnextcloud-server-29a7f7f6efd2a9791fdcfb9f9f7e862bafd8da82.tar.gz
nextcloud-server-29a7f7f6efd2a9791fdcfb9f9f7e862bafd8da82.zip
feat(files_trashbin): migrate to vue
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files/src/components')
-rw-r--r--apps/files/src/components/BreadCrumbs.vue58
-rw-r--r--apps/files/src/components/FileEntry.vue134
-rw-r--r--apps/files/src/components/FilesListHeader.vue122
-rw-r--r--apps/files/src/components/FilesListVirtual.vue124
4 files changed, 438 insertions, 0 deletions
diff --git a/apps/files/src/components/BreadCrumbs.vue b/apps/files/src/components/BreadCrumbs.vue
new file mode 100644
index 00000000000..15fd35667ec
--- /dev/null
+++ b/apps/files/src/components/BreadCrumbs.vue
@@ -0,0 +1,58 @@
+<template>
+ <NcBreadcrumbs data-cy-files-content-breadcrumbs>
+ <!-- Current path sections -->
+ <NcBreadcrumb v-for="section in sections"
+ :key="section.dir"
+ :aria-label="t('files', `Go to the '{dir}' directory`, section)"
+ v-bind="section" />
+ </NcBreadcrumbs>
+</template>
+
+<script>
+import NcBreadcrumbs from '@nextcloud/vue/dist/Components/NcBreadcrumbs.js'
+import NcBreadcrumb from '@nextcloud/vue/dist/Components/NcBreadcrumb.js'
+import { basename } from 'path'
+
+export default {
+ name: 'BreadCrumbs',
+
+ components: {
+ NcBreadcrumbs,
+ NcBreadcrumb,
+ },
+
+ props: {
+ path: {
+ type: String,
+ default: '/',
+ },
+ },
+
+ computed: {
+ dirs() {
+ const cumulativePath = (acc) => (value) => (acc += `${value}/`)
+ return ['/', ...this.path.split('/').filter(Boolean).map(cumulativePath('/'))]
+ },
+
+ sections() {
+ return this.dirs.map(dir => {
+ const to = { ...this.$route, query: { dir } }
+ return {
+ dir,
+ to,
+ title: basename(dir),
+ }
+ })
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.breadcrumb {
+ // Take as much space as possible
+ flex: 1 1 100% !important;
+ width: 100%;
+}
+
+</style>
diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue
new file mode 100644
index 00000000000..de340917b69
--- /dev/null
+++ b/apps/files/src/components/FileEntry.vue
@@ -0,0 +1,134 @@
+<!--
+ - @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
+ -
+ - @author Gary Kim <gary@garykim.dev>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+<template>
+ <Fragment>
+ <td class="files-list__row-checkbox">
+ <NcCheckboxRadioSwitch :aria-label="t('files', 'Select the row for {displayName}', { displayName })"
+ :checked.sync="selectedFiles"
+ :value="fileid.toString()"
+ name="selectedFiles" />
+ </td>
+
+ <!-- Icon or preview -->
+ <td class="files-list__row-icon">
+ <FolderIcon v-if="source.type === 'folder'" />
+ </td>
+
+ <!-- Link to file and -->
+ <td class="files-list__row-name">
+ <a v-bind="linkTo">
+ {{ displayName }}
+ </a>
+ </td>
+ </Fragment>
+</template>
+
+<script lang="ts">
+import { Folder, File } from '@nextcloud/files'
+import { Fragment } from 'vue-fragment'
+import { join } from 'path'
+import { translate } from '@nextcloud/l10n'
+import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
+import FolderIcon from 'vue-material-design-icons/Folder.vue'
+
+import logger from '../logger'
+
+export default {
+ name: 'FileEntry',
+
+ components: {
+ FolderIcon,
+ Fragment,
+ NcCheckboxRadioSwitch,
+ },
+
+ props: {
+ index: {
+ type: Number,
+ required: true,
+ },
+ source: {
+ type: [File, Folder],
+ required: true,
+ },
+ },
+
+ computed: {
+ dir() {
+ // Remove any trailing slash but leave root slash
+ return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
+ },
+
+ fileid() {
+ return this.source.attributes.fileid
+ },
+ displayName() {
+ return this.source.attributes.displayName
+ || this.source.basename
+ },
+
+ linkTo() {
+ if (this.source.type === 'folder') {
+ const to = { ...this.$route, query: { dir: join(this.dir, this.source.basename) } }
+ return {
+ is: 'router-link',
+ title: this.t('files', 'Open folder {name}', { name: this.displayName }),
+ to,
+ }
+ }
+ return {
+ href: this.source.source,
+ // TODO: Use first action title ?
+ title: this.t('files', 'Download file {name}', { name: this.displayName }),
+ }
+ },
+
+ selectedFiles: {
+ get() {
+ return this.$store.state.selection.selected
+ },
+ set(selection) {
+ logger.debug('Added node to selection', { selection })
+ this.$store.dispatch('selection/set', selection)
+ },
+ },
+ },
+
+ methods: {
+ /**
+ * Get a cached note from the store
+ *
+ * @param {number} fileId the file id to get
+ * @return {Folder|File}
+ */
+ getNode(fileId) {
+ return this.$store.getters['files/getNode'](fileId)
+ },
+
+ t: translate,
+ },
+}
+</script>
+
+<style scoped lang="scss">
+@import '../mixins/fileslist-row.scss'
+</style>
diff --git a/apps/files/src/components/FilesListHeader.vue b/apps/files/src/components/FilesListHeader.vue
new file mode 100644
index 00000000000..588d86709da
--- /dev/null
+++ b/apps/files/src/components/FilesListHeader.vue
@@ -0,0 +1,122 @@
+<!--
+ - @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
+ -
+ - @author Gary Kim <gary@garykim.dev>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+<template>
+ <tr>
+ <th class="files-list__row-checkbox">
+ <NcCheckboxRadioSwitch v-bind="selectAllBind" @update:checked="onToggleAll" />
+ </th>
+
+ <!-- Icon or preview -->
+ <th class="files-list__row-icon" />
+
+ <!-- Link to file and -->
+ <th class="files-list__row-name">
+ {{ t('files', 'Name') }}
+ </th>
+ </tr>
+</template>
+
+<script lang="ts">
+import { translate } from '@nextcloud/l10n'
+import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
+
+import logger from '../logger'
+import { File, Folder } from '@nextcloud/files'
+
+export default {
+ name: 'FilesListHeader',
+
+ components: {
+ NcCheckboxRadioSwitch,
+ },
+
+ props: {
+ nodes: {
+ type: [File, Folder],
+ required: true,
+ },
+ },
+
+ computed: {
+ dir() {
+ // Remove any trailing slash but leave root slash
+ return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
+ },
+
+ selectAllBind() {
+ return {
+ ariaLabel: this.isNoneSelected || this.isSomeSelected
+ ? this.t('files', 'Select all')
+ : this.t('files', 'Unselect all'),
+ checked: this.isAllSelected,
+ indeterminate: this.isSomeSelected,
+ }
+ },
+
+ isAllSelected() {
+ return this.selectedFiles.length === this.nodes.length
+ },
+
+ isNoneSelected() {
+ return this.selectedFiles.length === 0
+ },
+
+ isSomeSelected() {
+ return !this.isAllSelected && !this.isNoneSelected
+ },
+
+ selectedFiles() {
+ return this.$store.state.selection.selected
+ },
+ },
+
+ methods: {
+ /**
+ * Get a cached note from the store
+ *
+ * @param {number} fileId the file id to get
+ * @return {Folder|File}
+ */
+ getNode(fileId) {
+ return this.$store.getters['files/getNode'](fileId)
+ },
+
+ onToggleAll(selected) {
+ if (selected) {
+ const selection = this.nodes.map(node => node.attributes.fileid.toString())
+ logger.debug('Added all nodes to selection', { selection })
+ this.$store.dispatch('selection/set', selection)
+ } else {
+ logger.debug('Cleared selection')
+ this.$store.dispatch('selection/reset')
+ }
+ },
+
+ t: translate,
+ },
+}
+</script>
+
+<style scoped lang="scss">
+@import '../mixins/fileslist-row.scss'
+
+</style>
diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue
new file mode 100644
index 00000000000..9228179a96c
--- /dev/null
+++ b/apps/files/src/components/FilesListVirtual.vue
@@ -0,0 +1,124 @@
+<!--
+ - @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
+ -
+ - @author Gary Kim <gary@garykim.dev>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+<template>
+ <VirtualList class="files-list"
+ :data-component="FileEntry"
+ :data-key="getFileId"
+ :data-sources="nodes"
+ :estimate-size="55"
+ :table-mode="true"
+ item-class="files-list__row"
+ wrap-class="files-list__body">
+ <template #before>
+ <caption v-show="false" class="files-list__caption">
+ {{ summary }}
+ </caption>
+ </template>
+
+ <template #header>
+ <FilesListHeader :nodes="nodes" />
+ </template>
+ </VirtualList>
+</template>
+
+<script lang="ts">
+import { Folder, File } from '@nextcloud/files'
+import { translate, translatePlural } from '@nextcloud/l10n'
+import VirtualList from 'vue-virtual-scroll-list'
+
+import FileEntry from './FileEntry.vue'
+import FilesListHeader from './FilesListHeader.vue'
+
+export default {
+ name: 'FilesListVirtual',
+
+ components: {
+ VirtualList,
+ FilesListHeader,
+ },
+
+ props: {
+ nodes: {
+ type: [File, Folder],
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ FileEntry,
+ }
+ },
+
+ computed: {
+ files() {
+ return this.nodes.filter(node => node.type === 'file')
+ },
+
+ summaryFile() {
+ const count = this.files.length
+ return translatePlural('files', '{count} file', '{count} files', count, { count })
+ },
+ summaryFolder() {
+ const count = this.nodes.length - this.files.length
+ return translatePlural('files', '{count} folder', '{count} folders', count, { count })
+ },
+ summary() {
+ return translate('files', '{summaryFile} and {summaryFolder}', this)
+ },
+ },
+
+ methods: {
+ getFileId(node) {
+ return node.attributes.fileid
+ },
+
+ t: translate,
+ },
+}
+</script>
+
+<style scoped lang="scss">
+.files-list {
+ --row-height: 55px;
+ --checkbox-padding: calc((var(--row-height) - var(--checkbox-size)) / 2);
+ --checkbox-size: 24px;
+ --clickable-area: 44px;
+ --icon-preview-size: 32px;
+
+ display: block;
+ overflow: auto;
+ height: 100%;
+
+ &::v-deep {
+ tbody, thead, tfoot {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ }
+
+ thead, .files-list__row {
+ border-bottom: 1px solid var(--color-border);
+ }
+ }
+}
+</style>