mirror of https://github.com/sualko/cloud_bbb
fix: #373 missing send file to bbb menu, use vuejs for dialog
Signed-off-by: Sebastien Marinier <sebastien.marinier@arawa.fr>pull/408/head
parent
c5a0129d9a
commit
e248a12ec1
21
.eslintrc.js
21
.eslintrc.js
|
|
@ -1,12 +1,15 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: '@typescript-eslint/parser',
|
parser: 'vue-eslint-parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
jsx: true, // Allows for the parsing of JSX
|
jsx: true, // Allows for the parsing of JSX
|
||||||
},
|
},
|
||||||
|
extraFileExtensions: ['.vue'],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
'vue',
|
||||||
'@typescript-eslint',
|
'@typescript-eslint',
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
|
|
@ -15,6 +18,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
|
'plugin:vue/recommended',
|
||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
'plugin:@typescript-eslint/eslint-recommended',
|
'plugin:@typescript-eslint/eslint-recommended',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
|
@ -31,5 +35,18 @@ module.exports = {
|
||||||
indent: ['warn', 'tab'],
|
indent: ['warn', 'tab'],
|
||||||
semi: ['error', 'always'],
|
semi: ['error', 'always'],
|
||||||
'@typescript-eslint/ban-types': 'off',
|
'@typescript-eslint/ban-types': 'off',
|
||||||
|
'vue/script-setup-uses-vars': 'error',
|
||||||
|
'vue/html-indent': ['warn', 'tab', {
|
||||||
|
attribute: 1,
|
||||||
|
baseIndent: 1,
|
||||||
|
closeBracket: 0,
|
||||||
|
alignAttributesVertically: true,
|
||||||
|
ignores: []
|
||||||
|
}],
|
||||||
|
'vue/first-attribute-linebreak': 'off',
|
||||||
|
'vue/max-attributes-per-line': ['warn', {
|
||||||
|
singleline: 5,
|
||||||
|
multiline: 1
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
|
@ -22,8 +22,8 @@
|
||||||
"test:php:integration": "dotenv ./vendor/bin/phpunit -- -c phpunit.integration.xml",
|
"test:php:integration": "dotenv ./vendor/bin/phpunit -- -c phpunit.integration.xml",
|
||||||
"fix": "run-p --continue-on-error --print-label lint:fix:*",
|
"fix": "run-p --continue-on-error --print-label lint:fix:*",
|
||||||
"lint": "run-p --continue-on-error --print-label lint:*",
|
"lint": "run-p --continue-on-error --print-label lint:*",
|
||||||
"lint:script": "eslint --ext .tsx,.ts ts",
|
"lint:script": "eslint --ext .tsx,.ts,.vue ts",
|
||||||
"lint:fix:script": "eslint --ext .tsx,.ts ts --fix",
|
"lint:fix:script": "eslint --ext .tsx,.ts,.vue ts --fix",
|
||||||
"lint:style": "stylelint 'ts/**/*.scss'",
|
"lint:style": "stylelint 'ts/**/*.scss'",
|
||||||
"lint:fix:style": "stylelint 'ts/**/*.scss' --fix",
|
"lint:fix:style": "stylelint 'ts/**/*.scss' --fix",
|
||||||
"lint:php": "./vendor/bin/php-cs-fixer fix --dry-run",
|
"lint:php": "./vendor/bin/php-cs-fixer fix --dry-run",
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
"@nextcloud/dialogs": "^6.0.1",
|
"@nextcloud/dialogs": "^6.0.1",
|
||||||
"@nextcloud/files": "^3.12.0",
|
"@nextcloud/files": "^3.12.0",
|
||||||
"@nextcloud/router": "^3.0.1",
|
"@nextcloud/router": "^3.0.1",
|
||||||
|
"@nextcloud/vue": "^8.22.0",
|
||||||
"@octokit/rest": "^18.0.4",
|
"@octokit/rest": "^18.0.4",
|
||||||
"archiver": "^5.0.0",
|
"archiver": "^5.0.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
|
|
@ -52,7 +53,8 @@
|
||||||
"qrcode.react": "^2.0.0",
|
"qrcode.react": "^2.0.0",
|
||||||
"react-copy-to-clipboard": "^5.0.2",
|
"react-copy-to-clipboard": "^5.0.2",
|
||||||
"sass": "^1.89.0",
|
"sass": "^1.89.0",
|
||||||
"simple-git": "^3.16.0"
|
"simple-git": "^3.16.0",
|
||||||
|
"vue": "^2.7.16"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
|
@ -114,7 +116,7 @@
|
||||||
"stylelint-config-recommended-scss": "^5.0.2",
|
"stylelint-config-recommended-scss": "^5.0.2",
|
||||||
"stylelint-scss": "^4.2.0",
|
"stylelint-scss": "^4.2.0",
|
||||||
"ts-loader": "^9.2.8",
|
"ts-loader": "^9.2.8",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^5.9.2",
|
||||||
"url-loader": "^4.0.0",
|
"url-loader": "^4.0.0",
|
||||||
"webpack": "^5.70.0",
|
"webpack": "^5.70.0",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^5.1.4",
|
||||||
|
|
|
||||||
138
ts/filelist.ts
138
ts/filelist.ts
|
|
@ -1,13 +1,17 @@
|
||||||
import axios from '@nextcloud/axios';
|
import axios from '@nextcloud/axios';
|
||||||
|
|
||||||
import { generateOcsUrl, generateUrl } from '@nextcloud/router';
|
import { generateOcsUrl, generateUrl } from '@nextcloud/router';
|
||||||
import { showSuccess, showWarning, showError } from '@nextcloud/dialogs';
|
import { showSuccess, showWarning, showError } from '@nextcloud/dialogs';
|
||||||
|
// import * as Files from '@nextcloud/files';
|
||||||
|
import { FileAction, registerFileAction } from '@nextcloud/files';
|
||||||
import { api } from './Common/Api';
|
import { api } from './Common/Api';
|
||||||
import './filelist.scss';
|
import Vue from 'vue';
|
||||||
|
import SendFileDialog from './views/SendFileDialog.vue';
|
||||||
|
import iconBBBInline from '../img/app-dark.svg?raw';
|
||||||
|
|
||||||
type OC_Dialogs_Message = (content: string, title: string, dialogType: 'notice' | 'alert' | 'warn' | 'none', buttons?: number, callback?: () => void, modal?: boolean, allowHtml?: boolean) => Promise<void>;
|
type NCNode = any;
|
||||||
type ExtendedDialogs = typeof OC.dialogs & { message: OC_Dialogs_Message };
|
|
||||||
|
|
||||||
const mimeTypes = [
|
const mimeTypes: readonly string[] = [
|
||||||
'application/pdf',
|
'application/pdf',
|
||||||
'application/vnd.oasis.opendocument.presentation',
|
'application/vnd.oasis.opendocument.presentation',
|
||||||
'application/vnd.oasis.opendocument.text',
|
'application/vnd.oasis.opendocument.text',
|
||||||
|
|
@ -23,9 +27,7 @@ const mimeTypes = [
|
||||||
'image/png',
|
'image/png',
|
||||||
'text/plain',
|
'text/plain',
|
||||||
'text/rtf',
|
'text/rtf',
|
||||||
] as const;
|
];
|
||||||
|
|
||||||
type MimeTypes = typeof mimeTypes[number];
|
|
||||||
|
|
||||||
async function createDirectShare(fileId: number): Promise<string> {
|
async function createDirectShare(fileId: number): Promise<string> {
|
||||||
const url = generateOcsUrl('apps/dav/api/v1/', undefined, {
|
const url = generateOcsUrl('apps/dav/api/v1/', undefined, {
|
||||||
|
|
@ -54,7 +56,7 @@ function insertDocumentToRoom(shareUrl: string, filename: string, roomUid: strin
|
||||||
return api.insertDocument(roomUid, shareUrl, filename);
|
return api.insertDocument(roomUid, shareUrl, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendFile(fileId: number, filename: string, roomUid: string) {
|
export async function sendFileToBBB(fileId: number, filename: string, roomUid: string) {
|
||||||
const shareUrl = await createDirectShare(fileId);
|
const shareUrl = await createDirectShare(fileId);
|
||||||
const isRunning = await api.isRunning(roomUid);
|
const isRunning = await api.isRunning(roomUid);
|
||||||
|
|
||||||
|
|
@ -75,80 +77,58 @@ async function sendFile(fileId: number, filename: string, roomUid: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openDialog(fileId: number, filename: string) {
|
/**
|
||||||
const initContent = '<div id="bbb-file-action"><span className="icon icon-loading-small icon-visible"></span></div>';
|
* Create a DOM component to mount the dialog Vue component
|
||||||
const title = t('bbb', 'Send file to BBB');
|
*
|
||||||
|
* @param fileId number
|
||||||
|
* @param filename string
|
||||||
|
*/
|
||||||
|
export function showSendFileDialog(fileId: number, filename: string ) {
|
||||||
|
const mount = document.createElement('div');
|
||||||
|
mount.id = 'bbb-widget-container';
|
||||||
|
document.body.appendChild(mount);
|
||||||
|
|
||||||
const exDialogs = OC.dialogs as ExtendedDialogs;
|
const vm = new Vue({
|
||||||
|
el: '#bbb-widget-container',
|
||||||
await exDialogs.message(initContent, title, 'none', -1, undefined, true, true);
|
render: h => h(SendFileDialog, {
|
||||||
|
props: {
|
||||||
const rooms = await api.getRooms();
|
fileId,
|
||||||
|
filename,
|
||||||
const container = $('#bbb-file-action').empty();
|
},
|
||||||
const table = $('<table>').appendTo(container);
|
on: {
|
||||||
table.attr('style', 'margin-top: 1em; width: 100%;');
|
// listen to 'close' event emitted by the dialog component, to clean up
|
||||||
|
close: () => {
|
||||||
for (const room of rooms) {
|
vm.$destroy();
|
||||||
const row = $('<tr>');
|
mount.remove();
|
||||||
const button = $('<button>');
|
},
|
||||||
|
},
|
||||||
button.text(room.running ? t('bbb', 'Send to') : t('bbb', 'Start with'));
|
}),
|
||||||
button.addClass(room.running ? 'success' : 'primary');
|
|
||||||
button.attr('type', 'button');
|
|
||||||
button.on('click', (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
table.find('button').prop('disabled', true);
|
|
||||||
$(ev.target).addClass('icon-loading-small');
|
|
||||||
|
|
||||||
sendFile(fileId, filename, room.uid).then(() => {
|
|
||||||
container.parents('.oc-dialog').find('.oc-dialog-close').trigger('click');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
row.append($('<td>').append(button));
|
|
||||||
row.append($('<td>').attr('style', 'width: 100%;').text(room.name));
|
|
||||||
row.appendTo(table);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rooms.length > 0) {
|
|
||||||
const description = t('bbb', 'Please select the room in which you like to use the file "{filename}".', { filename });
|
|
||||||
|
|
||||||
container.append(description);
|
|
||||||
container.append(table);
|
|
||||||
} else {
|
|
||||||
container.append($('p').text(t('bbb', 'No rooms available!')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerFileAction(fileActions: any, mime: MimeTypes) {
|
|
||||||
fileActions.registerAction({
|
|
||||||
name: 'bbb',
|
|
||||||
displayName: t('bbb', 'Send to BBB'),
|
|
||||||
mime,
|
|
||||||
permissions: OC.PERMISSION_SHARE,
|
|
||||||
icon: OC.imagePath('bbb', 'app-dark.svg'),
|
|
||||||
actionHandler: (fileName, context) => {
|
|
||||||
console.log('Action handler');
|
|
||||||
|
|
||||||
openDialog(context.fileInfoModel.id, fileName);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const BBBFileListPlugin = {
|
/**
|
||||||
ignoreLists: [
|
* Register the file action "Send to BBB"
|
||||||
'trashbin',
|
*/
|
||||||
],
|
registerFileAction( new FileAction({
|
||||||
|
id: 'bbb-send-file',
|
||||||
attach(fileList) {
|
displayName: () => {
|
||||||
if (this.ignoreLists.includes(fileList.id) || !OC.currentUser) {
|
return t('bbb', 'Send to BBB');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mimeTypes.forEach(mime => registerFileAction(fileList.fileActions, mime));
|
|
||||||
},
|
},
|
||||||
};
|
enabled: (nodes) => {
|
||||||
|
// only files with the mime type allowed
|
||||||
|
if (!Array.isArray(nodes) || nodes.length === 0) return false;
|
||||||
|
|
||||||
OC.Plugins.register('OCA.Files.FileList', BBBFileListPlugin);
|
return nodes.every((node): boolean | null => {
|
||||||
|
const mime = node.mime;
|
||||||
|
if (!mime) return false;
|
||||||
|
// enable only for allowed mime types
|
||||||
|
return mimeTypes.includes(mime);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
iconSvgInline: () => iconBBBInline,
|
||||||
|
exec: async (node: NCNode) : Promise<boolean|null> => {
|
||||||
|
showSendFileDialog(node.fileid, node.displayname);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
order: 20,
|
||||||
|
}));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
declare module '*.svg?raw' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import { DefineComponent } from 'vue';
|
||||||
|
const component: DefineComponent<{}, {}, any>;
|
||||||
|
export default component;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
<template>
|
||||||
|
<NcDialog
|
||||||
|
dialog-classes="bbb-send-file-dialog"
|
||||||
|
size="normal"
|
||||||
|
:visible="opened"
|
||||||
|
:name="title"
|
||||||
|
@closing="onClose"
|
||||||
|
>
|
||||||
|
<NcLoadingIcon v-if="loading"
|
||||||
|
:size="32"
|
||||||
|
:name="t('bbb', 'Loading…')"
|
||||||
|
appearance="dark"
|
||||||
|
/>
|
||||||
|
<div v-else class="send-file__content">
|
||||||
|
<slot name="body">
|
||||||
|
<p>
|
||||||
|
{{ t('bbb', 'Please select the room in which you like to use the file "{filename}".', { filename }) }}
|
||||||
|
</p>
|
||||||
|
<p class="note">
|
||||||
|
{{ t('bbb', 'Only rooms for which you are one of the administrators will be displayed.') }}
|
||||||
|
</p>
|
||||||
|
<div class="send-file__content__container">
|
||||||
|
<table v-if="rooms.length > 0" class="send-file__content__table">
|
||||||
|
<tr v-for="room in rooms" :key="room.id">
|
||||||
|
<td>
|
||||||
|
<NcButton
|
||||||
|
:variant="room.running ? 'success' : 'primary'"
|
||||||
|
@click="sendFileToRoom(room.uid)"
|
||||||
|
>
|
||||||
|
{{ room.running ? t('bbb', 'Send to') : t('bbb', 'Start with') }}
|
||||||
|
</NcButton>
|
||||||
|
</td>
|
||||||
|
<td>{{ room.name }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p v-else>
|
||||||
|
{{ t('bbb', 'No rooms available!') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<template #actions>
|
||||||
|
<button class="nc-btn nc-btn--tertiary" @click="onClose">
|
||||||
|
{{ t('bbb','Close') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</NcDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NcDialog from '@nextcloud/vue/components/NcDialog';
|
||||||
|
import NcButton from '@nextcloud/vue/components/NcButton';
|
||||||
|
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon';
|
||||||
|
import { showError } from '@nextcloud/dialogs';
|
||||||
|
import { translate as t } from '@nextcloud/l10n';
|
||||||
|
import { api, Permission } from '../Common/Api';
|
||||||
|
import { sendFileToBBB } from '../filelist';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SendFileDialog',
|
||||||
|
components: {
|
||||||
|
NcDialog,
|
||||||
|
NcLoadingIcon,
|
||||||
|
NcButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
filename: { type: String, required: true },
|
||||||
|
fileId: { type: Number, required: true },
|
||||||
|
title: { type: String, default: t('bbb', 'Send to BBB') },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
opened: false,
|
||||||
|
loading: true,
|
||||||
|
rooms: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
|
||||||
|
api.getRooms().then((rooms) => {
|
||||||
|
this.rooms = rooms.filter(room => room.permission === Permission.Admin);
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
t,
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
this.opened = false;
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
La confirmation déclenche un événement 'confirm'.
|
||||||
|
Si la logique d'envoi est asynchrone côté parent, le parent peut gérer le flag `modelValue`/un loader.
|
||||||
|
Ici on active un loader visuel court pour l'UX puis on ferme la dialog.
|
||||||
|
*/
|
||||||
|
onConfirm() {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
this.$emit('confirm');
|
||||||
|
} finally {
|
||||||
|
// fermeture et reset loader (simulé court délai si nécessaire)
|
||||||
|
this.loading = false;
|
||||||
|
this.$emit('update:modelValue', false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sendFileToRoom(roomUid) {
|
||||||
|
this.loading = true;
|
||||||
|
sendFileToBBB(this.fileId, this.filename, roomUid).then(() => {
|
||||||
|
this.loading = false;
|
||||||
|
this.$emit('sent', roomUid);
|
||||||
|
this.onClose();
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false;
|
||||||
|
showError(t('bbb', 'An error occurred while sending the file to the room.'));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.send-file__title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.send-file__info {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.nc-btn {
|
||||||
|
min-width: 96px;
|
||||||
|
}
|
||||||
|
.send-file__content {
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
&__table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 1em;
|
||||||
|
td {
|
||||||
|
padding: 0.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
width: 80%;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: scroll auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.success {
|
||||||
|
background-color: var(--color-success);
|
||||||
|
border-color: var(--color-success-hover);
|
||||||
|
color: var(--color-primary-text);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-success-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bbb-send-file-dialog {
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
.send-file__content .note {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-secondary-text);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue