Skip to content

Commit

Permalink
Chore: Rewrite SaveToWebdav Modal to React Component (#24365)
Browse files Browse the repository at this point in the history
Co-authored-by: Pierre Lehnen <[email protected]>
  • Loading branch information
2 people authored and carlosrodrigues94 committed Aug 3, 2022
1 parent 7dbf2ed commit 31360e1
Show file tree
Hide file tree
Showing 18 changed files with 192 additions and 108 deletions.
27 changes: 12 additions & 15 deletions apps/meteor/app/webdav/client/actionButton.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Meteor } from 'meteor/meteor';

import { t, getURL } from '../../utils/client';
import { getURL } from '../../utils/client';
import { WebdavAccounts } from '../../models/client';
import { settings } from '../../settings/client';
import { MessageAction, modal } from '../../ui-utils/client';
import { MessageAction } from '../../ui-utils/client';
import { messageArgs } from '../../../client/lib/utils/messageArgs';
import { imperativeModal } from '../../../client/lib/imperativeModal';
import SaveToWebdav from '../../../client/views/room/webdav/SaveToWebdavModal';

Meteor.startup(function () {
MessageAction.addButton({
Expand All @@ -27,21 +29,16 @@ Meteor.startup(function () {
action(_, props) {
const { message = messageArgs(this).msg } = props;
const [attachment] = message.attachments || [];
const { file } = message;
const url = getURL(attachment.title_link, { full: true });
modal.open({
data: {
message,
attachment,
file,
url,
imperativeModal.open({
component: SaveToWebdav,
props: {
data: {
attachment,
url,
},
onClose: imperativeModal.close,
},
title: t('Save_To_Webdav'),
content: 'selectWebdavAccount',
showCancelButton: true,
showConfirmButton: false,
closeOnCancel: true,
html: true,
});
},
order: 100,
Expand Down
2 changes: 0 additions & 2 deletions apps/meteor/app/webdav/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,5 @@ Meteor.startup(() => {
import('./webdavFilePicker.html');
import('./webdavFilePicker.css');
import('./webdavFilePicker');
import('./selectWebdavAccount.html');
import('./selectWebdavAccount');
});
});
13 changes: 0 additions & 13 deletions apps/meteor/app/webdav/client/selectWebdavAccount.html

This file was deleted.

44 changes: 0 additions & 44 deletions apps/meteor/app/webdav/client/selectWebdavAccount.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ServerCredentials } from '../lib/webdavClientAdapter';
import { ServerCredentials } from './webdavClientAdapter';

export function getWebdavCredentials(account: ServerCredentials): ServerCredentials {
const cred = account.token
Expand Down
21 changes: 21 additions & 0 deletions apps/meteor/app/webdav/server/lib/uploadFileToWebdav.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { WebdavAccounts } from '@rocket.chat/models';
import type { IWebdavAccount } from '@rocket.chat/core-typings';

import { getWebdavCredentials } from './getWebdavCredentials';
import { WebdavClientAdapter } from './webdavClientAdapter';

export const uploadFileToWebdav = async (accountId: IWebdavAccount['_id'], fileData: string | Buffer, name: string): Promise<void> => {
const account = await WebdavAccounts.findOneById(accountId);
if (!account) {
throw new Error('error-invalid-account');
}

const uploadFolder = 'Rocket.Chat Uploads/';
const buffer = Buffer.from(fileData);

const cred = getWebdavCredentials(account);
const client = new WebdavClientAdapter(account.serverURL, cred);
// eslint-disable-next-line @typescript-eslint/no-empty-function
await client.createDirectory(uploadFolder).catch(() => {});
await client.putFileContents(`${uploadFolder}/${name}`, buffer, { overwrite: false });
};
2 changes: 1 addition & 1 deletion apps/meteor/app/webdav/server/methods/getFileFromWebdav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
import { WebdavAccounts } from '@rocket.chat/models';

import { settings } from '../../../settings/server';
import { getWebdavCredentials } from './getWebdavCredentials';
import { getWebdavCredentials } from '../lib/getWebdavCredentials';
import { WebdavClientAdapter } from '../lib/webdavClientAdapter';

Meteor.methods({
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/webdav/server/methods/getWebdavFileList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
import { WebdavAccounts } from '@rocket.chat/models';

import { settings } from '../../../settings/server';
import { getWebdavCredentials } from './getWebdavCredentials';
import { getWebdavCredentials } from '../lib/getWebdavCredentials';
import { WebdavClientAdapter } from '../lib/webdavClientAdapter';

Meteor.methods({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createClient } from 'webdav';
import { WebdavAccounts } from '@rocket.chat/models';

import { settings } from '../../../settings/server';
import { getWebdavCredentials } from './getWebdavCredentials';
import { getWebdavCredentials } from '../lib/getWebdavCredentials';

Meteor.methods({
async getWebdavFilePreview(accountId, path) {
Expand Down
31 changes: 11 additions & 20 deletions apps/meteor/app/webdav/server/methods/uploadFileToWebdav.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,36 @@
import { Meteor } from 'meteor/meteor';
import { WebdavAccounts } from '@rocket.chat/models';

import { MeteorError } from '../../../../server/sdk/errors';
import { settings } from '../../../settings/server';
import { Logger } from '../../../logger/server';
import { getWebdavCredentials } from './getWebdavCredentials';
import { WebdavClientAdapter } from '../lib/webdavClientAdapter';
import { uploadFileToWebdav } from '../lib/uploadFileToWebdav';

const logger = new Logger('WebDAV_Upload');

Meteor.methods({
async uploadFileToWebdav(accountId, fileData, name) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid User', {
throw new MeteorError('error-invalid-user', 'Invalid User', {
method: 'uploadFileToWebdav',
});
}

if (!settings.get('Webdav_Integration_Enabled')) {
throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', {
throw new MeteorError('error-not-allowed', 'WebDAV Integration Not Allowed', {
method: 'uploadFileToWebdav',
});
}

const account = await WebdavAccounts.findOneById(accountId);
if (!account) {
throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', {
method: 'uploadFileToWebdav',
});
}

const uploadFolder = 'Rocket.Chat Uploads/';
const buffer = Buffer.from(fileData);

try {
const cred = getWebdavCredentials(account);
const client = new WebdavClientAdapter(account.serverURL, cred);
// eslint-disable-next-line @typescript-eslint/no-empty-function
await client.createDirectory(uploadFolder).catch(() => {});
await client.putFileContents(`${uploadFolder}/${name}`, buffer, { overwrite: false });
await uploadFileToWebdav(accountId, fileData, name);
return { success: true };
} catch (error: any) {
if (typeof error === 'object' && error instanceof Error && error.name === 'error-invalid-account') {
throw new MeteorError(error.name, 'Invalid WebDAV Account', {
method: 'uploadFileToWebdav',
});
}

// @ts-ignore
logger.error(error);

Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/client/lib/getWebdavServerName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { IWebdavAccountIntegration } from '@rocket.chat/core-typings';

export const getWebdavServerName = ({ name, serverURL, username }: Omit<IWebdavAccountIntegration, '_id'>): string =>
name || `${username}@${serverURL?.replace(/^https?\:\/\//i, '')}`;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IWebdavAccount } from '@rocket.chat/core-typings';
import type { IWebdavAccountIntegration } from '@rocket.chat/core-typings';
import { Box, Select, SelectOption, Field, Button } from '@rocket.chat/fuselage';
import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import React, { useMemo, useCallback, ReactElement } from 'react';
Expand All @@ -7,13 +7,9 @@ import { WebdavAccounts } from '../../../../app/models/client';
import Page from '../../../components/Page';
import { useForm } from '../../../hooks/useForm';
import { useReactiveValue } from '../../../hooks/useReactiveValue';
import { getWebdavServerName } from '../../../lib/getWebdavServerName';

type WebdavAccountIntegration = Omit<IWebdavAccount, 'userId' | 'password' | '_updatedAt'>;

const getWebdavAccounts = (): Array<WebdavAccountIntegration> => WebdavAccounts.find().fetch();

const getServerName = ({ name, serverURL, username }: Omit<WebdavAccountIntegration, '_id'>): string =>
name || `${username}@${serverURL?.replace(/^https?\:\/\//i, '')}`;
const getWebdavAccounts = (): IWebdavAccountIntegration[] => WebdavAccounts.find().fetch();

const AccountIntegrationsPage = (): ReactElement => {
const t = useTranslation();
Expand All @@ -26,7 +22,7 @@ const AccountIntegrationsPage = (): ReactElement => {
handlers: { handleSelected },
} = useForm({ selected: [] });

const options: SelectOption[] = useMemo(() => accounts.map(({ _id, ...current }) => [_id, getServerName(current)]), [accounts]);
const options: SelectOption[] = useMemo(() => accounts?.map(({ _id, ...current }) => [_id, getWebdavServerName(current)]), [accounts]);

const handleClickRemove = useCallback(() => {
try {
Expand Down
120 changes: 120 additions & 0 deletions apps/meteor/client/views/room/webdav/SaveToWebdavModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { MessageAttachment, IWebdavAccount } from '@rocket.chat/core-typings';
import { Modal, Box, ButtonGroup, Button, FieldGroup, Field, Select, SelectOption, Throbber } from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useMethod, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement, useState, useMemo, useEffect, useRef } from 'react';
import { useForm, Controller } from 'react-hook-form';

import { useEndpointData } from '../../../hooks/useEndpointData';
import { getWebdavServerName } from '../../../lib/getWebdavServerName';

type SaveToWebdavModalProps = {
onClose: () => void;
data: {
attachment: MessageAttachment;
url: string;
};
};

const SaveToWebdavModal = ({ onClose, data }: SaveToWebdavModalProps): ReactElement => {
const t = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const dispatchToastMessage = useToastMessageDispatch();
const uploadFileToWebdav = useMethod('uploadFileToWebdav');
const fileRequest = useRef<XMLHttpRequest | null>(null);
const accountIdField = useUniqueId();

const {
control,
handleSubmit,
formState: { errors },
} = useForm<{ accountId: string }>();

const { value } = useEndpointData('/v1/webdav.getMyAccounts');

const accountsOptions: SelectOption[] = useMemo(() => {
if (value?.accounts) {
return value.accounts.map(({ _id, ...current }) => [_id, getWebdavServerName(current)]);
}

return [];
}, [value?.accounts]);

useEffect(() => fileRequest.current?.abort, []);

const handleSaveFile = ({ accountId }: { accountId: IWebdavAccount['_id'] }): void => {
setIsLoading(true);

const {
url,
attachment: { title },
} = data;

fileRequest.current = new XMLHttpRequest();
fileRequest.current.open('GET', url, true);
fileRequest.current.responseType = 'arraybuffer';
fileRequest.current.onload = async (): Promise<void> => {
const arrayBuffer = fileRequest.current?.response;
if (arrayBuffer) {
const fileData = new Uint8Array(arrayBuffer);

try {
const response = await uploadFileToWebdav(accountId, fileData, title);
if (!response.success) {
return dispatchToastMessage({ type: 'error', message: t(response.message) });
}
return dispatchToastMessage({ type: 'success', message: t('File_uploaded') });
} catch (error) {
return dispatchToastMessage({ type: 'error', message: error as Error });
} finally {
setIsLoading(false);
onClose();
}
}
};
fileRequest.current.send(null);
};

return (
<Modal is='form' onSubmit={handleSubmit(handleSaveFile)}>
<Modal.Header>
<Modal.Title>{t('Save_To_Webdav')}</Modal.Title>
<Modal.Close title={t('Close')} onClick={onClose} />
</Modal.Header>
<Modal.Content>
{isLoading && (
<Box alignItems='center' display='flex' justifyContent='center' minHeight='x32'>
<Throbber />
</Box>
)}
{!isLoading && (
<FieldGroup>
<Field>
<Field.Label>{t('Select_a_webdav_server')}</Field.Label>
<Field.Row>
<Controller
name='accountId'
control={control}
rules={{ required: true }}
render={({ field }): ReactElement => (
<Select {...field} options={accountsOptions} id={accountIdField} placeholder={t('Select_an_option')} />
)}
/>
</Field.Row>
{errors.accountId && <Field.Error>{t('Field_required')}</Field.Error>}
</Field>
</FieldGroup>
)}
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button onClick={onClose}>{t('Cancel')}</Button>
<Button primary type='submit' disabled={isLoading}>
{isLoading ? t('Please_wait') : t('Save_To_Webdav')}
</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>
);
};
export default SaveToWebdavModal;
1 change: 1 addition & 0 deletions apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -4057,6 +4057,7 @@
"Select_a_department": "Select a department",
"Select_a_room": "Select a room",
"Select_a_user": "Select a user",
"Select_a_webdav_server": "Select a WebDAV server",
"Select_an_avatar": "Select an avatar",
"Select_an_option": "Select an option",
"Select_at_least_one_user": "Select at least one user",
Expand Down
2 changes: 2 additions & 0 deletions packages/core-typings/src/IWebdavAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export interface IWebdavAccount extends IRocketChatRecord {
name: string;
}

export type IWebdavAccountIntegration = Pick<IWebdavAccount, '_id' | 'username' | 'serverURL' | 'name'>;

export type IWebdavAccountPayload = Pick<IWebdavAccount, 'serverURL' | 'username' | 'password' | 'name'>;
Loading

0 comments on commit 31360e1

Please sign in to comment.