Add presenter upload capabilities
This commit is contained in:
parent
ff5d3d1211
commit
1dc3bccb0d
@ -29,7 +29,7 @@ export default function removePresentation(credentials, presentationId) {
|
||||
}
|
||||
|
||||
let payload = {
|
||||
meeting_id: currentPoll.meetingId,
|
||||
meeting_id: meetingId,
|
||||
presentation_id: presentationId,
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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));
|
||||
|
@ -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 />
|
||||
|
@ -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)}/> :
|
||||
|
5
bigbluebutton-html5/imports/ui/components/checkbox/styles.scss
Normal file → Executable file
5
bigbluebutton-html5/imports/ui/components/checkbox/styles.scss
Normal file → Executable file
@ -11,6 +11,11 @@
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.disabled .icon {
|
||||
opacity: .5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
font-size: 1.35rem;
|
||||
|
@ -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 {
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user