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
19
.eslintrc.js
19
.eslintrc.js
|
|
@ -1,12 +1,15 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaFeatures: {
|
||||
jsx: true, // Allows for the parsing of JSX
|
||||
},
|
||||
extraFileExtensions: ['.vue'],
|
||||
},
|
||||
plugins: [
|
||||
'vue',
|
||||
'@typescript-eslint',
|
||||
],
|
||||
settings: {
|
||||
|
|
@ -15,6 +18,7 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
|
|
@ -31,5 +35,18 @@ module.exports = {
|
|||
indent: ['warn', 'tab'],
|
||||
semi: ['error', 'always'],
|
||||
'@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",
|
||||
"fix": "run-p --continue-on-error --print-label lint:fix:*",
|
||||
"lint": "run-p --continue-on-error --print-label lint:*",
|
||||
"lint:script": "eslint --ext .tsx,.ts ts",
|
||||
"lint:fix:script": "eslint --ext .tsx,.ts ts --fix",
|
||||
"lint:script": "eslint --ext .tsx,.ts,.vue ts",
|
||||
"lint:fix:script": "eslint --ext .tsx,.ts,.vue ts --fix",
|
||||
"lint:style": "stylelint 'ts/**/*.scss'",
|
||||
"lint:fix:style": "stylelint 'ts/**/*.scss' --fix",
|
||||
"lint:php": "./vendor/bin/php-cs-fixer fix --dry-run",
|
||||
|
|
@ -41,6 +41,7 @@
|
|||
"@nextcloud/dialogs": "^6.0.1",
|
||||
"@nextcloud/files": "^3.12.0",
|
||||
"@nextcloud/router": "^3.0.1",
|
||||
"@nextcloud/vue": "^8.22.0",
|
||||
"@octokit/rest": "^18.0.4",
|
||||
"archiver": "^5.0.0",
|
||||
"colors": "^1.4.0",
|
||||
|
|
@ -52,7 +53,8 @@
|
|||
"qrcode.react": "^2.0.0",
|
||||
"react-copy-to-clipboard": "^5.0.2",
|
||||
"sass": "^1.89.0",
|
||||
"simple-git": "^3.16.0"
|
||||
"simple-git": "^3.16.0",
|
||||
"vue": "^2.7.16"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
|
@ -114,7 +116,7 @@
|
|||
"stylelint-config-recommended-scss": "^5.0.2",
|
||||
"stylelint-scss": "^4.2.0",
|
||||
"ts-loader": "^9.2.8",
|
||||
"typescript": "^4.9.3",
|
||||
"typescript": "^5.9.2",
|
||||
"url-loader": "^4.0.0",
|
||||
"webpack": "^5.70.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
|
|
|
|||
138
ts/filelist.ts
138
ts/filelist.ts
|
|
@ -1,13 +1,17 @@
|
|||
import axios from '@nextcloud/axios';
|
||||
|
||||
import { generateOcsUrl, generateUrl } from '@nextcloud/router';
|
||||
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 './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 ExtendedDialogs = typeof OC.dialogs & { message: OC_Dialogs_Message };
|
||||
type NCNode = any;
|
||||
|
||||
const mimeTypes = [
|
||||
const mimeTypes: readonly string[] = [
|
||||
'application/pdf',
|
||||
'application/vnd.oasis.opendocument.presentation',
|
||||
'application/vnd.oasis.opendocument.text',
|
||||
|
|
@ -23,9 +27,7 @@ const mimeTypes = [
|
|||
'image/png',
|
||||
'text/plain',
|
||||
'text/rtf',
|
||||
] as const;
|
||||
|
||||
type MimeTypes = typeof mimeTypes[number];
|
||||
];
|
||||
|
||||
async function createDirectShare(fileId: number): Promise<string> {
|
||||
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);
|
||||
}
|
||||
|
||||
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 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>';
|
||||
const title = t('bbb', 'Send file to BBB');
|
||||
/**
|
||||
* Create a DOM component to mount the dialog Vue component
|
||||
*
|
||||
* @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;
|
||||
|
||||
await exDialogs.message(initContent, title, 'none', -1, undefined, true, true);
|
||||
|
||||
const rooms = await api.getRooms();
|
||||
|
||||
const container = $('#bbb-file-action').empty();
|
||||
const table = $('<table>').appendTo(container);
|
||||
table.attr('style', 'margin-top: 1em; width: 100%;');
|
||||
|
||||
for (const room of rooms) {
|
||||
const row = $('<tr>');
|
||||
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 vm = new Vue({
|
||||
el: '#bbb-widget-container',
|
||||
render: h => h(SendFileDialog, {
|
||||
props: {
|
||||
fileId,
|
||||
filename,
|
||||
},
|
||||
on: {
|
||||
// listen to 'close' event emitted by the dialog component, to clean up
|
||||
close: () => {
|
||||
vm.$destroy();
|
||||
mount.remove();
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
const BBBFileListPlugin = {
|
||||
ignoreLists: [
|
||||
'trashbin',
|
||||
],
|
||||
|
||||
attach(fileList) {
|
||||
if (this.ignoreLists.includes(fileList.id) || !OC.currentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
mimeTypes.forEach(mime => registerFileAction(fileList.fileActions, mime));
|
||||
/**
|
||||
* Register the file action "Send to BBB"
|
||||
*/
|
||||
registerFileAction( new FileAction({
|
||||
id: 'bbb-send-file',
|
||||
displayName: () => {
|
||||
return t('bbb', 'Send to BBB');
|
||||
},
|
||||
};
|
||||
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