Skip to content

Commit

Permalink
Merge branch 'feature/module-kdrive-monorepo' into 'master'
Browse files Browse the repository at this point in the history
kdrive module integration on monorepo

See merge request kchat/webapp!592
  • Loading branch information
antonbuks committed Feb 2, 2024
2 parents 1b20a40 + 685be86 commit 67f9a1a
Show file tree
Hide file tree
Showing 39 changed files with 683 additions and 35 deletions.
217 changes: 217 additions & 0 deletions webapp/channels/src/actions/kdrive_actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/ban-types */

import React from 'react';

import {Client4} from 'mattermost-redux/client';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import type {DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';

import KDriveIcon from 'components/widgets/icons/kdrive_icon';

import {ActionTypes, KDriveActionTypes} from 'utils/constants';
import {generateId, localizeMessage} from 'utils/utils';

interface IDriveSelectionOutput {
attachmentsSize: number;
attachments: Array<{
id: number;
driveId: number;
name: string;
type: string;
mimetype: string;
size: number;
url: string;
}>;
shareAttachments: Array<{
id: number;
driveId: number;
name: string;
type: string;
mimetype: string;
size: number;
url: string;
}>;
}

export function setKDriveToast(message?: string, link?: string) {
return async (dispatch: DispatchFunc) => {
if (!message) {
dispatch({type: KDriveActionTypes.TOAST, toast: null});
return;
}

dispatch({
type: KDriveActionTypes.TOAST,
toast: {
message,
props: {
link,
},
},
});

await new Promise((resolve) => setTimeout(resolve, 3000));
dispatch({type: KDriveActionTypes.TOAST, toast: null});
};
}

export function saveFileToKDrive(fileId: string, fileName: string) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
const theme = getTheme(getState());

// handle medium
const color = theme.ikType === 'dark' ? 'dark' : 'light';
const driveModule = document.querySelector('module-kdrive-component') as HTMLElement &
{ open: (mode: string, theme: string, fileName?: string) => Promise<{ driveId: number; elementId: number; name: string }> };

driveModule.open('save-to-drive', color, fileName).
then(async (data: { driveId: number; elementId: number; name: string }) => {
const res = await Client4.uploadToKdrive(fileId, data.driveId, data.elementId, data.name);

if (!('error' in res)) {
const [resDriveId, resElemId] = res.remote_id.split(':');

// TODO use env for preprod
const link = `https://kdrive.infomaniak.com/app/drive/${resDriveId}/redirect/${resElemId}`;
dispatch(setKDriveToast(localizeMessage('kdrive.uploadSuccess', 'Your file has been saved to kDrive'), link));
}
}).
catch((error: string) => console.warn(error));

return {data: true};
};
}

export function selectFileFromKdrive(
channelId: string,
rootId: string,
onUpload: Function,
uploadStartHandler: (clientIds: string[], channelId: string) => void,
stateAdd: Function,
stateRemove: Function,
handleInputChange: Function,
message: string,
caretPosition: number,
) {
return async (_: DispatchFunc, getState: GetStateFunc) => {
const theme = getTheme(getState());

// handle medium
const color = theme.ikType === 'dark' ? 'dark' : 'light';
const driveModule = document.querySelector('module-kdrive-component') as HTMLElement &
{ open: (mode: string, theme: string, limit: number/**in bytes*/) => Promise<IDriveSelectionOutput> };

driveModule.open('select-from-drive-mail', color, 104900000).
then((data: IDriveSelectionOutput) => {
console.log(data);
const reqs: Array<{
name: string;
size: number;
mime_type: string;
clientId: string;
driveId: number;
id: number;
}> = [];
const ids: string[] = [];
data.attachments.forEach((file) => {
const clientId = generateId();
ids.push(clientId);
const dummyRequest = stateAdd(clientId, file.name, file.type);
const fileInfo = {
name: file.name,
size: file.size,
mime_type: file.mimetype,
clientId,
};
dummyRequest.onProgress(fileInfo);
reqs.push({
...fileInfo,
driveId: file.driveId,
id: file.id,
});
});
uploadStartHandler(ids, channelId);
reqs.forEach(async (req) => {
try {
const {file_infos, client_ids} = await Client4.downloadFromKdrive(channelId, req.driveId, req.id, req.clientId);
onUpload(file_infos, client_ids, channelId, rootId);
stateRemove(req.clientId);
} catch (error) {
console.warn(error);
}
});
let linksConcat = '';
const messageStart = message.slice(0, caretPosition);
const messageEnd = message.slice(caretPosition);
data.shareAttachments.forEach((share, idx) => {
linksConcat += (idx > 0 ? '\n' : '') + share.url;
});
const newMessage = messageStart + linksConcat + messageEnd;
handleInputChange({target: {value: newMessage}});
}).
catch((error: string) => console.warn(error));

return {data: true};
};
}

/**
* This action is called every channel switch in {@code ChannelView} to register a kdrive upload plugin.
* The plugin needs to be reregistered every channel switch because it needs to bind the channel id to the
* plugin action. The old one needs to be manually removed otherwise it adds a duplicate.
*
* @param channelId - Current channel id.
* @returns {void}
*/
export function registerInternalKdrivePlugin() {
return async (dispatch: DispatchFunc) => {
dispatch({
type: ActionTypes.RECEIVED_PLUGIN_COMPONENT,
name: 'FileUploadMethod',
data: {
id: 'kdrive',
pluginId: 'kdrive',
text: localizeMessage('kdrive.upload', 'Upload from kDrive'),
customArgs: [
'onFileUpload',
'onUploadStart',
'addDummyRequest',
'removeDummyRequest',
'channelId',
'rootId',
'handleDriveSharelink',
'message',
'caretPosition',
],
action: (
onFileUpload: Function,
onUploadStart: (clientIds: string[], channelId: string) => void,
stateAdd: Function,
stateRemove: Function,
channelId: string,
rootId: string,
handleDriveSharelink: Function,
message: string,
caretPosition: number,
) => {
dispatch(selectFileFromKdrive(
channelId,
rootId,
onFileUpload,
onUploadStart,
stateAdd,
stateRemove,
handleDriveSharelink,
message,
caretPosition,
));
},
icon: <KDriveIcon/>,
},
});
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
isThreadView={this.props.isThreadView}
isSchedulable={true}
handleSchedulePost={this.handleSchedulePost}
caretPosition={this.state.caretPosition}
/>
</form>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1716,6 +1716,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
labels={priorityLabels}
isSchedulable={true}
handleSchedulePost={this.handleSchedulePost}
caretPosition={this.state.caretPosition}
additionalControls={[
this.props.isPostPriorityEnabled && (
<React.Fragment key='PostPriorityPicker'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Props = {
location: string;
currentUserId: string;
message: string;
caretPosition: number;
showEmojiPicker: boolean;
uploadsProgressPercent: { [clientID: string]: FilePreviewInfo };
currentChannel?: Channel;
Expand Down Expand Up @@ -109,6 +110,7 @@ type Props = {
const AdvanceTextEditor = ({
location,
message,
caretPosition,
showEmojiPicker,
uploadsProgressPercent,
currentChannel,
Expand Down Expand Up @@ -230,6 +232,11 @@ const AdvanceTextEditor = ({
rootId={postId}
channelId={channelId}
postType={postType}

// For drive sharelinks
message={message}
caretPosition={caretPosition}
handleDriveSharelink={handleChange}
/>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,7 @@ class DraftEditor extends React.PureComponent<Props, State> {
handleChange={this.handleChange}
prefillMessage={this.prefillMessage}
removePreview={this.removePreview}
caretPosition={this.state.caretPosition}
additionalControls={[
this.props.isPostPriorityEnabled && (
<React.Fragment key='PostPriorityPicker'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ export default function FileAttachment(props: Props) {
compactDisplay={compactDisplay}
canDownload={props.canDownloadFiles}
handleImageClick={onAttachmentClick}
handleKDriveSave={props.actions.saveFileToKDrive}
iconClass={'post-image__download'}
>
<i className='icon icon-download-outline'/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import ExternalLink from 'components/external_link';
import OverlayTrigger from 'components/overlay_trigger';
import Tooltip from 'components/tooltip';
import AttachmentIcon from 'components/widgets/icons/attachment_icon';
import KDriveIcon from 'components/widgets/icons/kdrive_icon';

import {trimFilename} from 'utils/file_utils';
import {isServerVersionGreaterThanOrEqualTo} from 'utils/server_version';
import {getDesktopVersion, isDesktopApp} from 'utils/user_agent';
import {localizeMessage} from 'utils/utils';

type Props = {
Expand All @@ -27,6 +30,11 @@ type Props = {
*/
handleImageClick?: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;

/*
* Handler for saving image to kdrive.
*/
handleKDriveSave?: (fileId: string, fileName: string) => void;

/*
* Display in compact format
*/
Expand Down Expand Up @@ -56,6 +64,7 @@ export default class FilenameOverlay extends React.PureComponent<Props> {
compactDisplay,
fileInfo,
handleImageClick,
handleKDriveSave,
iconClass,
} = this.props;

Expand Down Expand Up @@ -94,14 +103,34 @@ export default class FilenameOverlay extends React.PureComponent<Props> {
</Tooltip>
}
>
<ExternalLink
href={getFileDownloadUrl(fileInfo.id)}
aria-label={localizeMessage('view_image_popover.download', 'Download').toLowerCase()}
download={fileName}
location='filename_overlay'
>
{children || trimmedFilename}
</ExternalLink>
<>
{(!isDesktopApp() || isServerVersionGreaterThanOrEqualTo(getDesktopVersion(), '2.4.0')) && (
<OverlayTrigger
delayShow={200}
placement='top'
overlay={
<Tooltip id='file-name__tooltip'>
{localizeMessage('kdrive.save', 'Save file to kDrive')}
</Tooltip>
}
>
<span className='file-kdrive__icon--wrapper'>
<KDriveIcon
onClick={() => handleKDriveSave?.(fileInfo.id, fileName)}
className='icon file-kdrive__icon'
/>
</span>
</OverlayTrigger>
)}
<ExternalLink
href={getFileDownloadUrl(fileInfo.id)}
aria-label={localizeMessage('view_image_popover.download', 'Download').toLowerCase()}
download={fileName}
location='filename_overlay'
>
{children || trimmedFilename}
</ExternalLink>
</>
</OverlayTrigger>
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions webapp/channels/src/components/file_attachment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {bindActionCreators} from 'redux';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import type {GenericAction} from 'mattermost-redux/types/actions';

import {saveFileToKDrive} from 'actions/kdrive_actions';
import {openModal} from 'actions/views/modals';
import {getFilesDropdownPluginMenuItems} from 'selectors/plugins';

Expand All @@ -33,6 +34,7 @@ function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
return {
actions: bindActionCreators({
openModal,
saveFileToKDrive,
}, dispatch),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default class FileProgressPreview extends React.PureComponent<Props> {
id='admin.plugin.uploading'
defaultMessage='Uploading...'
/>
<span>{percentTxt}</span>
{fileInfo.percent && <span>{percentTxt}</span>}
</React.Fragment>
)}
</span>
Expand Down
Loading

0 comments on commit 67f9a1a

Please sign in to comment.