diff options
author | skjnldsv <skjnldsv@protonmail.com> | 2024-12-13 12:00:28 +0100 |
---|---|---|
committer | skjnldsv <skjnldsv@protonmail.com> | 2024-12-17 09:59:56 +0100 |
commit | e7001022c75b3a818356378bb53bbfe5129a10fe (patch) | |
tree | 5cb23bd1fb1ff92f6ee99aed94629525de42ab45 /apps/files/src/components/FilesListVirtual.vue | |
parent | f16d0478084ca17551a0bc3242e48b633ff23a24 (diff) | |
download | nextcloud-server-e7001022c75b3a818356378bb53bbfe5129a10fe.tar.gz nextcloud-server-e7001022c75b3a818356378bb53bbfe5129a10fe.zip |
feat(files): add opendetails param and file list up/down keyboard shortcut
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files/src/components/FilesListVirtual.vue')
-rw-r--r-- | apps/files/src/components/FilesListVirtual.vue | 172 |
1 files changed, 129 insertions, 43 deletions
diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue index 81c4c5ac666..6df059f6143 100644 --- a/apps/files/src/components/FilesListVirtual.vue +++ b/apps/files/src/components/FilesListVirtual.vue @@ -12,7 +12,6 @@ isMtimeAvailable, isSizeAvailable, nodes, - fileListWidth, }" :scroll-to-index="scrollToIndex" :caption="caption"> @@ -58,32 +57,34 @@ </template> <script lang="ts"> -import type { Node as NcNode } from '@nextcloud/files' import type { ComponentPublicInstance, PropType } from 'vue' +import type { Node as NcNode } from '@nextcloud/files' import type { UserConfig } from '../types' +import { defineComponent } from 'vue' import { getFileListHeaders, Folder, Permission, View, getFileActions, FileType } from '@nextcloud/files' import { showError } from '@nextcloud/dialogs' -import { translate as t } from '@nextcloud/l10n' import { subscribe, unsubscribe } from '@nextcloud/event-bus' -import { defineComponent } from 'vue' +import { translate as t } from '@nextcloud/l10n' +import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js' import { action as sidebarAction } from '../actions/sidebarAction.ts' +import { getSummaryFor } from '../utils/fileUtils' +import { useActiveStore } from '../store/active.ts' import { useFileListWidth } from '../composables/useFileListWidth.ts' import { useRouteParameters } from '../composables/useRouteParameters.ts' -import { getSummaryFor } from '../utils/fileUtils' import { useSelectionStore } from '../store/selection.js' import { useUserConfigStore } from '../store/userconfig.ts' import FileEntry from './FileEntry.vue' import FileEntryGrid from './FileEntryGrid.vue' +import FileListFilters from './FileListFilters.vue' import FilesListHeader from './FilesListHeader.vue' import FilesListTableFooter from './FilesListTableFooter.vue' import FilesListTableHeader from './FilesListTableHeader.vue' -import VirtualList from './VirtualList.vue' -import logger from '../logger.ts' import FilesListTableHeaderActions from './FilesListTableHeaderActions.vue' -import FileListFilters from './FileListFilters.vue' +import logger from '../logger.ts' +import VirtualList from './VirtualList.vue' export default defineComponent({ name: 'FilesListVirtual', @@ -113,18 +114,24 @@ export default defineComponent({ }, setup() { - const userConfigStore = useUserConfigStore() + const activeStore = useActiveStore() const selectionStore = useSelectionStore() + const userConfigStore = useUserConfigStore() + const fileListWidth = useFileListWidth() - const { fileId, openFile } = useRouteParameters() + const { fileId, openDetails, openFile } = useRouteParameters() return { fileId, fileListWidth, + openDetails, openFile, - userConfigStore, + activeStore, selectionStore, + userConfigStore, + + t, } }, @@ -215,12 +222,20 @@ export default defineComponent({ handler() { // wait for scrolling and updating the actions to settle this.$nextTick(() => { - if (this.fileId) { - if (this.openFile) { - this.handleOpenFile(this.fileId) - } else { - this.unselectFile() - } + if (this.fileId && this.openFile) { + this.handleOpenFile(this.fileId) + } + }) + }, + immediate: true, + }, + + openDetails: { + handler() { + // wait for scrolling and updating the actions to settle + this.$nextTick(() => { + if (this.fileId && this.openDetails) { + this.openSidebarForFile(this.fileId) } }) }, @@ -228,39 +243,39 @@ export default defineComponent({ }, }, + created() { + useHotKey('Escape', this.unselectFile, { + stop: true, + prevent: true, + }) + + useHotKey(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'], this.onKeyDown, { + stop: true, + prevent: true, + }) + }, + mounted() { // Add events on parent to cover both the table and DragAndDrop notice const mainContent = window.document.querySelector('main.app-content') as HTMLElement mainContent.addEventListener('dragover', this.onDragOver) - - subscribe('files:sidebar:closed', this.unselectFile) - - // If the file list is mounted with a fileId specified - // then we need to open the sidebar initially - if (this.fileId) { - this.openSidebarForFile(this.fileId) - } + subscribe('files:sidebar:closed', this.onSidebarClosed) }, beforeDestroy() { const mainContent = window.document.querySelector('main.app-content') as HTMLElement mainContent.removeEventListener('dragover', this.onDragOver) - - unsubscribe('files:sidebar:closed', this.unselectFile) + unsubscribe('files:sidebar:closed', this.onSidebarClosed) }, methods: { - // Open the file sidebar if we have the room for it - // but don't open the sidebar for the current folder openSidebarForFile(fileId) { - if (document.documentElement.clientWidth > 1024 && this.currentFolder.fileid !== fileId) { - // Open the sidebar for the given URL fileid - // iif we just loaded the app. - const node = this.nodes.find(n => n.fileid === fileId) as NcNode - if (node && sidebarAction?.enabled?.([node], this.currentView)) { - logger.debug('Opening sidebar on file ' + node.path, { node }) - sidebarAction.exec(node, this.currentView, this.currentFolder.path) - } + // Open the sidebar for the given URL fileid + // iif we just loaded the app. + const node = this.nodes.find(n => n.fileid === fileId) as NcNode + if (node && sidebarAction?.enabled?.([node], this.currentView)) { + logger.debug('Opening sidebar on file ' + node.path, { node }) + sidebarAction.exec(node, this.currentView, this.currentFolder.path) } }, @@ -273,19 +288,39 @@ export default defineComponent({ const index = this.nodes.findIndex(node => node.fileid === fileId) if (warn && index === -1 && fileId !== this.currentFolder.fileid) { - showError(this.t('files', 'File not found')) + showError(t('files', 'File not found')) } + this.scrollToIndex = Math.max(0, index) } }, + /** + * Unselect the current file and clear open parameters from the URL + */ unselectFile() { - // If the Sidebar is closed and if openFile is false, remove the file id from the URL - if (!this.openFile && OCA.Files.Sidebar.file === '') { + const query = { ...this.$route.query } + delete query.openfile + delete query.opendetails + + this.activeStore.clearActiveNode() + window.OCP.Files.Router.goToRoute( + null, + { ...this.$route.params, fileid: String(this.currentFolder.fileid ?? '') }, + query, + true, + ) + }, + + // When sidebar is closed, we remove the openDetails parameter from the URL + onSidebarClosed() { + if (this.openDetails) { + const query = { ...this.$route.query } + delete query.opendetails window.OCP.Files.Router.goToRoute( null, - { ...this.$route.params, fileid: String(this.currentFolder.fileid ?? '') }, - this.$route.query, + this.$route.params, + query, ) } }, @@ -348,7 +383,58 @@ export default defineComponent({ } }, - t, + onKeyDown(event: KeyboardEvent) { + // Up and down arrow keys + if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { + const columnCount = this.$refs.table?.columnCount ?? 1 + const index = this.nodes.findIndex(node => node.fileid === this.fileId) ?? 0 + const nextIndex = event.key === 'ArrowUp' ? index - columnCount : index + columnCount + if (nextIndex < 0 || nextIndex >= this.nodes.length) { + return + } + + const nextNode = this.nodes[nextIndex] + + if (nextNode && nextNode?.fileid) { + this.setActiveNode(nextNode) + } + } + + // if grid mode, left and right arrow keys + if (this.userConfig.grid_view && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) { + const index = this.nodes.findIndex(node => node.fileid === this.fileId) ?? 0 + const nextIndex = event.key === 'ArrowLeft' ? index - 1 : index + 1 + if (nextIndex < 0 || nextIndex >= this.nodes.length) { + return + } + + const nextNode = this.nodes[nextIndex] + + if (nextNode && nextNode?.fileid) { + this.setActiveNode(nextNode) + } + } + }, + + setActiveNode(node: NcNode & { fileid: number }) { + logger.debug('Navigating to file ' + node.path, { node, fileid: node.fileid }) + this.scrollToFile(node.fileid) + + // Remove openfile and opendetails from the URL + const query = { ...this.$route.query } + delete query.openfile + delete query.opendetails + + this.activeStore.setActiveNode(node) + + // Silent update of the URL + window.OCP.Files.Router.goToRoute( + null, + { ...this.$route.params, fileid: String(node.fileid) }, + query, + true, + ) + }, }, }) </script> |