Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions apps/files/src/actions/moveOrCopyAction.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { IFilePickerButton } from '@nextcloud/dialogs'
import type { Folder, Node } from '@nextcloud/files'
import type { IFolder, INode } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed, WebDAVClientError } from 'webdav'
import type { MoveCopyResult } from './moveOrCopyActionUtils.ts'

Expand All @@ -14,7 +15,7 @@ import { FilePickerClosed, getFilePickerBuilder, showError, showInfo, TOAST_PERM
import { emit } from '@nextcloud/event-bus'
import { FileAction, FileType, getUniqueName, NodeStatus, Permission } from '@nextcloud/files'
import { defaultRootPath, getClient, getDefaultPropfind, resultToNode } from '@nextcloud/files/dav'
import { translate as t } from '@nextcloud/l10n'
import { t } from '@nextcloud/l10n'
import { hasConflict, openConflictPicker } from '@nextcloud/upload'
import { basename, join } from 'path'
import Vue from 'vue'
Expand All @@ -28,7 +29,7 @@ import { canCopy, canMove, getQueue, MoveCopyAction } from './moveOrCopyActionUt
* @param nodes The nodes to check against
* @return The action that is possible for the given nodes
*/
function getActionForNodes(nodes: Node[]): MoveCopyAction {
function getActionForNodes(nodes: INode[]): MoveCopyAction {
if (canMove(nodes)) {
if (canCopy(nodes)) {
return MoveCopyAction.MOVE_OR_COPY
Expand Down Expand Up @@ -76,7 +77,7 @@ function createLoadingNotification(mode: MoveCopyAction, source: string, destina
* @param overwrite Whether to overwrite the destination if it exists
* @return A promise that resolves when the copy/move is done
*/
export async function handleCopyMoveNodeTo(node: Node, destination: Folder, method: MoveCopyAction.COPY | MoveCopyAction.MOVE, overwrite = false) {
export async function handleCopyMoveNodeTo(node: INode, destination: IFolder, method: MoveCopyAction.COPY | MoveCopyAction.MOVE, overwrite = false) {
if (!destination) {
return
}
Expand Down Expand Up @@ -217,13 +218,13 @@ export async function handleCopyMoveNodeTo(node: Node, destination: Folder, meth
async function openFilePickerForAction(
action: MoveCopyAction,
dir = '/',
nodes: Node[],
nodes: INode[],
): Promise<MoveCopyResult | false> {
const { resolve, reject, promise } = Promise.withResolvers<MoveCopyResult | false>()
const fileIDs = nodes.map((node) => node.fileid).filter(Boolean)
const filePicker = getFilePickerBuilder(t('files', 'Choose destination'))
.allowDirectories(true)
.setFilter((n: Node) => {
.setFilter((n: INode) => {
// We don't want to show the current nodes in the file picker
return !fileIDs.includes(n.fileid)
})
Expand All @@ -234,7 +235,7 @@ async function openFilePickerForAction(
.setMimeTypeFilter([])
.setMultiSelect(false)
.startAt(dir)
.setButtonFactory((selection: Node[], path: string) => {
.setButtonFactory((selection: INode[], path: string) => {
const buttons: IFilePickerButton[] = []
const target = basename(path)

Expand All @@ -246,9 +247,9 @@ async function openFilePickerForAction(
label: target ? t('files', 'Copy to {target}', { target }, { escape: false, sanitize: false }) : t('files', 'Copy'),
variant: 'primary',
icon: CopyIconSvg,
async callback(destination: Node[]) {
async callback(destination: INode[]) {
resolve({
destination: destination[0] as Folder,
destination: destination[0] as IFolder,
action: MoveCopyAction.COPY,
} as MoveCopyResult)
},
Expand Down Expand Up @@ -276,9 +277,9 @@ async function openFilePickerForAction(
label: target ? t('files', 'Move to {target}', { target }, undefined, { escape: false, sanitize: false }) : t('files', 'Move'),
variant: action === MoveCopyAction.MOVE ? 'primary' : 'secondary',
icon: FolderMoveSvg,
async callback(destination: Node[]) {
async callback(destination: INode[]) {
resolve({
destination: destination[0] as Folder,
destination: destination[0] as IFolder,
action: MoveCopyAction.MOVE,
} as MoveCopyResult)
},
Expand Down
21 changes: 12 additions & 9 deletions apps/files/src/actions/moveOrCopyActionUtils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Folder, Node } from '@nextcloud/files'
import type { IFolder, INode } from '@nextcloud/files'
import type { ShareAttribute } from '../../../files_sharing/src/sharing.ts'

import { Permission } from '@nextcloud/files'
Expand Down Expand Up @@ -36,35 +36,38 @@ export enum MoveCopyAction {
}

export type MoveCopyResult = {
destination: Folder
destination: IFolder
action: MoveCopyAction.COPY | MoveCopyAction.MOVE
}

/**
* Check if the given nodes can be moved
*
* @param nodes
* @param nodes - The nodes to check
*/
export function canMove(nodes: Node[]) {
export function canMove(nodes: INode[]) {
const minPermission = nodes.reduce((min, node) => Math.min(min, node.permissions), Permission.ALL)
return Boolean(minPermission & Permission.DELETE)
}

/**
* Check if the given nodes can be downloaded
*
* @param nodes
* @param nodes - The nodes to check
*/
export function canDownload(nodes: Node[]) {
export function canDownload(nodes: INode[]) {
return nodes.every((node) => {
const shareAttributes = JSON.parse(node.attributes?.['share-attributes'] ?? '[]') as Array<ShareAttribute>
return !shareAttributes.some((attribute) => attribute.scope === 'permissions' && attribute.value === false && attribute.key === 'download')
})
}

/**
* Check if the given nodes can be copied
*
* @param nodes
* @param nodes - The nodes to check
*/
export function canCopy(nodes: Node[]) {
export function canCopy(nodes: INode[]) {
// a shared file cannot be copied if the download is disabled
if (!canDownload(nodes)) {
return false
Expand Down
24 changes: 13 additions & 11 deletions apps/files/src/components/BreadCrumbs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ import NcBreadcrumb from '@nextcloud/vue/components/NcBreadcrumb'
import NcBreadcrumbs from '@nextcloud/vue/components/NcBreadcrumbs'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useNavigation } from '../composables/useNavigation.ts'
import { useViews } from '../composables/useViews.ts'
import logger from '../logger.ts'
import { dataTransferToFileTree, onDropExternalFiles, onDropInternalFiles } from '../services/DropService.ts'
import { useActiveStore } from '../store/active.ts'
import { useDragAndDropStore } from '../store/dragging.ts'
import { useFilesStore } from '../store/files.ts'
import { usePathsStore } from '../store/paths.ts'
Expand All @@ -76,22 +77,24 @@ export default defineComponent({
},

setup() {
const draggingStore = useDragAndDropStore()
const activeStore = useActiveStore()
const filesStore = useFilesStore()
const pathsStore = usePathsStore()
const draggingStore = useDragAndDropStore()
const selectionStore = useSelectionStore()
const uploaderStore = useUploaderStore()

const fileListWidth = useFileListWidth()
const { currentView, views } = useNavigation()
const views = useViews()

return {
activeStore,
draggingStore,
filesStore,
pathsStore,
selectionStore,
uploaderStore,

currentView,
fileListWidth,
views,
}
Expand Down Expand Up @@ -134,7 +137,7 @@ export default defineComponent({

// used to show the views icon for the first breadcrumb
viewIcon(): string {
return this.currentView?.icon ?? HomeSvg
return this.activeStore.activeView?.icon ?? HomeSvg
},

selectedFiles() {
Expand All @@ -152,12 +155,12 @@ export default defineComponent({
},

getFileSourceFromPath(path: string): FileSource | null {
return (this.currentView && this.pathsStore.getPath(this.currentView.id, path)) ?? null
return (this.activeStore.activeView && this.pathsStore.getPath(this.activeStore.activeView.id, path)) ?? null
},

getDirDisplayName(path: string): string {
if (path === '/') {
return this.currentView?.name || t('files', 'Home')
return this.activeStore.activeView?.name || t('files', 'Home')
}

const source = this.getFileSourceFromPath(path)
Expand All @@ -169,7 +172,7 @@ export default defineComponent({
if (dir === '/') {
return {
...this.$route,
params: { view: this.currentView?.id },
params: { view: this.activeStore.activeView?.id },
query: {},
}
}
Expand Down Expand Up @@ -233,7 +236,8 @@ export default defineComponent({
const fileTree = await dataTransferToFileTree(items)

// We might not have the target directory fetched yet
const contents = await this.currentView?.getContents(path)
const controller = new AbortController()
const contents = await this.activeStore.activeView?.getContents(path, { signal: controller.signal })
const folder = contents?.folder
if (!folder) {
showError(this.t('files', 'Target folder does not exist any more'))
Expand Down Expand Up @@ -275,14 +279,12 @@ export default defineComponent({
} else if (index === 0) {
return t('files', 'Go to the "{dir}" directory', section)
}
return null
},

ariaForSection(section) {
if (section?.to?.query?.dir === this.$route.query.dir) {
return t('files', 'Reload current directory')
}
return null
},

t,
Expand Down
15 changes: 5 additions & 10 deletions apps/files/src/components/DragAndDropNotice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ import { UploadStatus } from '@nextcloud/upload'
import debounce from 'debounce'
import { defineComponent } from 'vue'
import TrayArrowDownIcon from 'vue-material-design-icons/TrayArrowDown.vue'
import { useNavigation } from '../composables/useNavigation.ts'
import logger from '../logger.ts'
import { dataTransferToFileTree, onDropExternalFiles } from '../services/DropService.ts'
import { useActiveStore } from '../store/active.ts'

export default defineComponent({
name: 'DragAndDropNotice',
Expand All @@ -57,10 +57,10 @@ export default defineComponent({
},

setup() {
const { currentView } = useNavigation()
const activeStore = useActiveStore()

return {
currentView,
activeStore,
}
},

Expand Down Expand Up @@ -177,7 +177,8 @@ export default defineComponent({
const fileTree = await dataTransferToFileTree(items)

// We might not have the target directory fetched yet
const contents = await this.currentView?.getContents(this.currentFolder.path)
const controller = new AbortController()
const contents = await this.activeStore.activeView?.getContents(this.currentFolder.path, { signal: controller.signal })
const folder = contents?.folder
if (!folder) {
showError(this.t('files', 'Target folder does not exist any more'))
Expand Down Expand Up @@ -211,13 +212,7 @@ export default defineComponent({
...this.$route.params,
fileid: String(lastUpload.response!.headers['oc-fileid']),
},

query: {
...this.$route.query,
},
}
// Remove open file from query
delete location.query?.openfile
this.$router.push(location)
}

Expand Down
20 changes: 11 additions & 9 deletions apps/files/src/components/FilesListTableHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,15 @@ 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 { t } from '@nextcloud/l10n'
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { defineComponent } from 'vue'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import FilesListTableHeaderButton from './FilesListTableHeaderButton.vue'
import { useNavigation } from '../composables/useNavigation.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import logger from '../logger.ts'
import filesSortingMixin from '../mixins/filesSorting.ts'
import { useActiveStore } from '../store/active.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'

Expand Down Expand Up @@ -126,15 +127,17 @@ export default defineComponent({
},

setup() {
const activeStore = useActiveStore()
const filesStore = useFilesStore()
const selectionStore = useSelectionStore()
const { currentView } = useNavigation()
const { directory } = useRouteParameters()

return {
activeStore,
filesStore,
selectionStore,

currentView,
directory,
}
},

Expand All @@ -144,12 +147,12 @@ export default defineComponent({
if (this.filesListWidth < 512) {
return []
}
return this.currentView?.columns || []
return this.activeStore.activeView?.columns || []
},

dir() {
// Remove any trailing slash but leave root slash
return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
return this.directory.replace(/^(.+)\/$/, '$1')
},

selectAllBind() {
Expand Down Expand Up @@ -195,19 +198,18 @@ export default defineComponent({
},

methods: {
ariaSortForMode(mode: string): 'ascending' | 'descending' | null {
ariaSortForMode(mode: string): 'ascending' | 'descending' | undefined {
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,
[`files-list__row-${this.activeStore.activeView?.id}-${column.id}`]: true,
}
},

Expand Down
Loading
Loading