Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Implement redesigned upload confirmation screens
Browse files Browse the repository at this point in the history
Also fairly significant refactor of the uploading code: there are
a number of different ways of triggerring a file upload and each
went through a different code path (the media config size limit
worked on one of those paths). Basically take a lot of code out
of the views and put it into ContentMessages.

Sorry about the size of this patch.

element-hq/element-web#7565
  • Loading branch information
dbkr committed Apr 1, 2019
1 parent f3e8722 commit 5b2cee2
Show file tree
Hide file tree
Showing 11 changed files with 452 additions and 202 deletions.
1 change: 0 additions & 1 deletion .eslintignore.errorfiles
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ src/components/views/settings/ChangePassword.js
src/components/views/settings/DevicesPanel.js
src/components/views/settings/IntegrationsManager.js
src/components/views/settings/Notifications.js
src/ContentMessages.js
src/GroupAddressPicker.js
src/HtmlUtils.js
src/ImageUtils.js
Expand Down
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
@import "./views/dialogs/_SettingsDialog.scss";
@import "./views/dialogs/_ShareDialog.scss";
@import "./views/dialogs/_UnknownDeviceDialog.scss";
@import "./views/dialogs/_UploadConfirmDialog.scss";
@import "./views/dialogs/_UserSettingsDialog.scss";
@import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss";
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
Expand Down
33 changes: 33 additions & 0 deletions res/css/views/dialogs/_UploadConfirmDialog.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright 2019 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_UploadConfirmDialog_fileIcon {
margin-right: 5px;
}

.mx_UploadConfirmDialog_previewOuter {
text-align: center;
}

.mx_UploadConfirmDialog_previewInner {
display: inline-block;
text-align: left;
}

.mx_UploadConfirmDialog_imagePreview {
max-height: 300px;
max-width: 100%;
}
164 changes: 142 additions & 22 deletions src/ContentMessages.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -17,17 +18,18 @@ limitations under the License.
'use strict';

import Promise from 'bluebird';
const extend = require('./extend');
const dis = require('./dispatcher');
const MatrixClientPeg = require('./MatrixClientPeg');
const sdk = require('./index');
import extend from './extend';
import dis from './dispatcher';
import MatrixClientPeg from './MatrixClientPeg';
import sdk from './index';
import { _t } from './languageHandler';
const Modal = require('./Modal');
import Modal from './Modal';
import RoomViewStore from './stores/RoomViewStore';

const encrypt = require("browser-encrypt-attachment");
import encrypt from "browser-encrypt-attachment";

// Polyfill for Canvas.toBlob API using Canvas.toDataURL
require("blueimp-canvas-to-blob");
import "blueimp-canvas-to-blob";

const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
Expand Down Expand Up @@ -91,7 +93,7 @@ function createThumbnail(element, inputWidth, inputHeight, mimeType) {
/**
* Load a file into a newly created image element.
*
* @param {File} file The file to load in an image element.
* @param {File} imageFile The file to load in an image element.
* @return {Promise} A promise that resolves with the html image element.
*/
function loadImageElement(imageFile) {
Expand Down Expand Up @@ -119,7 +121,7 @@ function loadImageElement(imageFile) {
*
* @param {MatrixClient} matrixClient A matrixClient to upload the thumbnail with.
* @param {String} roomId The ID of the room the image will be uploaded in.
* @param {File} The image to read and thumbnail.
* @param {File} imageFile The image to read and thumbnail.
* @return {Promise} A promise that resolves with the attachment info.
*/
function infoForImageFile(matrixClient, roomId, imageFile) {
Expand All @@ -144,7 +146,7 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
/**
* Load a file into a newly created video element.
*
* @param {File} file The file to load in an video element.
* @param {File} videoFile The file to load in an video element.
* @return {Promise} A promise that resolves with the video image element.
*/
function loadVideoElement(videoFile) {
Expand Down Expand Up @@ -179,7 +181,7 @@ function loadVideoElement(videoFile) {
*
* @param {MatrixClient} matrixClient A matrixClient to upload the thumbnail with.
* @param {String} roomId The ID of the room the video will be uploaded to.
* @param {File} The video to read and thumbnail.
* @param {File} videoFile The video to read and thumbnail.
* @return {Promise} A promise that resolves with the attachment info.
*/
function infoForVideoFile(matrixClient, roomId, videoFile) {
Expand All @@ -200,6 +202,7 @@ function infoForVideoFile(matrixClient, roomId, videoFile) {

/**
* Read the file as an ArrayBuffer.
* @param {File} file The file to read
* @return {Promise} A promise that resolves with an ArrayBuffer when the file
* is read.
*/
Expand Down Expand Up @@ -269,11 +272,43 @@ function uploadFile(matrixClient, roomId, file, progressHandler) {
}
}


class ContentMessages {
export default class ContentMessages {
constructor() {
this.inprogress = [];
this.nextId = 0;
this._mediaConfig = null;
}

static sharedInstance() {
if (global.mx_ContentMessages === undefined) {
global.mx_ContentMessages = new ContentMessages();
}
return global.mx_ContentMessages;
}

_isFileSizeAcceptable(file) {
if (this._mediaConfig !== null &&
this._mediaConfig["m.upload.size"] !== undefined &&
file.size > this._mediaConfig["m.upload.size"]) {
return false;
}
return true;
}

_ensureMediaConfigFetched() {
if (this._mediaConfig !== null) return;

console.log("[Media Config] Fetching");
return MatrixClientPeg.get().getMediaConfig().then((config) => {
console.log("[Media Config] Fetched config:", config);
return config;
}).catch(() => {
// Media repo can't or won't report limits, so provide an empty object (no limits).
console.log("[Media Config] Could not fetch config, so not limiting uploads.");
return {};
}).then((config) => {
this._mediaConfig = config;
});
}

sendStickerContentToRoom(url, roomId, info, text, matrixClient) {
Expand All @@ -283,7 +318,88 @@ class ContentMessages {
});
}

sendContentToRoom(file, roomId, matrixClient) {
getUploadLimit() {
if (this._mediaConfig !== null && this._mediaConfig["m.upload.size"] !== undefined) {
return this._mediaConfig["m.upload.size"];
}
}

async sendContentListToRoom(files, roomId, matrixClient) {
if (matrixClient.isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
}

const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
if (isQuoting) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const shouldUpload = await new Promise((resolve) => {
Modal.createTrackedDialog('Upload Reply Warning', '', QuestionDialog, {
title: _t('Replying With Files'),
description: (
<div>{_t(
'At this time it is not possible to reply with a file ' +
'so this will be sent without being a reply.',
)}</div>
),
hasCancelButton: true,
button: _t("Continue"),
onFinished: (shouldUpload) => {
resolve(shouldUpload);
},
});
});
if (!shouldUpload) return;
}

await this._ensureMediaConfigFetched();

const tooBigFiles = [];
const okFiles = [];

for (let i = 0; i < files.length; ++i) {
if (this._isFileSizeAcceptable(files[i])) {
okFiles.push(files[i]);
} else {
tooBigFiles.push(files[i]);
}
}

if (tooBigFiles.length > 0) {
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
const uploadFailureDialogPromise = new Promise((resolve) => {
Modal.createTrackedDialog('Upload Failure', '', UploadFailureDialog, {
badFiles: tooBigFiles,
totalFiles: files.length,
contentMessages: this,
onFinished: (shouldContinue) => {
resolve(shouldContinue);
},
});
});
const shouldContinue = await uploadFailureDialogPromise;
if (!shouldContinue) return;
}

const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i];
const shouldContinue = await new Promise((resolve) => {
Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
file,
currentIndex: i,
totalFiles: okFiles.length,
onFinished: (shouldContinue) => {
resolve(shouldContinue);
},
});
});
if (!shouldContinue) break;
this._sendContentToRoom(file, roomId, matrixClient);
}
}

_sendContentToRoom(file, roomId, matrixClient) {
const content = {
body: file.name || 'Attachment',
info: {
Expand Down Expand Up @@ -357,9 +473,12 @@ class ContentMessages {
}, function(err) {
error = err;
if (!upload.canceled) {
let desc = _t('The file \'%(fileName)s\' failed to upload', {fileName: upload.fileName}) + '.';
let desc = _t("The file '%(fileName)s' failed to upload.", {fileName: upload.fileName});
if (err.http_status == 413) {
desc = _t('The file \'%(fileName)s\' exceeds this homeserver\'s size limit for uploads', {fileName: upload.fileName});
desc = _t(
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
{fileName: upload.fileName},
);
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
Expand All @@ -377,9 +496,16 @@ class ContentMessages {
}
}
if (error) {
// 413: File was too big or upset the server in some way:
// clear the media size limit so we fetch it again next time
// we try to upload
if (error && error.http_status === 413) {
this._mediaConfig = null;
}
dis.dispatch({action: 'upload_failed', upload, error});
} else {
dis.dispatch({action: 'upload_finished', upload});
dis.dispatch({action: 'message_sent'});
}
});
}
Expand All @@ -404,9 +530,3 @@ class ContentMessages {
}
}
}

if (global.mx_ContentMessage === undefined) {
global.mx_ContentMessage = new ContentMessages();
}

module.exports = global.mx_ContentMessage;
Loading

0 comments on commit 5b2cee2

Please sign in to comment.