From 5c652484e3e3cdf0abac2ea7c2eae49f45ab8c30 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Tue, 23 Dec 2025 20:08:43 +0100 Subject: [PATCH] refactor(files_external): migrate files integration to script-setup and Vue 3 Signed-off-by: Ferdinand Thiessen --- .../src/actions/enterCredentialsAction.ts | 21 ++---- .../src/actions/inlineStorageCheckAction.ts | 71 ++++++++++--------- .../src/actions/openInFilesAction.ts | 2 +- apps/files_external/src/init.ts | 7 +- .../src/services/externalStorage.ts | 5 +- .../src/utils/credentialsUtils.ts | 4 +- .../src/utils/externalStorageUtils.ts | 8 ++- .../src/views/CredentialsDialog.vue | 65 ++++++----------- build/frontend-legacy/webpack.modules.cjs | 4 -- .../apps/files_external | 0 build/frontend/vite.config.ts | 4 ++ 11 files changed, 84 insertions(+), 107 deletions(-) rename build/{frontend-legacy => frontend}/apps/files_external (100%) diff --git a/apps/files_external/src/actions/enterCredentialsAction.ts b/apps/files_external/src/actions/enterCredentialsAction.ts index 8ad84996557..79bb6a2bb84 100644 --- a/apps/files_external/src/actions/enterCredentialsAction.ts +++ b/apps/files_external/src/actions/enterCredentialsAction.ts @@ -10,12 +10,13 @@ import type { StorageConfig } from '../services/externalStorage.ts' import LoginSvg from '@mdi/svg/svg/login.svg?raw' import axios from '@nextcloud/axios' import { showError, showSuccess } from '@nextcloud/dialogs' +import { emit } from '@nextcloud/event-bus' import { DefaultType, FileAction } from '@nextcloud/files' import { t } from '@nextcloud/l10n' import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation' import { generateUrl } from '@nextcloud/router' import { spawnDialog } from '@nextcloud/vue/functions/dialog' -import Vue, { defineAsyncComponent } from 'vue' +import { defineAsyncComponent } from 'vue' import { isMissingAuthConfig, STORAGE_STATUS } from '../utils/credentialsUtils.ts' import { isNodeExternalStorage } from '../utils/externalStorageUtils.ts' @@ -23,11 +24,6 @@ import { isNodeExternalStorage } from '../utils/externalStorageUtils.ts' // the backend requires the user to confirm their password addPasswordConfirmationInterceptors(axios) -type CredentialResponse = { - login?: string - password?: string -} - /** * Set credentials for external storage * @@ -55,7 +51,9 @@ async function setCredentials(node: Node, login: string, password: string): Prom // Success update config attribute showSuccess(t('files_external', 'New configuration successfully saved')) - Vue.set(node.attributes, 'config', config) + node.attributes.config = config + emit('files:node:updated', node) + return true } @@ -86,14 +84,7 @@ export const action = new FileAction({ }, async exec({ nodes }) { - const { login, password } = await new Promise((resolve) => spawnDialog( - defineAsyncComponent(() => import('../views/CredentialsDialog.vue')), - {}, - (args) => { - resolve(args as CredentialResponse) - }, - )) - + const { login, password } = await spawnDialog(defineAsyncComponent(() => import('../views/CredentialsDialog.vue'))) ?? {} if (login && password) { try { await setCredentials(nodes[0], login, password) diff --git a/apps/files_external/src/actions/inlineStorageCheckAction.ts b/apps/files_external/src/actions/inlineStorageCheckAction.ts index 7280c622411..01ef4db706c 100644 --- a/apps/files_external/src/actions/inlineStorageCheckAction.ts +++ b/apps/files_external/src/actions/inlineStorageCheckAction.ts @@ -1,4 +1,4 @@ -/** +/*! * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -8,9 +8,9 @@ import type { StorageConfig } from '../services/externalStorage.ts' import AlertSvg from '@mdi/svg/svg/alert-circle.svg?raw' import { showWarning } from '@nextcloud/dialogs' +import { emit } from '@nextcloud/event-bus' import { FileAction } from '@nextcloud/files' -import { translate as t } from '@nextcloud/l10n' -import Vue from 'vue' +import { t } from '@nextcloud/l10n' import { getStatus } from '../services/externalStorage.ts' import { isMissingAuthConfig, STORAGE_STATUS } from '../utils/credentialsUtils.ts' import { isNodeExternalStorage } from '../utils/externalStorageUtils.ts' @@ -31,7 +31,8 @@ export const action = new FileAction({ * Use this function to check the storage availability * We then update the node attributes directly. * - * @param node The node to render inline + * @param context - The action context + * @param context.nodes - The node to render inline */ async renderInline({ nodes }) { if (nodes.length !== 1 || !nodes[0]) { @@ -44,43 +45,43 @@ export const action = new FileAction({ span.innerHTML = t('files_external', 'Checking storage …') let config = null as unknown as StorageConfig - getStatus(node.attributes.id, node.attributes.scope === 'system') - .then((response) => { - config = response.data - Vue.set(node.attributes, 'config', config) + try { + const { data } = await getStatus(node.attributes.id, node.attributes.scope === 'system') + config = data + node.attributes.config = config + emit('files:node:updated', node) - if (config.status !== STORAGE_STATUS.SUCCESS) { - throw new Error(config?.statusMessage || t('files_external', 'There was an error with this external storage.')) - } + if (config.status !== STORAGE_STATUS.SUCCESS) { + throw new Error(config?.statusMessage || t('files_external', 'There was an error with this external storage.')) + } - span.remove() - }) - .catch((error) => { - // If axios failed or if something else prevented - // us from getting the config - if ((error as AxiosError).response && !config) { - showWarning(t('files_external', 'We were unable to check the external storage {basename}', { - basename: node.basename, - })) - } + span.remove() + } catch (error) { + // If axios failed or if something else prevented + // us from getting the config + if ((error as AxiosError).response && !config) { + showWarning(t('files_external', 'We were unable to check the external storage {basename}', { + basename: node.basename, + })) + } - // Reset inline status - span.innerHTML = '' + // Reset inline status + span.innerHTML = '' - // Checking if we really have an error - const isWarning = !config ? false : isMissingAuthConfig(config) - const overlay = document.createElement('span') - overlay.classList.add(`files-list__row-status--${isWarning ? 'warning' : 'error'}`) + // Checking if we really have an error + const isWarning = !config ? false : isMissingAuthConfig(config) + const overlay = document.createElement('span') + overlay.classList.add(`files-list__row-status--${isWarning ? 'warning' : 'error'}`) - // Only show an icon for errors, warning like missing credentials - // have a dedicated inline action button - if (!isWarning) { - span.innerHTML = AlertSvg - span.title = (error as Error).message - } + // Only show an icon for errors, warning like missing credentials + // have a dedicated inline action button + if (!isWarning) { + span.innerHTML = AlertSvg + span.title = (error as Error).message + } - span.prepend(overlay) - }) + span.prepend(overlay) + } return span }, diff --git a/apps/files_external/src/actions/openInFilesAction.ts b/apps/files_external/src/actions/openInFilesAction.ts index 15bc8f58312..55c1a7b084d 100644 --- a/apps/files_external/src/actions/openInFilesAction.ts +++ b/apps/files_external/src/actions/openInFilesAction.ts @@ -6,7 +6,7 @@ import type { StorageConfig } from '../services/externalStorage.ts' import { getCurrentUser } from '@nextcloud/auth' import { DefaultType, FileAction } from '@nextcloud/files' -import { translate as t } from '@nextcloud/l10n' +import { t } from '@nextcloud/l10n' import { generateUrl } from '@nextcloud/router' import { STORAGE_STATUS } from '../utils/credentialsUtils.ts' diff --git a/apps/files_external/src/init.ts b/apps/files_external/src/init.ts index 1f21ad9f16e..d3fc5e6d916 100644 --- a/apps/files_external/src/init.ts +++ b/apps/files_external/src/init.ts @@ -1,9 +1,10 @@ -import FolderNetworkSvg from '@mdi/svg/svg/folder-network-outline.svg?raw' -import { Column, getNavigation, registerFileAction, View } from '@nextcloud/files' -/** +/* * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ + +import FolderNetworkSvg from '@mdi/svg/svg/folder-network-outline.svg?raw' +import { Column, getNavigation, registerFileAction, View } from '@nextcloud/files' import { loadState } from '@nextcloud/initial-state' import { translate as t } from '@nextcloud/l10n' import { action as enterCredentialsAction } from './actions/enterCredentialsAction.ts' diff --git a/apps/files_external/src/services/externalStorage.ts b/apps/files_external/src/services/externalStorage.ts index 6801eeb8084..9d095bf3c8a 100644 --- a/apps/files_external/src/services/externalStorage.ts +++ b/apps/files_external/src/services/externalStorage.ts @@ -48,8 +48,9 @@ export type MountEntry = { } /** + * Convert an OCS api result (mount entry) to a Folder instance * - * @param ocsEntry + * @param ocsEntry - The OCS mount entry */ function entryToFolder(ocsEntry: MountEntry): Folder { const path = (ocsEntry.path + '/' + ocsEntry.name).replace(/^\//gm, '') @@ -69,7 +70,7 @@ function entryToFolder(ocsEntry: MountEntry): Folder { } /** - * + * Fetch the contents of external storage mounts */ export async function getContents(): Promise { const response = await axios.get(generateOcsUrl('apps/files_external/api/v1/mounts')) as AxiosResponse> diff --git a/apps/files_external/src/utils/credentialsUtils.ts b/apps/files_external/src/utils/credentialsUtils.ts index 2bf6a79b996..ed95fe8a68b 100644 --- a/apps/files_external/src/utils/credentialsUtils.ts +++ b/apps/files_external/src/utils/credentialsUtils.ts @@ -17,7 +17,9 @@ export enum STORAGE_STATUS { } /** - * @param config + * Check if the given storage configuration is missing authentication configuration + * + * @param config - The storage configuration to check */ export function isMissingAuthConfig(config: StorageConfig) { // If we don't know the status, assume it is ok diff --git a/apps/files_external/src/utils/externalStorageUtils.ts b/apps/files_external/src/utils/externalStorageUtils.ts index ed69b7f5917..f28e30a02b0 100644 --- a/apps/files_external/src/utils/externalStorageUtils.ts +++ b/apps/files_external/src/utils/externalStorageUtils.ts @@ -3,15 +3,17 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import type { Node } from '@nextcloud/files' +import type { INode } from '@nextcloud/files' import type { MountEntry } from '../services/externalStorage.ts' import { FileType } from '@nextcloud/files' /** - * @param node + * Check if the given node represents an external storage mount + * + * @param node - The node to check */ -export function isNodeExternalStorage(node: Node) { +export function isNodeExternalStorage(node: INode) { // Not a folder, not a storage if (node.type === FileType.File) { return false diff --git a/apps/files_external/src/views/CredentialsDialog.vue b/apps/files_external/src/views/CredentialsDialog.vue index d7a3945c90b..b9d7036d565 100644 --- a/apps/files_external/src/views/CredentialsDialog.vue +++ b/apps/files_external/src/views/CredentialsDialog.vue @@ -3,6 +3,28 @@ - SPDX-License-Identifier: AGPL-3.0-or-later --> + + - - diff --git a/build/frontend-legacy/webpack.modules.cjs b/build/frontend-legacy/webpack.modules.cjs index aa15203613b..48ab1f6108c 100644 --- a/build/frontend-legacy/webpack.modules.cjs +++ b/build/frontend-legacy/webpack.modules.cjs @@ -40,10 +40,6 @@ module.exports = { 'settings-personal': path.join(__dirname, 'apps/files/src', 'main-settings-personal.ts'), 'reference-files': path.join(__dirname, 'apps/files/src', 'reference-files.ts'), }, - files_external: { - init: path.join(__dirname, 'apps/files_external/src', 'init.ts'), - settings: path.join(__dirname, 'apps/files_external/src', 'settings.js'), - }, files_sharing: { additionalScripts: path.join(__dirname, 'apps/files_sharing/src', 'additionalScripts.js'), collaboration: path.join(__dirname, 'apps/files_sharing/src', 'collaborationresourceshandler.js'), diff --git a/build/frontend-legacy/apps/files_external b/build/frontend/apps/files_external similarity index 100% rename from build/frontend-legacy/apps/files_external rename to build/frontend/apps/files_external diff --git a/build/frontend/vite.config.ts b/build/frontend/vite.config.ts index 086f3c748ea..dd5c0e73992 100644 --- a/build/frontend/vite.config.ts +++ b/build/frontend/vite.config.ts @@ -20,6 +20,10 @@ const modules = { 'settings-admin': resolve(import.meta.dirname, 'apps/federatedfilesharing/src', 'settings-admin.ts'), 'settings-personal': resolve(import.meta.dirname, 'apps/federatedfilesharing/src', 'settings-personal.ts'), }, + files_external: { + init: resolve(import.meta.dirname, 'apps/files_external/src', 'init.ts'), + settings: resolve(import.meta.dirname, 'apps/files_external/src', 'settings.js'), + }, files_reminders: { init: resolve(import.meta.dirname, 'apps/files_reminders/src', 'files-init.ts'), },