Skip to content

Commit

Permalink
fix(files): allow triggering a Files reload directly
Browse files Browse the repository at this point in the history
Signed-off-by: skjnldsv <[email protected]>
  • Loading branch information
skjnldsv committed Jan 23, 2025
1 parent be557ca commit 9fc3b2f
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 49 deletions.
6 changes: 3 additions & 3 deletions apps/files/src/actions/convertAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { t } from '@nextcloud/l10n'

import AutoRenewSvg from '@mdi/svg/svg/autorenew.svg?raw'

import { convertFile, convertFiles, getParentFolder } from './convertUtils'
import { convertFile, convertFiles } from './convertUtils'

type ConversionsProvider = {
from: string,
Expand All @@ -35,15 +35,15 @@ export const registerConvertActions = () => {

async exec(node: Node, view: View, dir: string) {

Check failure on line 36 in apps/files/src/actions/convertAction.ts

View workflow job for this annotation

GitHub Actions / NPM lint

'view' is defined but never used

Check failure on line 36 in apps/files/src/actions/convertAction.ts

View workflow job for this annotation

GitHub Actions / NPM lint

'dir' is defined but never used
// If we're here, we know that the node has a fileid
convertFile(node.fileid as number, to, getParentFolder(view, dir))
convertFile(node.fileid as number, to)

// Silently terminate, we'll handle the UI in the background
return null
},

async execBatch(nodes: Node[], view: View, dir: string) {

Check failure on line 44 in apps/files/src/actions/convertAction.ts

View workflow job for this annotation

GitHub Actions / NPM lint

'view' is defined but never used

Check failure on line 44 in apps/files/src/actions/convertAction.ts

View workflow job for this annotation

GitHub Actions / NPM lint

'dir' is defined but never used
const fileIds = nodes.map(node => node.fileid).filter(Boolean) as number[]
convertFiles(fileIds, to, getParentFolder(view, dir))
convertFiles(fileIds, to)

// Silently terminate, we'll handle the UI in the background
return Array(nodes.length).fill(null)
Expand Down
78 changes: 32 additions & 46 deletions apps/files/src/actions/convertUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,56 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { AxiosResponse } from '@nextcloud/axios'
import type { Folder, View } from '@nextcloud/files'
import type { OCSResponse } from '@nextcloud/typings/ocs'

import { emit } from '@nextcloud/event-bus'
import { AxiosError } from 'axios'

Check failure on line 8 in apps/files/src/actions/convertUtils.ts

View workflow job for this annotation

GitHub Actions / NPM lint

"axios" is extraneous
import { generateOcsUrl } from '@nextcloud/router'
import { showError, showLoading, showSuccess } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import PQueue from 'p-queue'

import logger from '../logger'
import { useFilesStore } from '../store/files'
import { getPinia } from '../store'
import { usePathsStore } from '../store/paths'

const queue = new PQueue({ concurrency: 5 })

const requestConversion = function(fileId: number, targetMimeType: string): Promise<AxiosResponse> {
type ConversionResponse = {
path: string
fileId: number
}

interface PromiseRejectedResult<T> {
status: 'rejected'
reason: T
}

type PromiseSettledResult<T, E> = PromiseFulfilledResult<T> | PromiseRejectedResult<E>;
type ConversionSuccess = AxiosResponse<OCSResponse<ConversionResponse>>
type ConversionError = AxiosError<OCSResponse<ConversionResponse>>

const requestConversion = function(fileId: number, targetMimeType: string): Promise<AxiosResponse<OCSResponse<ConversionResponse>>> {
return axios.post(generateOcsUrl('/apps/files/api/v1/convert'), {
fileId,
targetMimeType,
})
}

export const convertFiles = async function(fileIds: number[], targetMimeType: string, parentFolder: Folder | null) {
export const convertFiles = async function(fileIds: number[], targetMimeType: string) {
const conversions = fileIds.map(fileId => queue.add(() => requestConversion(fileId, targetMimeType)))

// Start conversion
const toast = showLoading(t('files', 'Converting files…'))

// Handle results
try {
const results = await Promise.allSettled(conversions)
const failed = results.filter(result => result.status === 'rejected')
const results = await Promise.allSettled(conversions) as PromiseSettledResult<ConversionSuccess, ConversionError>[]
const failed = results.filter(result => result.status === 'rejected') as PromiseRejectedResult<ConversionError>[]
if (failed.length > 0) {
const messages = failed.map(result => result.reason?.response?.data?.ocs?.meta?.message) as string[]
const messages = failed.map(result => result.reason?.response?.data?.ocs?.meta?.message)
logger.error('Failed to convert files', { fileIds, targetMimeType, messages })

// If all failed files have the same error message, show it
if (new Set(messages).size === 1) {
if (new Set(messages).size === 1 && typeof messages[0] === 'string') {
showError(t('files', 'Failed to convert files: {message}', { message: messages[0] }))
return
}
Expand Down Expand Up @@ -75,14 +86,14 @@ export const convertFiles = async function(fileIds: number[], targetMimeType: st
showSuccess(t('files', 'Files successfully converted'))

// Trigger a reload of the file list
if (parentFolder) {
emit('files:node:updated', parentFolder)
}
await window.OCP?.Files?.App?.reload?.()

// Find the first file that is within the current folder

// Switch to the new files
const firstSuccess = results[0] as PromiseFulfilledResult<AxiosResponse>
const firstSuccess = results[0] as PromiseFulfilledResult<ConversionSuccess>
const newFileId = firstSuccess.value.data.ocs.data.fileId
window.OCP.Files.Router.goToRoute(null, { ...window.OCP.Files.Router.params, fileid: newFileId }, window.OCP.Files.Router.query)
window.OCP.Files.Router.goToRoute(null, { ...window.OCP.Files.Router.params, fileid: newFileId.toString() }, window.OCP.Files.Router.query)
} catch (error) {
// Should not happen as we use allSettled and handle errors above
showError(t('files', 'Failed to convert files'))
Expand All @@ -93,24 +104,22 @@ export const convertFiles = async function(fileIds: number[], targetMimeType: st
}
}

export const convertFile = async function(fileId: number, targetMimeType: string, parentFolder: Folder | null) {
export const convertFile = async function(fileId: number, targetMimeType: string) {
const toast = showLoading(t('files', 'Converting file…'))

try {
const result = await queue.add(() => requestConversion(fileId, targetMimeType)) as AxiosResponse
const result = await queue.add(() => requestConversion(fileId, targetMimeType)) as AxiosResponse<OCSResponse<ConversionResponse>>
showSuccess(t('files', 'File successfully converted'))

// Trigger a reload of the file list
if (parentFolder) {
emit('files:node:updated', parentFolder)
}
await window.OCP?.Files?.App?.reload?.()

// Switch to the new file
const newFileId = result.data.ocs.data.fileId
window.OCP.Files.Router.goToRoute(null, { ...window.OCP.Files.Router.params, fileid: newFileId }, window.OCP.Files.Router.query)
window.OCP.Files.Router.goToRoute(null, { ...window.OCP.Files.Router.params, fileid: newFileId.toString() }, window.OCP.Files.Router.query)
} catch (error) {
// If the server returned an error message, show it
if (error.response?.data?.ocs?.meta?.message) {
if (error instanceof AxiosError && error?.response?.data?.ocs?.meta?.message) {
showError(t('files', 'Failed to convert file: {message}', { message: error.response.data.ocs.meta.message }))
return
}
Expand All @@ -122,26 +131,3 @@ export const convertFile = async function(fileId: number, targetMimeType: string
toast.hideToast()
}
}

/**
* Get the parent folder of a path
*
* TODO: replace by the parent node straight away when we
* update the Files actions api accordingly.
*
* @param view The current view
* @param path The path to the file
* @returns The parent folder
*/
export const getParentFolder = function(view: View, path: string): Folder | null {
const filesStore = useFilesStore(getPinia())
const pathsStore = usePathsStore(getPinia())

const parentSource = pathsStore.getPath(view.id, path)
if (!parentSource) {
return null
}

const parentFolder = filesStore.getNode(parentSource) as Folder | undefined
return parentFolder ?? null
}
7 changes: 7 additions & 0 deletions apps/files/src/views/FilesList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,13 @@ export default defineComponent({

// reload on settings change
subscribe('files:config:updated', this.fetchContent)

// Init the global OCP.Files.App object
// OCP.Files is already initialized at this point
if (!window?.OCP?.Files?.App) {
window.OCP.Files.App = {}
}
Object.assign(window.OCP.Files.App, { ...window.OCP.Files.App, reload: this.fetchContent })
},

unmounted() {
Expand Down

0 comments on commit 9fc3b2f

Please sign in to comment.