Add presenter upload capabilities

This commit is contained in:
Oswaldo Acauan 2017-05-03 17:51:17 -03:00
parent ff5d3d1211
commit 1dc3bccb0d
10 changed files with 102 additions and 60 deletions

View File

@ -29,7 +29,7 @@ export default function removePresentation(credentials, presentationId) {
}
let payload = {
meeting_id: currentPoll.meetingId,
meeting_id: meetingId,
presentation_id: presentationId,
};

View File

@ -19,7 +19,7 @@ export default function sharePresentation(credentials, presentationId, shouldSha
check(shouldShare, Boolean);
let payload = {
meeting_id: currentPoll.meetingId,
meeting_id: meetingId,
presentation_id: presentationId,
share: shouldShare,
};

View File

@ -1,5 +1,6 @@
import React, { Component, PropTypes } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/modal/service';
import Button from '/imports/ui/components/button/component';
import Dropdown from '/imports/ui/components/dropdown/component';
@ -8,6 +9,8 @@ import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import PresentationUploderContainer from '/imports/ui/components/presentation/presentation-uploader/container';
const intlMessages = defineMessages({
actionsLabel: {
id: 'app.actionsBar.actionsDropdown.actionsLabel',
@ -39,22 +42,20 @@ const intlMessages = defineMessages({
},
});
const presentation = () => {console.log('Should show the uploader component');};
const polling = () => {console.log('Should initiate a polling');};
const shareScreen = () => {console.log('Should start screen sharing');};
class ActionsDropdown extends Component {
constructor(props) {
super(props);
this.handlePresentationClick = this.handlePresentationClick.bind(this);
}
handlePresentationClick() {
this.props.mountModal(<PresentationUploderContainer />);
}
render() {
const { intl, isUserPresenter } = this.props;
// if (!isUserPresenter) return null;
return null; // temporarily disabling the functionality
if (!isUserPresenter) return null;
return (
<Dropdown ref="dropdown">
@ -74,12 +75,12 @@ class ActionsDropdown extends Component {
icon="presentation"
label={intl.formatMessage(intlMessages.presentationLabel)}
description={intl.formatMessage(intlMessages.presentationDesc)}
onClick={presentation.bind(this)}
onClick={this.handlePresentationClick}
/>
{/* These icons are unaligned because of the font issue
Check it later */}
<DropdownListItem
{/* <DropdownListItem
icon="polling"
label={intl.formatMessage(intlMessages.initPollLabel)}
description={intl.formatMessage(intlMessages.initPollDesc)}
@ -90,7 +91,7 @@ class ActionsDropdown extends Component {
label={intl.formatMessage(intlMessages.desktopShareLabel)}
description={intl.formatMessage(intlMessages.desktopShareDesc)}
onClick={shareScreen.bind(this)}
/>
/> */}
</DropdownList>
</DropdownContent>
</Dropdown>
@ -98,4 +99,4 @@ class ActionsDropdown extends Component {
}
}
export default injectIntl(ActionsDropdown);
export default withModalMounter(injectIntl(ActionsDropdown));

View File

@ -5,9 +5,6 @@ import ActionsDropdown from './actions-dropdown/component';
import JoinAudioOptionsContainer from '../audio/audio-menu/container';
import MuteAudioContainer from './mute-button/container';
import { showModal } from '../app/service';
import PresentationUploderContainer from '../presentation/presentation-uploader/container'
export default class ActionsBar extends Component {
constructor(props) {
super(props);
@ -20,7 +17,6 @@ export default class ActionsBar extends Component {
<div className={styles.actionsbar}>
<div className={styles.left}>
<ActionsDropdown {...{isUserPresenter}}/>
<button onClick={()=>{showModal(<PresentationUploderContainer/>)}}>UPLOAD</button>
</div>
<div className={styles.center}>
<MuteAudioContainer />

View File

@ -13,22 +13,25 @@ export default class Checkbox extends Component {
}
handleChange() {
this.onChange();
if (!this.props.disabled)
this.onChange();
}
render() {
const { ariaLabel, ariaLabelledBy, ariaDesc, ariaDescribedBy, } = this.props;
const { className, ariaLabel, ariaLabelledBy, ariaDesc, ariaDescribedBy, disabled } = this.props;
return (
<div className={styles.container}>
<div className={cx({
[styles.disabled]: !!disabled,
}, styles.container, className)}>
<input
type='checkbox'
onChange={this.handleChange}
checked={this.props.checked}
className={styles.input}
aria-labelledby={ariaLabelledBy}
aria-describedby={ariaDescribedBy}/>
aria-describedby={ariaDescribedBy}
disabled={disabled} />
<div onClick={this.handleChange}>
{ this.props.checked ?
<Icon iconName='check' className={cx(styles.icon, styles.checked)}/> :

View File

@ -11,6 +11,11 @@
width: 1px;
}
.disabled .icon {
opacity: .5;
cursor: not-allowed;
}
.icon {
cursor: pointer;
font-size: 1.35rem;

View File

@ -9,16 +9,19 @@ import ListSeparator from './separator/component';
import ListTitle from './title/component';
const propTypes = {
children: PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => {
if (propValue[key].type !== ListItem &&
propValue[key].type !== ListSeparator &&
propValue[key].type !== ListTitle) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
}),
children: PropTypes.oneOfType([
PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => {
if (propValue[key].type !== ListItem &&
propValue[key].type !== ListSeparator &&
propValue[key].type !== ListTitle) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
}),
PropTypes.element,
]).isRequired,
};
export default class DropdownList extends Component {

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { defineMessages, injectIntl, FormattedDate } from 'react-intl';
import update from 'immutability-helper';
import ModalFullscreen from '/imports/ui/components/modal/component';
import ModalFullscreen from '/imports/ui/components/modal/fullscreen/component';
import Icon from '/imports/ui/components/icon/component';
import ButtonBase from '/imports/ui/components/button/base/component';
import Checkbox from '/imports/ui/components/checkbox/component';
@ -83,13 +83,18 @@ class PresentationUploder extends Component {
file: file,
filename: file.name,
uploadedAt: new Date(),
isCurrent: false,
isCurrent: true,
isUploaded: false,
isProcessed: false,
}));
this.setState(({ presentations }) => ({
presentations: presentations.concat(presentationsToUpload),
presentations: presentations
.map(p => {
p.isCurrent = false;
return p;
})
.concat(presentationsToUpload),
}));
}
@ -174,7 +179,7 @@ class PresentationUploder extends Component {
}
renderPresentationItem(item) {
const { isProcessing } = this.state;
const { isProcessing, presentations } = this.state;
let itemClassName = {};
@ -182,6 +187,8 @@ class PresentationUploder extends Component {
itemClassName[styles.tableItemUploading] = item.isUploading;
itemClassName[styles.tableItemProcessing] = item.isProcessing;
const hideActions = isProcessing || presentations.some(_ => !_.isUploaded);
return (
<tr
key={item.id}
@ -210,18 +217,23 @@ class PresentationUploder extends Component {
)
}
</td>
<td className={styles.tableItemActions}>
<Checkbox
disabled={isProcessing}
checked={item.isCurrent}
onChange={() => this.handleCurrentChange(item)}
/>
<ButtonBase
disabled={isProcessing || item.isCurrent || item.filename === 'default.pdf'}
onClick={() => this.handleRemove(item)}>
<Icon iconName={'close'}/>
</ButtonBase>
</td>
{hideActions ? null : (
<td className={styles.tableItemActions}>
<Checkbox
ariaLabel={'Set as current presentation'}
className={styles.itemAction}
checked={item.isCurrent}
onChange={() => this.handleCurrentChange(item)}
/>
<ButtonBase
className={styles.itemAction}
label={'Remove presentation'}
disabled={isProcessing || item.isCurrent || item.filename === 'default.pdf'}
onClick={() => this.handleRemove(item)}>
<Icon iconName={'close'}/>
</ButtonBase>
</td>
)}
</tr>
);
}
@ -234,8 +246,11 @@ class PresentationUploder extends Component {
fileValidMimeTypes,
} = this.props;
// TODO: Change the multiple prop when the endpoint supports multiple files
return (
<Dropzone
multiple={false}
className={styles.dropzone}
activeClassName={styles.dropzoneActive}
rejectClassName={styles.dropzoneReject}

View File

@ -1,7 +1,7 @@
import Presentations from '/imports/api/presentations';
import Auth from '/imports/ui/services/auth';
import { makeCall } from '/imports/ui/services/api';
import { call } from '/imports/ui/services/api';
const getPresentations = () =>
Presentations
@ -32,20 +32,28 @@ const uploadPresentation = (file, meetingID, endpoint) => {
});
};
const removePresentation = (presentationID) => {
return makeCall('removePresentation');
};
const uploadPresentations = (presentationsToUpload, meetingID, uploadEndpoint) =>
Promise.all(
presentationsToUpload
.map(p => uploadPresentation(p.file, meetingID, uploadEndpoint))
);
const removePresentation = presentationID => call('removePresentation', presentationID);
const removePresentations = presentationsToRemove =>
Promise.all(presentationsToRemove.map(p => removePresentation(p.id)));
const persistPresentationChanges = (oldState, newState, uploadEndpoint) => {
const presentationsToUpload = newState.filter(_ => !oldState.includes(_));
const presentationsToDelete = oldState.filter(_ => !newState.includes(_));
const presentationsToRemove = oldState.filter(_ => !newState.includes(_));
const currentPresentation = newState.find(_ => _.isCurrent);
return new Promise((resolve, reject) =>
Promise.resolve().then(
Promise.all(presentationsToUpload.map(p =>
uploadPresentation(p.file, Auth.meetingID, uploadEndpoint)))
).then(
Promise.all(presentationsToDelete.map(p => removePresentation(p.filename)))
).then(() => makeCall('sharePresentation'))
uploadPresentations(presentationsToUpload, Auth.meetingID, uploadEndpoint)
.then(removePresentations.bind(null, presentationsToRemove))
.then(call.bind(null, 'sharePresentation', currentPresentation.id, true))
.then(resolve)
.catch(reject)
);
};

View File

@ -3,6 +3,7 @@
.fileList {
@include scrollbox-vertical();
max-height: 50vh;
}
.table {
@ -75,6 +76,16 @@
background-color: transparentize($color-primary, .95);
}
.itemAction {
display: inline-block;
border: none;
background: transparent;
cursor: pointer;
font-size: 1.35rem;
color: $color-gray-light;
padding: 0;
}
.dropzone {
width: 100%;
display: block;