Skip to content
Snippets Groups Projects
Commit 1ead9ced authored by Lajellu's avatar Lajellu
Browse files

adds presentation controls

parent f1d79376
No related branches found
No related tags found
No related merge requests found
Showing
with 372 additions and 3 deletions
import { publish } from '/imports/api/common/server/helpers';
import { isAllowedTo } from '/imports/startup/server/userPermissions';
import { appendMessageHeader } from '/imports/api/common/server/helpers';
import Presentations from '/imports/api/presentations';
import Slides from '/imports/api/slides';
import { redisConfig } from '/config';
Meteor.methods({
publishSwitchToXSlideMessage(credentials, requestedSlideNum) {
const { meetingId, requesterUserId, requesterToken } = credentials;
const currentPresentationDoc = Presentations.findOne({
meetingId: meetingId,
'presentation.current': true,
});
if (currentPresentationDoc != null) {
const currentSlideDoc = Slides.findOne({
meetingId: meetingId,
presentationId: currentPresentationDoc.presentation.id,
'slide.current': true,
});
if (currentSlideDoc != null) {
const requestedSlideDoc = Slides.findOne({
meetingId: meetingId,
presentationId: currentPresentationDoc.presentation.id,
'slide.num': parseInt(requestedSlideNum),
});
if ((requestedSlideDoc != null) && isAllowedTo('switchSlide', credentials)) {
let message = {
payload: {
page: requestedSlideDoc.slide.id,
meeting_id: meetingId,
},
};
message = appendMessageHeader('go_to_slide', message);
return publish(redisConfig.channels.toBBBApps.presentation, message);
}
}
}
},
});
import React, { PropTypes } from 'react';
import WhiteboardShapeModel from './shape-factory/component.jsx';
import Cursor from './cursor/component.jsx';
import SlideControlsContainer from './slide-controls/container.jsx'; //I added
import { createContainer } from 'meteor/react-meteor-data';
import Slide from './slide/component.jsx';
import styles from './styles.scss';
......@@ -14,6 +15,7 @@ export default class Whiteboard extends React.Component {
renderWhiteboard() {
let slideObj = this.props.currentSlide;
if (this.props.currentSlide) {
slideObj = this.props.currentSlide.slide;
let x = -slideObj.x_offset * 2 * slideObj.width / 100;
......@@ -51,7 +53,7 @@ export default class Whiteboard extends React.Component {
</clipPath>
</defs>
<g clipPath="url(#viewBox)">
<Slide currentSlide={this.props.currentSlide}/>
<Slide id="slideComponent" currentSlide={this.props.currentSlide}/>
{this.props.shapes ? this.props.shapes.map((shape) =>
<WhiteboardShapeModel
shape={shape.shape}
......@@ -74,6 +76,10 @@ export default class Whiteboard extends React.Component {
/>
</g>
</svg>
<SlideControlsContainer
currentSlideNum={slideObj.num}
presentationId={this.props.currentSlide.presentationId}
/>
</ReactCSSTransitionGroup>
);
} else {
......@@ -90,6 +96,7 @@ export default class Whiteboard extends React.Component {
</div>
</div>
<PollingContainer />
</div>
);
}
......
import React, { Component, PropTypes } from 'react';
import styles from './styles.scss';
import Button from '/imports/ui/components/button/component';
import { callServer } from '/imports/ui/services/api/index.js'
export default class SlideControls extends Component {
constructor(props) {
super(props);
this.state = {sliderValue: 100};
}
handleValuesChange(event) {
console.log("handleValuesChange()");
console.log(event.target.value);
this.setState({
sliderValue: event.target.value,
});
}
//Change to the next slide
nextSlide(){
const currentSlideNum = this.props.currentSlideNum;
callServer('publishSwitchToXSlideMessage', currentSlideNum + 1);
}
//Change to the previous slide
previousSlide(){
const currentSlideNum = this.props.currentSlideNum;
callServer('publishSwitchToXSlideMessage', currentSlideNum - 1);
}
//Change to a specific slide (using dropdown menu)
skipToSlide(event){
const requestedSlideNum = event.target.value;
callServer('publishSwitchToXSlideMessage', requestedSlideNum);
}
clickHandler(){
console.log("test click handler");
}
renderSkipSlideOpts(numberOfSlides){
// Fill drop down menu with all the slides in presentation
let optionList = [];
for(i = 1; i <= numberOfSlides; i++){
optionList.push(
<option
value={i}
key={i}
role="option"
aria-controls="slideComponent"
>
Slide {i}
</option>
);
}
return optionList;
}
render() {
const {
currentSlideNum,
numberOfSlides,
} = this.props;
return (
<div id="slideControls" className={styles.slideControlsDiv}>
<div className={styles.ariaLabelsDescs}>
<p id="nextSlideLabel"> Next slide </p>
<p id="nextSlideDescrip"> Change the presentation to the next slide </p>
<p id="prevSlideLabel"> Previous slide </p>
<p id="prevSlideDescrip"> Change the presentation to the previous slide </p>
<p id="skipSlideLabel"> Skip slide</p>
<p id="skipSlideDescrip"> Change the presentation to a specific slide </p>
<p id="fitWidthLabel"> Fit to width </p>
<p id="fitWidthDescrip"> Display the whole width of the slide </p>
<p id="fitScreenLabel"> Fit to screen </p>
<p id="fitScreenDescrip"> Display the whole slide </p>
<p id="zoomLabel"> Zoom </p>
<p id="zoomDescrip"> Change the zoom level of the presentation </p>
</div>
{/*Previous Slide button*/}
<Button
role="button"
aria-labelledby="prevSlideLabel"
aria-describedby="prevSlideDescrip"
aria-controls="slideComponent"
color={'default'}
icon={'left-arrow'}
size={'md'}
onClick={this.previousSlide.bind(this)}
label={'Previous Slide'}
hideLabel={true}
/>
{/*Next Slide button*/}
<Button
role="button"
aria-labelledby="nextSlideLabel"
aria-describedby="nextSlideDescrip"
aria-controls="slideComponent"
color={'default'}
icon={'right-arrow'}
size={'md'}
onClick={this.nextSlide.bind(this)}
label={'Next Slide'}
hideLabel={true}
/>
{/*Skip Slide drop down*/}
<select
role="listbox"
aria-labelledby="skipSlideLabel"
aria-describedby="skipSlideDescrip"
aria-controls="slideComponent"
aria-live="polite"
aria-relevant="all"
value={currentSlideNum} //indicates which option is selected
onChange={this.skipToSlide.bind(this)}
className={styles.scaleLikeButtons}
>
{this.renderSkipSlideOpts(numberOfSlides)}
</select>
{/*Fit to width button*/}
<Button
role="button"
aria-labelledby="fitWidthLabel"
aria-describedby="fitWidthDescrip"
color={'default'}
icon={'fit-to-width'}
size={'md'}
circle={false}
onClick={this.clickHandler.bind(this)}
label={'Fit to Width'}
hideLabel={true}
/>
{/*Fit to page button*/}
<Button
role="button"
aria-labelledby="fitScreenLabel"
aria-describedby="fitScreenDescrip"
color={'default'}
icon={'fit-to-screen'}
size={'md'}
circle={false}
onClick={this.clickHandler.bind(this)}
label={'Fit to Screen'}
hideLabel={true}
/>
{/*Zoom slider*/}
<form
className={styles.scaleLikeButtons}
style={{borderRight : 0}}
>
<span className={styles.zoomMinMax}> 100% </span>
<input
role="slider"
aria-labelledby="zoomLabel"
aria-describedby="zoomDescrip"
aria-valuemax="400"
aria-valuemin="100"
aria-valuenow={this.state.sliderValue}
step="5"
type="range"
min="100"
max="400"
onChange={this.handleValuesChange.bind(this)}
onInput={this.handleValuesChange.bind(this)}
className={styles.zoomSlider}
/>
<span className={styles.zoomMinMax}> 400% </span>
</form>
</div>
);
}
}
import React, { Component, PropTypes } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import SlideService from './service';
import SlideControls from './component.jsx';
const propTypes = {
//Number of current slide being displayed
currentSlideNum: PropTypes.number.isRequired,
//PresentationId of the current presentation
presentationId: PropTypes.string.isRequired,
//Is the user a presenter
userIsPresenter: PropTypes.bool.isRequired,
//Total number of slides in this presentation
numberOfSlides: PropTypes.number.isRequired,
};
class SlideControlsContainer extends React.Component {
constructor(props) {
super(props);
}
render() {
const {
currentSlideNum,
presentationId,
userIsPresenter,
numberOfSlides,
} = this.props;
if (userIsPresenter) {
//Only show controls if user is presenter
return (
<SlideControls
currentSlideNum={currentSlideNum}
numberOfSlides={numberOfSlides}
/>
);
} else {
return null;
}
}
}
export default createContainer((params) => {
const data = SlideService.getSlideData(params);
return data;
}, SlideControlsContainer);
SlideControlsContainer.propTypes = propTypes;
import SlideControls from './component.jsx';
import AuthSingleton from '/imports/ui/services/auth/index.js';
import Users from '/imports/api/users';
import Slides from '/imports/api/slides';
let getSlideData = (params) => {
const { currentSlideNum, presentationId } = params;
//Get userId and meetingId
const userId = AuthSingleton.getCredentials().requesterUserId;
const meetingId = AuthSingleton.getCredentials().meetingId;
//Find the user object of this specific meeting and userid
const user = Users.findOne({
meetingId: meetingId,
userId: userId,
});
//Get total number of slides in this presentation
const numberOfSlides = Slides.find({
'meetingId': meetingId,
'presentationId': presentationId,
}).fetch().length;
return {
userIsPresenter: user.user.presenter,
numberOfSlides: numberOfSlides,
};
}
export default {
getSlideData,
};
@import "imports/ui/components/button/styles.scss";
@import "imports/ui/stylesheets/variables/_all";
// Applies to all slide controls
.slideControlsDiv > * {
background-color: $color-wb-controls-background !important;
color: $color-white;
//Uncomment For the dividing lines
// border-top: 0;
// border-right: $color-gray-light 2px solid;
// border-bottom: 0;
// border-left: 0;
//Uncomment for no border
//border: 0;
border-radius: 0;
box-shadow: none;
}
// For parents of slide controls
.slideControlsDiv, .scaleLikeButtons {
padding: $line-height-computed / 2;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.slideControlsDiv {
flex: 1;
}
// Give non-buttons same height as buttons
.scaleLikeButtons {
height: $line-height-computed * 2;
}
.ariaLabelsDescs {
position: fixed;
left: -999em;
margin: 0;
}
.zoomMinMax {
margin: 0 2%;
}
......@@ -43,7 +43,7 @@
.whiteboardWrapper {
order: 1;
width: 100%;
height: 100%;
height: 90%; //I changed this
display: block;
position: relative;
}
......@@ -16,3 +16,5 @@ $color-heading: #4E525E !default;
$color-link: #3E9DD6 !default;
$color-link-hover: darken($color-link, 15%) !default;
$color-wb-controls-background: #212121 !default; //I added this
......@@ -29,6 +29,7 @@ import '/imports/api/polls/server/modifiers/updatePollCollection';
import '/imports/api/presentations/server/publications';
import '/imports/api/presentations/server/methods/publishSwitchToNextSlideMessage';
import '/imports/api/presentations/server/methods/publishSwitchToPreviousSlideMessage';
import '/imports/api/presentations/server/methods/publishSwitchToXSlideMessage';
import '/imports/api/presentations/server/modifiers/addPresentationToCollection';
import '/imports/api/presentations/server/modifiers/clearPresentationsCollection';
import '/imports/api/presentations/server/modifiers/removePresentationFromCollection';
......
#
# the idea is that this way we prevent test runs (for whenever needed)
HOME=/usr/share/meteor JASMINE_SERVER_UNIT=0 JASMINE_SERVER_INTEGRATION=0 JASMINE_CLIENT_INTEGRATION=0 JASMINE_BROWSER=PhantomJS JASMINE_MIRROR_PORT=3000 ROOT_URL=http://127.0.0.1/html5client meteor
JASMINE_SERVER_UNIT=0 JASMINE_SERVER_INTEGRATION=0 JASMINE_CLIENT_INTEGRATION=0 JASMINE_BROWSER=PhantomJS JASMINE_MIRROR_PORT=3000 ROOT_URL=http://127.0.0.1/html5client meteor
# ROOT_URL_PATH_PREFIX=html5client meteor
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment