Presentation Uploader to 2.0
This commit is contained in:
parent
a2268494e6
commit
c0307e36a6
@ -2,8 +2,11 @@ import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import handlePresentationChange from './handlers/presentationChange';
|
||||
import handlePresentationInfoReply from './handlers/presentationInfoReply';
|
||||
import handlePresentationRemove from './handlers/presentationRemove';
|
||||
import handlePresentationCurrentChange from './handlers/presentationCurrentChange';
|
||||
|
||||
|
||||
RedisPubSub.on('SyncGetPresentationInfoRespMsg', handlePresentationInfoReply);
|
||||
RedisPubSub.on('NewPresentationEvtMsg', handlePresentationChange);
|
||||
RedisPubSub.on('PresentationConversionCompletedEvtMsg', handlePresentationChange);
|
||||
RedisPubSub.on('RemovePresentationEvtMsg', handlePresentationRemove);
|
||||
RedisPubSub.on('SetCurrentPresentationEvtMsg', handlePresentationCurrentChange);
|
||||
|
@ -7,7 +7,7 @@ import addPresentation from '../modifiers/addPresentation';
|
||||
const clearCurrentPresentation = (meetingId, presentationId) => {
|
||||
const selector = {
|
||||
meetingId,
|
||||
presentationId: { $ne: presentationId },
|
||||
id: { $ne: presentationId },
|
||||
current: true,
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Presentations from '/imports/api/presentations';
|
||||
|
||||
export default function handlePresentationConversionDone({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const presentationId = payload.presentation.id;
|
||||
const status = payload.message_key;
|
||||
export default function handlePresentationConversionDone({ body }, meetingId) {
|
||||
const presentationId = body.presentationId;
|
||||
const status = body.messageKey;
|
||||
|
||||
check(meetingId, String);
|
||||
check(presentationId, String);
|
||||
@ -18,7 +17,7 @@ export default function handlePresentationConversionDone({ payload }) {
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
'presentation.id': presentationId,
|
||||
id: presentationId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
|
@ -18,10 +18,9 @@ const GENERATED_SLIDE_KEY = 'GENERATED_SLIDE';
|
||||
// const GENERATED_SVGIMAGES_KEY = 'GENERATED_SVGIMAGES';
|
||||
// const CONVERSION_COMPLETED_KEY = 'CONVERSION_COMPLETED';
|
||||
|
||||
export default function handlePresentationConversionUpdate({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const presentationId = payload.presentation_id;
|
||||
const status = payload.message_key;
|
||||
export default function handlePresentationConversionUpdate({ body }, meetingId) {
|
||||
const presentationId = body.presentationId;
|
||||
const status = body.messageKey;
|
||||
|
||||
check(meetingId, String);
|
||||
check(presentationId, String);
|
||||
@ -36,7 +35,7 @@ export default function handlePresentationConversionUpdate({ payload }) {
|
||||
switch (status) {
|
||||
case SUPPORTED_DOCUMENT_KEY:
|
||||
statusModifier['presentation.id'] = presentationId;
|
||||
statusModifier['presentation.name'] = payload.presentation_name;
|
||||
statusModifier['presentation.name'] = body.presentation_name;
|
||||
break;
|
||||
|
||||
case UNSUPPORTED_DOCUMENT_KEY:
|
||||
@ -45,13 +44,13 @@ export default function handlePresentationConversionUpdate({ payload }) {
|
||||
case PAGE_COUNT_FAILED_KEY:
|
||||
case PAGE_COUNT_EXCEEDED_KEY:
|
||||
statusModifier['presentation.id'] = presentationId;
|
||||
statusModifier['presentation.name'] = payload.presentation_name;
|
||||
statusModifier['presentation.name'] = body.presentation_name;
|
||||
statusModifier['conversion.error'] = true;
|
||||
break;
|
||||
|
||||
case GENERATED_SLIDE_KEY:
|
||||
statusModifier['conversion.pages_completed'] = payload.pages_completed;
|
||||
statusModifier['conversion.num_pages'] = payload.num_pages;
|
||||
statusModifier['conversion.pagesCompleted'] = body.pagesCompleted;
|
||||
statusModifier['conversion.numPages'] = body.numPages;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -61,7 +60,7 @@ export default function handlePresentationConversionUpdate({ payload }) {
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
'presentation.id': presentationId,
|
||||
id: presentationId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { check } from 'meteor/check';
|
||||
import changeCurrentPresentation from '../modifiers/changeCurrentPresentation';
|
||||
|
||||
export default function presentationCurrentChange({ body }, meetingId) {
|
||||
const { presentationId } = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(presentationId, String);
|
||||
|
||||
return changeCurrentPresentation(meetingId, presentationId);
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import mapToAcl from '/imports/startup/mapToAcl';
|
||||
import removePresentation from './methods/removePresentation';
|
||||
import sharePresentation from './methods/sharePresentation';
|
||||
|
||||
Meteor.methods({
|
||||
});
|
||||
Meteor.methods(mapToAcl(['methods.removePresentation', 'methods.sharePresentation'], {
|
||||
removePresentation,
|
||||
sharePresentation,
|
||||
}));
|
||||
|
@ -1,16 +1,11 @@
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import { check } from 'meteor/check';
|
||||
import Presentations from '/imports/api/presentations';
|
||||
import Presentations from '/imports/api/2.0/presentations';
|
||||
|
||||
export default function removePresentation(credentials, presentationId) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.presentation;
|
||||
const EVENT_NAME = 'remove_presentation';
|
||||
|
||||
if (!isAllowedTo('removePresentation', credentials)) {
|
||||
throw new Meteor.Error('not-allowed', 'You are not allowed to removePresentation');
|
||||
}
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'RemovePresentationPubMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
@ -18,19 +13,28 @@ export default function removePresentation(credentials, presentationId) {
|
||||
check(requesterUserId, String);
|
||||
check(presentationId, String);
|
||||
|
||||
const currentPresentation = Presentations.findOne({
|
||||
const presenationToDelete = Presentations.findOne({
|
||||
meetingId,
|
||||
'presentation.id': presentationId,
|
||||
id: presentationId,
|
||||
});
|
||||
|
||||
if (currentPresentation.name === 'default.pdf') {
|
||||
if (presenationToDelete.name === 'default.pdf') {
|
||||
throw new Meteor.Error('not-allowed', 'You are not allowed to remove the default slide');
|
||||
}
|
||||
|
||||
if (presenationToDelete.current) {
|
||||
throw new Meteor.Error('not-allowed', 'You are not allowed to remove the current presentation');
|
||||
}
|
||||
|
||||
const payload = {
|
||||
meeting_id: meetingId,
|
||||
presentation_id: presentationId,
|
||||
presentationId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
|
||||
const header = {
|
||||
meetingId,
|
||||
name: EVENT_NAME,
|
||||
userId: requesterUserId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, meetingId, payload, header);
|
||||
}
|
||||
|
@ -1,16 +1,11 @@
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import { check } from 'meteor/check';
|
||||
import Presentations from '/imports/api/presentations';
|
||||
import Presentations from '/imports/api/2.0/presentations';
|
||||
|
||||
export default function sharePresentation(credentials, presentationId, shouldShare = true) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.presentation;
|
||||
const EVENT_NAME = 'share_presentation';
|
||||
|
||||
if (!isAllowedTo('sharePresentation', credentials)) {
|
||||
throw new Meteor.Error('not-allowed', 'You are not allowed to sharePresentation');
|
||||
}
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'SetCurrentPresentationPubMsg';
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
@ -21,19 +16,23 @@ export default function sharePresentation(credentials, presentationId, shouldSha
|
||||
|
||||
const currentPresentation = Presentations.findOne({
|
||||
meetingId,
|
||||
'presentation.id': presentationId,
|
||||
'presentation.current': true,
|
||||
id: presentationId,
|
||||
current: true,
|
||||
});
|
||||
|
||||
if (currentPresentation && currentPresentation.presentation.id === presentationId) {
|
||||
if (currentPresentation && currentPresentation.id === presentationId) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const payload = {
|
||||
meeting_id: meetingId,
|
||||
presentation_id: presentationId,
|
||||
share: shouldShare,
|
||||
presentationId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
|
||||
const header = {
|
||||
meetingId,
|
||||
name: EVENT_NAME,
|
||||
userId: requesterUserId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, meetingId, payload, header);
|
||||
}
|
||||
|
@ -43,7 +43,11 @@ export default function addPresentation(meetingId, presentation) {
|
||||
|
||||
const modifier = {
|
||||
$set: Object.assign(
|
||||
{ meetingId },
|
||||
{
|
||||
meetingId,
|
||||
'conversion.done': true,
|
||||
'conversion.error': false,
|
||||
},
|
||||
flat(presentation, { safe: true }),
|
||||
),
|
||||
};
|
||||
|
@ -4,3 +4,5 @@ import handleSlideChange from './handlers/slideChange';
|
||||
|
||||
RedisPubSub.on('ResizeAndMovePageEvtMsg', handleSlideResize);
|
||||
RedisPubSub.on('SetCurrentPageEvtMsg', handleSlideChange);
|
||||
RedisPubSub.on('PresentationConversionUpdateEvtMsg', (...args) => console.error(args));
|
||||
RedisPubSub.on('PresentationPageGeneratedEvtMsg', (...args) => console.error(args));
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedDate } from 'react-intl';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import update from 'immutability-helper';
|
||||
@ -12,6 +13,25 @@ import styles from './styles.scss';
|
||||
|
||||
const DEFAULT_FILENAME = 'default.pdf';
|
||||
|
||||
const propTypes = {
|
||||
defaultFileName: PropTypes.string.isRequired,
|
||||
fileSizeMin: PropTypes.number.isRequired,
|
||||
fileSizeMax: PropTypes.number.isRequired,
|
||||
handleSave: PropTypes.func.isRequired,
|
||||
fileValidMimeTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
presentations: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
filename: PropTypes.string.isRequired,
|
||||
isCurrent: PropTypes.bool.isRequired,
|
||||
conversion: PropTypes.object,
|
||||
upload: PropTypes.object,
|
||||
})).isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
defaultFileName: 'default.pdf',
|
||||
};
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
title: {
|
||||
id: 'app.presentationUploder.title',
|
||||
@ -51,9 +71,13 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploder.fileToUpload',
|
||||
defaultMessage: 'To be uploaded...',
|
||||
},
|
||||
genericError: {
|
||||
id: 'app.presentationUploder.conversion.genericError',
|
||||
defaultMessage: 'Ops, something went wrong.',
|
||||
uploadProcess: {
|
||||
id: 'app.presentationUploder.upload.progress',
|
||||
defaultMessage: 'Uploading ({progress}%)',
|
||||
},
|
||||
413: {
|
||||
id: 'app.presentationUploder.upload.413',
|
||||
defaultMessage: 'File is too large.',
|
||||
},
|
||||
conversionProcessingSlides: {
|
||||
id: 'app.presentationUploder.conversion.conversionProcessingSlides',
|
||||
@ -73,7 +97,22 @@ const intlMessages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const isProcessingOrUploading = item => item && (!item.isProcessed || !item.isUploaded);
|
||||
const isProcessingOrUploading = item => item && (!item.conversion.done || !item.upload.done);
|
||||
|
||||
function updateFileUploadState(id, state) {
|
||||
this.setState(({ presentations }) => {
|
||||
const fileIndex = presentations.findIndex(f => f.id === id);
|
||||
return {
|
||||
presentations: update(presentations, {
|
||||
[fileIndex]: { $apply: file =>
|
||||
update(file, {
|
||||
upload: { $apply: upload => update(upload, { $merge: state }) },
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class PresentationUploader extends Component {
|
||||
constructor(props) {
|
||||
@ -131,21 +170,24 @@ class PresentationUploader extends Component {
|
||||
id: file.name,
|
||||
file,
|
||||
filename: file.name,
|
||||
uploadedAt: new Date(),
|
||||
isCurrent: true,
|
||||
isUploaded: false,
|
||||
isProcessed: false,
|
||||
isCurrent: false,
|
||||
conversion: { done: false, error: false },
|
||||
upload: { done: false, error: false, progress: 0 },
|
||||
onProgress: (event) => {
|
||||
if (!event.lengthComputable) return;
|
||||
|
||||
updateFileUploadState.call(this, file.name, {
|
||||
progress: (event.loaded / event.total) * 100,
|
||||
done: event.loaded === event.total,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
updateFileUploadState.call(this, file.name, { error });
|
||||
},
|
||||
}));
|
||||
|
||||
this.setState(({ presentations }) => ({
|
||||
presentations: presentations
|
||||
.map((_) => {
|
||||
const p = _;
|
||||
p.isCurrent = false;
|
||||
return p;
|
||||
})
|
||||
.concat(presentationsToUpload),
|
||||
presentations: presentations.concat(presentationsToUpload),
|
||||
}));
|
||||
}
|
||||
|
||||
@ -198,7 +240,6 @@ class PresentationUploader extends Component {
|
||||
const { presentations } = this.state;
|
||||
|
||||
const presentationsSorted = presentations
|
||||
.sort((a, b) => a.uploadedAt.getTime() - b.uploadedAt.getTime())
|
||||
.sort((a, b) => b.filename === DEFAULT_FILENAME);
|
||||
|
||||
return (
|
||||
@ -215,18 +256,31 @@ class PresentationUploader extends Component {
|
||||
renderPresentationItemStatus(item) {
|
||||
const { intl } = this.props;
|
||||
|
||||
if (!item.isUploaded) { return intl.formatMessage(intlMessages.fileToUpload); }
|
||||
if (!item.upload.done && item.upload.progress === 0) {
|
||||
return intl.formatMessage(intlMessages.fileToUpload);
|
||||
}
|
||||
|
||||
if (!item.isProcessed && item.conversion.error) {
|
||||
if (!item.upload.done && !item.upload.error) {
|
||||
return intl.formatMessage(intlMessages.uploadProcess, {
|
||||
progress: item.upload.progress,
|
||||
});
|
||||
}
|
||||
|
||||
if (item.upload.done && item.upload.error) {
|
||||
const errorMessage = intlMessages[item.upload.error.code] || intlMessages.genericError;
|
||||
return intl.formatMessage(errorMessage);
|
||||
}
|
||||
|
||||
if (!item.conversion.done && item.conversion.error) {
|
||||
const errorMessage = intlMessages[status] || intlMessages.genericError;
|
||||
return intl.formatMessage(errorMessage);
|
||||
}
|
||||
|
||||
if (!item.isProcessed && !item.conversion.error) {
|
||||
if (item.conversion.pages_completed < item.conversion.num_pages) {
|
||||
if (!item.conversion.done && !item.conversion.error) {
|
||||
if (item.conversion.pagesCompleted < item.conversion.numPages) {
|
||||
return intl.formatMessage(intlMessages.conversionProcessingSlides, {
|
||||
current: item.conversion.pages_completed,
|
||||
total: item.conversion.num_pages,
|
||||
current: item.conversion.pagesCompleted,
|
||||
total: item.conversion.numPages,
|
||||
});
|
||||
}
|
||||
|
||||
@ -235,33 +289,22 @@ class PresentationUploader extends Component {
|
||||
return intl.formatMessage(conversionStatusMessage);
|
||||
}
|
||||
|
||||
return (
|
||||
<time dateTime={item.uploadedAt}>
|
||||
<FormattedDate
|
||||
value={item.uploadedAt}
|
||||
day="2-digit"
|
||||
month="2-digit"
|
||||
year="numeric"
|
||||
hour="2-digit"
|
||||
minute="2-digit"
|
||||
/>
|
||||
</time>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
renderPresentationItem(item) {
|
||||
const { disableActions, presentations } = this.state;
|
||||
const { disableActions } = this.state;
|
||||
|
||||
const itemClassName = {};
|
||||
|
||||
itemClassName[styles.tableItemNew] = item.id === item.filename;
|
||||
itemClassName[styles.tableItemUploading] = false;
|
||||
itemClassName[styles.tableItemProcessing] = !item.isProcessed && item.isUploaded;
|
||||
itemClassName[styles.tableItemError] = item.conversion.error || false;
|
||||
itemClassName[styles.tableItemUploading] = !item.upload.done;
|
||||
itemClassName[styles.tableItemProcessing] = !item.conversion.done;
|
||||
itemClassName[styles.tableItemError] = item.conversion.error || item.upload.error;
|
||||
itemClassName[styles.tableItemAnimated] =
|
||||
!item.conversion.done && (!item.upload.done && item.upload.progress > 0);
|
||||
|
||||
const hideRemove = (item.isCurrent && item.isUploaded) || item.filename === DEFAULT_FILENAME;
|
||||
const hasSomeFileNotUploaded = presentations.some(_ => !_.isUploaded);
|
||||
const disableCheck = hasSomeFileNotUploaded;
|
||||
const hideRemove = (item.isCurrent && item.upload.done) || item.filename === DEFAULT_FILENAME;
|
||||
|
||||
return (
|
||||
<tr
|
||||
@ -278,24 +321,23 @@ class PresentationUploader extends Component {
|
||||
{this.renderPresentationItemStatus(item)}
|
||||
</td>
|
||||
<td className={styles.tableItemActions}>
|
||||
{disableActions ? null : (<span>
|
||||
<Checkbox
|
||||
disabled={disableCheck}
|
||||
ariaLabel={'Set as current presentation'}
|
||||
className={styles.itemAction}
|
||||
checked={item.isCurrent}
|
||||
onChange={() => this.handleCurrentChange(item)}
|
||||
/>
|
||||
{ hideRemove ? null : (
|
||||
<ButtonBase
|
||||
className={cx(styles.itemAction, styles.itemActionRemove)}
|
||||
label={'Remove presentation'}
|
||||
onClick={() => this.handleRemove(item)}
|
||||
>
|
||||
<Icon iconName={'delete'} />
|
||||
</ButtonBase>
|
||||
)}
|
||||
</span>)}
|
||||
<Checkbox
|
||||
disabled={disableActions}
|
||||
ariaLabel={'Set as current presentation'}
|
||||
className={styles.itemAction}
|
||||
checked={item.isCurrent}
|
||||
onChange={() => this.handleCurrentChange(item)}
|
||||
/>
|
||||
{ hideRemove ? null : (
|
||||
<ButtonBase
|
||||
disabled={disableActions}
|
||||
className={cx(styles.itemAction, styles.itemActionRemove)}
|
||||
label={'Remove presentation'}
|
||||
onClick={() => this.handleRemove(item)}
|
||||
>
|
||||
<Icon iconName={'delete'} />
|
||||
</ButtonBase>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
@ -311,14 +353,13 @@ class PresentationUploader extends Component {
|
||||
|
||||
const { disableActions } = this.state;
|
||||
|
||||
// TODO: Change the multiple prop when the endpoint supports multiple files
|
||||
const hasSomeFileNotUploaded = this.state.presentations.some(_ => !_.isUploaded);
|
||||
const hasSomeFileNotUploaded = this.state.presentations.some(_ => !_.upload.done);
|
||||
|
||||
if (hasSomeFileNotUploaded || disableActions) return null;
|
||||
|
||||
return (
|
||||
<Dropzone
|
||||
multiple={false}
|
||||
multiple
|
||||
className={styles.dropzone}
|
||||
activeClassName={styles.dropzoneActive}
|
||||
rejectClassName={styles.dropzoneReject}
|
||||
|
@ -1,43 +1,60 @@
|
||||
import Presentations from '/imports/api/presentations';
|
||||
import Presentations from '/imports/api/2.0/presentations';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
|
||||
import { call } from '/imports/ui/services/api';
|
||||
|
||||
const futch = (url, opts = {}, onProgress) => new Promise((res, rej) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open(opts.method || 'get', url);
|
||||
|
||||
Object.keys(opts.headers || {})
|
||||
.forEach(k => xhr.setRequestHeader(k, opts.headers[k]));
|
||||
|
||||
xhr.onload = (e) => {
|
||||
if (e.target.status !== 200) {
|
||||
return rej({ code: e.target.status, message: e.target.statusText });
|
||||
}
|
||||
|
||||
return res(e.target.responseText);
|
||||
};
|
||||
xhr.onerror = rej;
|
||||
if (xhr.upload && onProgress) {
|
||||
xhr.upload.addEventListener('progress', onProgress, false);
|
||||
}
|
||||
xhr.send(opts.body);
|
||||
});
|
||||
|
||||
const getPresentations = () =>
|
||||
Presentations
|
||||
.find()
|
||||
.fetch()
|
||||
.map(p => ({
|
||||
_id: p._id,
|
||||
id: p.presentation.id,
|
||||
filename: p.presentation.name,
|
||||
uploadedAt: p.upload ? p.upload.date : new Date(),
|
||||
isCurrent: p.presentation.current,
|
||||
isUploaded: true,
|
||||
isProcessed: p.conversion.done,
|
||||
conversion: p.conversion,
|
||||
.map(presentation => ({
|
||||
id: presentation.id,
|
||||
filename: presentation.name,
|
||||
isCurrent: presentation.current,
|
||||
upload: { done: true, error: false },
|
||||
conversion: presentation.conversion || { done: true, error: false },
|
||||
}));
|
||||
|
||||
const uploadPresentation = (file, meetingID, endpoint) => {
|
||||
const uploadPresentation = (file, meetingID, endpoint, onError, onProgress) => {
|
||||
const data = new FormData();
|
||||
data.append('Filename', file.filename);
|
||||
data.append('presentation_name', file.filename);
|
||||
data.append('fileUpload', file);
|
||||
data.append('conference', meetingID);
|
||||
data.append('room', meetingID);
|
||||
|
||||
/* TODO: Should we do the request on the html5 server instead of the client? */
|
||||
/* TODO: Upload progress */
|
||||
return fetch(endpoint, {
|
||||
const opts = {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
});
|
||||
};
|
||||
|
||||
return futch(endpoint, opts, onProgress).catch(onError);
|
||||
};
|
||||
|
||||
const uploadPresentations = (presentationsToUpload, meetingID, uploadEndpoint) =>
|
||||
Promise.all(
|
||||
presentationsToUpload
|
||||
.map(p => uploadPresentation(p.file, meetingID, uploadEndpoint)),
|
||||
.map(p => uploadPresentation(p.file, meetingID, uploadEndpoint, p.onError, p.onProgress)),
|
||||
);
|
||||
|
||||
const removePresentation = presentationID => call('removePresentation', presentationID);
|
||||
|
@ -108,8 +108,7 @@ $item-height: 1rem;
|
||||
background-color: transparentize($color-danger, .75);
|
||||
}
|
||||
|
||||
.tableItemUploading,
|
||||
.tableItemProcessing {
|
||||
.tableItemAnimated {
|
||||
background-image: linear-gradient(45deg,
|
||||
rgba(255, 255, 255, .15) 25%,
|
||||
transparent 25%,
|
||||
|
@ -30,3 +30,5 @@ acl:
|
||||
methods:
|
||||
- 'assignPresenter'
|
||||
- 'switchSlide'
|
||||
- 'removePresentation'
|
||||
- 'sharePresentation'
|
||||
|
Loading…
Reference in New Issue
Block a user