From 8da0a112b62482a32df4b7ab928a08f1df077cd0 Mon Sep 17 00:00:00 2001 From: Andrey Borysenko Date: Wed, 4 Sep 2024 19:46:19 +0300 Subject: [PATCH] feat: drop support of Nextcloud 27 Signed-off-by: Andrey Borysenko --- Makefile | 48 +++--- appinfo/info.xml | 2 +- lib/Listener/LoadFilesPluginListener.php | 7 +- src/filesplugin.js | 188 +++++++++++++++++------ src/filesplugin28.js | 159 ------------------- webpack.js | 1 - 6 files changed, 166 insertions(+), 239 deletions(-) delete mode 100644 src/filesplugin28.js diff --git a/Makefile b/Makefile index 9f5b5cdd..301b3c3e 100644 --- a/Makefile +++ b/Makefile @@ -15,32 +15,20 @@ help: @echo " " @echo " Next commands are only for dev environment with nextcloud-docker-dev!" @echo " Daemon register(Linux, socket):" - @echo " dock-sock create docker daemon for Nextcloud 29, 28, 27 (/var/run/docker.sock)" - @echo " dock-sock27 create docker daemon for Nextcloud 27 (/var/run/docker.sock)" - @echo " dock-sock27-gpu create docker daemon with GPU for Nextcloud 27 (/var/run/docker.sock)" + @echo " dock-sock create docker daemon for Nextcloud 30, 29, 28 (/var/run/docker.sock)" @echo " dock-sock28 create docker daemon for Nextcloud 28 (/var/run/docker.sock)" @echo " dock-sock28-gpu create docker daemon with GPU for Nextcloud 28 (/var/run/docker.sock)" + @echo " dock-sock29 create docker daemon for Nextcloud 29 (/var/run/docker.sock)" + @echo " dock-sock29-gpu create docker daemon with GPU for Nextcloud 29 (/var/run/docker.sock)" @echo " dock-sock create docker daemon for Nextcloud Last (/var/run/docker.sock)" @echo " dock-sock-gpu create docker daemon with GPU for Nextcloud Last (/var/run/docker.sock)" @echo " " @echo " Daemon register(any OS, host:port)" @echo " dock2port will map docker socket to port. first use this!" - @echo " dock-port27 create docker daemon for Nextcloud 27 (host.docker.internal:8443)" @echo " dock-port28 create docker daemon for Nextcloud 28 (host.docker.internal:8443)" + @echo " dock-port29 create docker daemon for Nextcloud 29 (host.docker.internal:8443)" @echo " dock-port create docker daemons for Nextcloud Last (host.docker.internal:8443)" -.PHONY: dock-sock27 -dock-sock27: - @echo "creating daemon for nextcloud 'stable27' container" - docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:register \ - docker_dev Docker docker-install http /var/run/docker.sock http://stable27.local/index.php --net=master_default - -.PHONY: dock-sock27-gpu -dock-sock27-gpu: - @echo "creating daemon with NVIDIA gpu for nextcloud 'stable27' container" - docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:register \ - docker_dev_gpu "Docker with GPU" docker-install http /var/run/docker.sock http://stable27.local/index.php --net=master_default --gpu --set-default - .PHONY: dock-sock28 dock-sock28: @echo "creating daemon for nextcloud 'stable28' container" @@ -53,6 +41,18 @@ dock-sock28-gpu: docker exec master-stable28-1 sudo -u www-data php occ app_api:daemon:register \ docker_dev_gpu "Docker with GPU" docker-install http /var/run/docker.sock http://stable28.local/index.php --net=master_default --gpu --set-default +.PHONY: dock-sock29 +dock-sock29: + @echo "creating daemon for nextcloud 'stable29' container" + docker exec master-stable29-1 sudo -u www-data php occ app_api:daemon:register \ + docker_dev Docker docker-install http /var/run/docker.sock http://stable29.local/index.php --net=master_default + +.PHONY: dock-sock29-gpu +dock-sock29-gpu: + @echo "creating daemon with NVIDIA gpu for nextcloud 'stable29' container" + docker exec master-stable29-1 sudo -u www-data php occ app_api:daemon:register \ + docker_dev_gpu "Docker with GPU" docker-install http /var/run/docker.sock http://stable29.local/index.php --net=master_default --gpu --set-default + .PHONY: dock-sock dock-sock: @echo "creating daemon for nextcloud 'master' container" @@ -74,20 +74,20 @@ dock2port: --net=master_default \ --restart unless-stopped --privileged -d ghcr.io/cloud-py-api/nextcloud-appapi-dsp:latest -.PHONY: dock-port27 -dock-port27: - @echo "creating daemon for nextcloud '27' container" - docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:register \ - docker_dev Docker docker-install http nextcloud-appapi-dsp:2375 http://stable27.local/index.php \ - --net=master_default --haproxy_password="some_secure_password" - .PHONY: dock-port28 dock-port28: - @echo "creating daemon for nextcloud '27' container" + @echo "creating daemon for nextcloud '28' container" docker exec master-stable28-1 sudo -u www-data php occ app_api:daemon:register \ docker_dev Docker docker-install http nextcloud-appapi-dsp:2375 http://stable28.local/index.php \ --net=master_default --haproxy_password="some_secure_password" +.PHONY: dock-port29 +dock-port29: + @echo "creating daemon for nextcloud '29' container" + docker exec master-stable29-1 sudo -u www-data php occ app_api:daemon:register \ + docker_dev Docker docker-install http nextcloud-appapi-dsp:2375 http://stable29.local/index.php \ + --net=master_default --haproxy_password="some_secure_password" + .PHONY: dock-port dock-port: @echo "creating daemon for nextcloud 'master' container" diff --git a/appinfo/info.xml b/appinfo/info.xml index d7b32089..259a2889 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -63,7 +63,7 @@ to join us in shaping a more versatile, stable, and secure app landscape. https://raw.githubusercontent.com/cloud-py-api/app_api/main/screenshots/app_api_4.png - + OCA\AppAPI\BackgroundJob\ExAppInitStatusCheckJob diff --git a/lib/Listener/LoadFilesPluginListener.php b/lib/Listener/LoadFilesPluginListener.php index 43bcafe0..2969c939 100644 --- a/lib/Listener/LoadFilesPluginListener.php +++ b/lib/Listener/LoadFilesPluginListener.php @@ -36,12 +36,7 @@ public function handle(Event $event): void { 'fileActions' => $exFilesActions, 'instanceId' => $this->config->getSystemValue('instanceid'), ]); - $ncVersion = $this->config->getSystemValueString('version', '0.0.0'); - if (version_compare($ncVersion, '28.0', '<')) { - Util::addScript(Application::APP_ID, Application::APP_ID . '-filesplugin'); - } else { - Util::addScript(Application::APP_ID, Application::APP_ID . '-filesplugin28'); - } + Util::addScript(Application::APP_ID, Application::APP_ID . '-filesplugin'); Util::addStyle(Application::APP_ID, 'filesactions'); } } diff --git a/src/filesplugin.js b/src/filesplugin.js index 97e5707c..1f606322 100644 --- a/src/filesplugin.js +++ b/src/filesplugin.js @@ -1,67 +1,159 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' import { loadState } from '@nextcloud/initial-state' -import { translate as t } from '@nextcloud/l10n' +import { registerFileAction, FileAction } from '@nextcloud/files' import { getCurrentUser } from '@nextcloud/auth' +import { translate as t } from '@nextcloud/l10n' const state = loadState('app_api', 'ex_files_actions_menu') +function loadStaticAppAPIInlineSvgIcon() { + return '' +} + +function loadExAppInlineSvgIcon(appId, route) { + const url = generateAppAPIProxyUrl(appId, route) + return axios.get(url).then((response) => { + // Check content type to be svg image + if (response.headers['content-type'] !== 'image/svg+xml') { + return null + } + return response.data + }).catch((error) => { + console.error('Failed to load ExApp FileAction icon inline svg', error) + return null + }) +} + function generateAppAPIProxyUrl(appId, route) { return generateUrl(`/apps/app_api/proxy/${appId}/${route}`) } -document.addEventListener('DOMContentLoaded', () => { - if (OCA.Files && OCA.Files.fileActions) { // NC 27 - state.fileActions.forEach(fileAction => { - const mimes = fileAction.mime.split(',').map(mime => mime.trim()) // multiple mimes are separated by comma +function generateExAppUIPageUrl(appId, route) { + return generateUrl(`/apps/app_api/embedded/${appId}/${route}`) +} - const actionHandler = (fileName, context) => { - const file = context.$file[0] - const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler) - axios.post(exAppFileActionHandler, { - fileId: Number(file.dataset.id), - name: fileName, - directory: file.dataset.path, - etag: file.dataset.etag, - mime: file.dataset.mime, - favorite: file.dataset.favorite || 'false', - permissions: Number(file.dataset.permissions), - fileType: file.dataset.type, - size: Number(file.dataset.size), - mtime: Number(file.dataset.mtime) / 1000, // convert ms to s - shareTypes: file.dataset?.shareTypes || null, - shareAttributes: file.dataset?.shareAttributes || null, - sharePermissions: file.dataset?.sharePermissions || null, - shareOwner: file.dataset?.shareOwner || null, - shareOwnerId: file.dataset?.shareOwnerId || null, - userId: getCurrentUser().uid, - instanceId: state.instanceId, - }).then((response) => { - if (response.status === 200) { - OC.dialogs.info(t('app_api', 'Action request sent to ExApp'), t(fileAction.appid, fileAction.display_name)) - } else { - console.debug(response) - OC.dialogs.info(t('app_api', 'Error while sending File action request to ExApp'), t(fileAction.appid, fileAction.display_name)) +function registerFileAction28(fileAction, inlineSvgIcon) { + const action = new FileAction({ + id: fileAction.name, + displayName: () => t(fileAction.appid, fileAction.display_name), + iconSvgInline: () => inlineSvgIcon, + order: Number(fileAction.order), + enabled(files, view) { + if (files.length === 1) { + // Check for multiple mimes separated by comma + let isMimeMatch = false + fileAction.mime.split(',').forEach((mime) => { + if (files[0].mime.indexOf(mime.trim()) !== -1) { + isMimeMatch = true } - }).catch((error) => { - console.error('error', error) - OC.dialogs.info(t('app_api', 'Error while sending File action request to ExApp'), t(fileAction.appid, fileAction.display_name)) }) + return isMimeMatch + } else if (files.length > 1) { + // Check all files match fileAction mime + return files.every((file) => { + // Check for multiple mimes separated by comma + let isMimeMatch = false + fileAction.mime.split(',').forEach((mime) => { + if (file.mime.indexOf(mime.trim()) !== -1) { + isMimeMatch = true + } + }) + return isMimeMatch + }) + } + }, + async exec(node, view, dir) { + const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler) + if ('version' in fileAction && fileAction.version === '2.0') { + return axios.post(exAppFileActionHandler, { files: [buildNodeInfo(node)] }) + .then((response) => { + if (typeof response.data === 'object' && 'redirect_handler' in response.data) { + const redirectPage = generateExAppUIPageUrl(fileAction.appid, response.data.redirect_handler) + window.location.assign(`${redirectPage}?fileIds=${node.fileid}`) + return true + } + return true + }).catch((error) => { + console.error('Failed to send FileAction request to ExApp', error) + return false + }) } + return axios.post(exAppFileActionHandler, buildNodeInfo(node)) + .then(() => { + return true + }) + .catch((error) => { + console.error('Failed to send FileAction request to ExApp', error) + return false + }) + }, + async execBatch(nodes, view, dir) { + if ('version' in fileAction && fileAction.version === '2.0') { + const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler) + const nodesDataList = nodes.map(buildNodeInfo) + return axios.post(exAppFileActionHandler, { files: nodesDataList }) + .then((response) => { + if (typeof response.data === 'object' && 'redirect_handler' in response.data) { + const redirectPage = generateExAppUIPageUrl(fileAction.appid, response.data.redirect_handler) + const fileIds = nodes.map((node) => node.fileid).join(',') + window.location.assign(`${redirectPage}?fileIds=${fileIds}`) + } + return nodes.map(_ => true) + }) + .catch((error) => { + console.error('Failed to send FileAction request to ExApp', error) + return nodes.map(_ => false) + }) + } + // for version 1.0 behavior is not changed + return Promise.all(nodes.map((node) => { + return this.exec(node, view, dir) + })) + }, + }) + registerFileAction(action) +} - mimes.forEach((mimeType) => { - const action = { - name: fileAction.name, - displayName: t(fileAction.appid, fileAction.display_name), - mime: mimeType, - permissions: Number(fileAction.permissions), - order: Number(fileAction.order), - icon: fileAction.icon !== '' ? generateAppAPIProxyUrl(fileAction.appid, fileAction.icon) : null, - iconClass: fileAction.icon === '' ? 'icon-app-api' : '', - actionHandler, +function buildNodeInfo(node) { + return { + fileId: node.fileid, + name: node.basename, + directory: node.dirname, + etag: node.attributes.etag, + mime: node.mime, + favorite: Boolean(node.attributes.favorite).toString(), + permissions: node.permissions, + fileType: node.type, + size: Number(node.size), + mtime: new Date(node.mtime).getTime() / 1000, // convert ms to s + shareTypes: node.attributes.shareTypes || null, + shareAttributes: node.attributes.shareAttributes || null, + sharePermissions: node.attributes.sharePermissions || null, + shareOwner: node.attributes.ownerDisplayName || null, + shareOwnerId: node.attributes.ownerId || null, + userId: getCurrentUser().uid, + instanceId: state.instanceId, + } +} + +document.addEventListener('DOMContentLoaded', () => { + state.fileActions.forEach(fileAction => { + if (fileAction.icon === '') { + const inlineSvgIcon = loadStaticAppAPIInlineSvgIcon() + registerFileAction28(fileAction, inlineSvgIcon) + } else { + loadExAppInlineSvgIcon(fileAction.appid, fileAction.icon).then((svg) => { + if (svg !== null) { + // Set css filter for theming + const parser = new DOMParser() + const icon = parser.parseFromString(svg, 'image/svg+xml') + icon.documentElement.setAttribute('style', 'filter: var(--background-invert-if-dark);') + // Convert back to inline string + const inlineSvgIcon = icon.documentElement.outerHTML + registerFileAction28(fileAction, inlineSvgIcon) } - OCA.Files.fileActions.registerAction(action) }) - }) - } + } + }) }) diff --git a/src/filesplugin28.js b/src/filesplugin28.js deleted file mode 100644 index 1f606322..00000000 --- a/src/filesplugin28.js +++ /dev/null @@ -1,159 +0,0 @@ -import axios from '@nextcloud/axios' -import { generateUrl } from '@nextcloud/router' -import { loadState } from '@nextcloud/initial-state' -import { registerFileAction, FileAction } from '@nextcloud/files' -import { getCurrentUser } from '@nextcloud/auth' -import { translate as t } from '@nextcloud/l10n' - -const state = loadState('app_api', 'ex_files_actions_menu') - -function loadStaticAppAPIInlineSvgIcon() { - return '' -} - -function loadExAppInlineSvgIcon(appId, route) { - const url = generateAppAPIProxyUrl(appId, route) - return axios.get(url).then((response) => { - // Check content type to be svg image - if (response.headers['content-type'] !== 'image/svg+xml') { - return null - } - return response.data - }).catch((error) => { - console.error('Failed to load ExApp FileAction icon inline svg', error) - return null - }) -} - -function generateAppAPIProxyUrl(appId, route) { - return generateUrl(`/apps/app_api/proxy/${appId}/${route}`) -} - -function generateExAppUIPageUrl(appId, route) { - return generateUrl(`/apps/app_api/embedded/${appId}/${route}`) -} - -function registerFileAction28(fileAction, inlineSvgIcon) { - const action = new FileAction({ - id: fileAction.name, - displayName: () => t(fileAction.appid, fileAction.display_name), - iconSvgInline: () => inlineSvgIcon, - order: Number(fileAction.order), - enabled(files, view) { - if (files.length === 1) { - // Check for multiple mimes separated by comma - let isMimeMatch = false - fileAction.mime.split(',').forEach((mime) => { - if (files[0].mime.indexOf(mime.trim()) !== -1) { - isMimeMatch = true - } - }) - return isMimeMatch - } else if (files.length > 1) { - // Check all files match fileAction mime - return files.every((file) => { - // Check for multiple mimes separated by comma - let isMimeMatch = false - fileAction.mime.split(',').forEach((mime) => { - if (file.mime.indexOf(mime.trim()) !== -1) { - isMimeMatch = true - } - }) - return isMimeMatch - }) - } - }, - async exec(node, view, dir) { - const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler) - if ('version' in fileAction && fileAction.version === '2.0') { - return axios.post(exAppFileActionHandler, { files: [buildNodeInfo(node)] }) - .then((response) => { - if (typeof response.data === 'object' && 'redirect_handler' in response.data) { - const redirectPage = generateExAppUIPageUrl(fileAction.appid, response.data.redirect_handler) - window.location.assign(`${redirectPage}?fileIds=${node.fileid}`) - return true - } - return true - }).catch((error) => { - console.error('Failed to send FileAction request to ExApp', error) - return false - }) - } - return axios.post(exAppFileActionHandler, buildNodeInfo(node)) - .then(() => { - return true - }) - .catch((error) => { - console.error('Failed to send FileAction request to ExApp', error) - return false - }) - }, - async execBatch(nodes, view, dir) { - if ('version' in fileAction && fileAction.version === '2.0') { - const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler) - const nodesDataList = nodes.map(buildNodeInfo) - return axios.post(exAppFileActionHandler, { files: nodesDataList }) - .then((response) => { - if (typeof response.data === 'object' && 'redirect_handler' in response.data) { - const redirectPage = generateExAppUIPageUrl(fileAction.appid, response.data.redirect_handler) - const fileIds = nodes.map((node) => node.fileid).join(',') - window.location.assign(`${redirectPage}?fileIds=${fileIds}`) - } - return nodes.map(_ => true) - }) - .catch((error) => { - console.error('Failed to send FileAction request to ExApp', error) - return nodes.map(_ => false) - }) - } - // for version 1.0 behavior is not changed - return Promise.all(nodes.map((node) => { - return this.exec(node, view, dir) - })) - }, - }) - registerFileAction(action) -} - -function buildNodeInfo(node) { - return { - fileId: node.fileid, - name: node.basename, - directory: node.dirname, - etag: node.attributes.etag, - mime: node.mime, - favorite: Boolean(node.attributes.favorite).toString(), - permissions: node.permissions, - fileType: node.type, - size: Number(node.size), - mtime: new Date(node.mtime).getTime() / 1000, // convert ms to s - shareTypes: node.attributes.shareTypes || null, - shareAttributes: node.attributes.shareAttributes || null, - sharePermissions: node.attributes.sharePermissions || null, - shareOwner: node.attributes.ownerDisplayName || null, - shareOwnerId: node.attributes.ownerId || null, - userId: getCurrentUser().uid, - instanceId: state.instanceId, - } -} - -document.addEventListener('DOMContentLoaded', () => { - state.fileActions.forEach(fileAction => { - if (fileAction.icon === '') { - const inlineSvgIcon = loadStaticAppAPIInlineSvgIcon() - registerFileAction28(fileAction, inlineSvgIcon) - } else { - loadExAppInlineSvgIcon(fileAction.appid, fileAction.icon).then((svg) => { - if (svg !== null) { - // Set css filter for theming - const parser = new DOMParser() - const icon = parser.parseFromString(svg, 'image/svg+xml') - icon.documentElement.setAttribute('style', 'filter: var(--background-invert-if-dark);') - // Convert back to inline string - const inlineSvgIcon = icon.documentElement.outerHTML - registerFileAction28(fileAction, inlineSvgIcon) - } - }) - } - }) -}) diff --git a/webpack.js b/webpack.js index a40f03fc..203ae358 100644 --- a/webpack.js +++ b/webpack.js @@ -22,7 +22,6 @@ webpackConfig.entry = { main: { import: path.join(__dirname, 'src', 'main.js'), filename: appId + '-main.js' }, adminSettings: { import: path.join(__dirname, 'src', 'adminSettings.js'), filename: appId + '-adminSettings.js' }, filesplugin: { import: path.join(__dirname, 'src', 'filesplugin.js'), filename: appId + '-filesplugin.js' }, - filesplugin28: { import: path.join(__dirname, 'src', 'filesplugin28.js'), filename: appId + '-filesplugin28.js' }, } webpackConfig.plugins.push(