UNPKG

spectacle

Version:

ReactJS Powered Presentation Framework

517 lines (465 loc) 18.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _dec, _class, _class2, _temp; /*eslint new-cap:0, max-statements:0*/ /* eslint react/no-did-mount-set-state: 0 */ var _react = require("react"); var _react2 = _interopRequireDefault(_react); var _reactAddonsTransitionGroup = require("react-addons-transition-group"); var _reactAddonsTransitionGroup2 = _interopRequireDefault(_reactAddonsTransitionGroup); var _radium = require("radium"); var _radium2 = _interopRequireDefault(_radium); var _lodash = require("lodash"); var _lodash2 = _interopRequireDefault(_lodash); var _reactRedux = require("react-redux"); var _actions = require("../actions"); var _presenter = require("./presenter"); var _presenter2 = _interopRequireDefault(_presenter); var _export = require("./export"); var _export2 = _interopRequireDefault(_export); var _overview = require("./overview"); var _overview2 = _interopRequireDefault(_overview); var _fullscreen = require("./fullscreen"); var _fullscreen2 = _interopRequireDefault(_fullscreen); var _progress = require("./progress"); var _progress2 = _interopRequireDefault(_progress); var _controls = require("./controls"); var _controls2 = _interopRequireDefault(_controls); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var TransitionGroup = (0, _radium2.default)(_reactAddonsTransitionGroup2.default); var Deck = (_dec = (0, _reactRedux.connect)(function (state) { return state; }), _dec(_class = (0, _radium2.default)(_class = (_temp = _class2 = function (_Component) { _inherits(Deck, _Component); function Deck() { _classCallCheck(this, Deck); var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Deck).call(this)); _this._handleKeyPress = _this._handleKeyPress.bind(_this); _this._handleScreenChange = _this._handleScreenChange.bind(_this); _this._handleClick = _this._handleClick.bind(_this); _this._goToSlide = _this._goToSlide.bind(_this); _this.state = { lastSlide: null, fullscreen: window.innerHeight === screen.height, mobile: window.innerWidth < 1000 }; return _this; } _createClass(Deck, [{ key: "componentDidMount", value: function componentDidMount() { var slide = this._getSlideIndex(); this.setState({ lastSlide: slide }); this._attachEvents(); } }, { key: "componentWillUnmount", value: function componentWillUnmount() { this._detachEvents(); } }, { key: "_attachEvents", value: function _attachEvents() { window.addEventListener("storage", this._goToSlide); window.addEventListener("keydown", this._handleKeyPress); window.addEventListener("resize", this._handleScreenChange); } }, { key: "_detachEvents", value: function _detachEvents() { window.removeEventListener("storage", this._goToSlide); window.removeEventListener("keydown", this._handleKeyPress); window.removeEventListener("resize", this._handleScreenChange); } }, { key: "_handleEvent", value: function _handleEvent(e) { var event = window.event ? window.event : e; if (event.keyCode === 37 || event.keyCode === 33 || event.keyCode === 32 && event.shiftKey) { this._prevSlide(); } else if (event.keyCode === 39 || event.keyCode === 34 || event.keyCode === 32 && !event.shiftKey) { this._nextSlide(); } else if (event.altKey && event.keyCode === 79 && !event.ctrlKey && !event.metaKey) { // o this._toggleOverviewMode(); } else if (event.altKey && event.keyCode === 80 && !event.ctrlKey && !event.metaKey) { // p this._togglePresenterMode(); } } }, { key: "_handleKeyPress", value: function _handleKeyPress(e) { var event = window.event ? window.event : e; if (event.target instanceof HTMLInputElement || event.target.type === "textarea") { return; } this._handleEvent(e); } }, { key: "_handleScreenChange", value: function _handleScreenChange() { this.setState({ fullscreen: window.innerHeight === screen.height, mobile: window.innerWidth < 1000 }); } }, { key: "_toggleOverviewMode", value: function _toggleOverviewMode() { var suffix = this.props.route.params.indexOf("overview") !== -1 ? "" : "?overview"; this.context.history.replace("/" + this.props.route.slide + suffix); } }, { key: "_togglePresenterMode", value: function _togglePresenterMode() { var suffix = this.props.route.params.indexOf("presenter") !== -1 ? "" : "?presenter"; this.context.history.replace("/" + this.props.route.slide + suffix); } }, { key: "_getSuffix", value: function _getSuffix() { if (this.props.route.params.indexOf("presenter") !== -1) { return "?presenter"; } else if (this.props.route.params.indexOf("overview") !== -1) { return "?overview"; } else { return ""; } } }, { key: "_goToSlide", value: function _goToSlide(e) { if (e.key === "spectacle-slide") { var data = JSON.parse(e.newValue); var slide = this._getSlideIndex(); this.setState({ lastSlide: slide || 0 }); if (this._checkFragments(this.props.route.slide, data.forward)) { this.context.history.replace("/" + data.slide + this._getSuffix()); } } } }, { key: "_prevSlide", value: function _prevSlide() { var slide = this._getSlideIndex(); this.setState({ lastSlide: slide }); if (this._checkFragments(this.props.route.slide, false) || this.props.route.params.indexOf("overview") !== -1) { if (slide > 0) { this.context.history.replace("/" + this._getHash(slide - 1) + this._getSuffix()); localStorage.setItem("spectacle-slide", JSON.stringify({ slide: this._getHash(slide - 1), forward: false, time: Date.now() })); } } else if (slide > 0) { localStorage.setItem("spectacle-slide", JSON.stringify({ slide: this._getHash(slide), forward: false, time: Date.now() })); } } }, { key: "_nextSlide", value: function _nextSlide() { var slide = this._getSlideIndex(); this.setState({ lastSlide: slide }); if (this._checkFragments(this.props.route.slide, true) || this.props.route.params.indexOf("overview") !== -1) { if (slide < this.props.children.length - 1) { this.context.history.replace("/" + (this._getHash(slide + 1) + this._getSuffix())); localStorage.setItem("spectacle-slide", JSON.stringify({ slide: this._getHash(slide + 1), forward: true, time: Date.now() })); } } else if (slide < this.props.children.length) { localStorage.setItem("spectacle-slide", JSON.stringify({ slide: this._getHash(slide), forward: true, time: Date.now() })); } } }, { key: "_getHash", value: function _getHash(slide) { var hash = slide; var children = _react2.default.Children.toArray(this.props.children); if ("id" in children[slide].props) { hash = children[slide].props.id; } return hash; } }, { key: "_checkFragments", value: function _checkFragments(slide, forward) { var state = this.context.store.getState(); var fragments = state.fragment.fragments; // Not proud of this at all. 0.14 Parent based contexts will fix this. if (this.props.route.params.indexOf("presenter") !== -1) { var main = document.querySelector(".spectacle-presenter-main"); if (main) { var frags = main.querySelectorAll(".fragment"); if (!frags.length) { return true; } } else { return true; } } if (slide in fragments) { var count = _lodash2.default.size(fragments[slide]); var visible = _lodash2.default.filter(fragments[slide], function (s) { return s.visible === true; }); var hidden = _lodash2.default.filter(fragments[slide], function (s) { return s.visible !== true; }); if (forward === true && visible.length !== count) { this.props.dispatch((0, _actions.updateFragment)({ fragment: hidden[0], visible: true })); return false; } if (forward === false && hidden.length !== count) { this.props.dispatch((0, _actions.updateFragment)({ fragment: visible[_lodash2.default.size(visible) - 1], visible: false })); return false; } return true; } else { return true; } } }, { key: "_getTouchEvents", value: function _getTouchEvents() { var self = this; return { onTouchStart: function onTouchStart(e) { self.touchObject = { startX: e.touches[0].pageX, startY: e.touches[0].pageY }; }, onTouchMove: function onTouchMove(e) { var direction = self._swipeDirection({ x1: self.touchObject.startX, x2: e.touches[0].pageX, y1: self.touchObject.startY, y2: e.touches[0].pageY }); self.touchObject = { startX: self.touchObject.startX, startY: self.touchObject.startY, endX: e.clientX, endY: e.clientY, length: Math.round(Math.sqrt(Math.pow(e.touches[0].pageX - self.touchObject.startX, 2))), direction: direction }; if (direction !== 0) { e.preventDefault(); } }, onTouchEnd: function onTouchEnd(e) { self._handleSwipe(e); }, onTouchCancel: function onTouchCancel(e) { self._handleSwipe(e); } }; } }, { key: "_handleClick", value: function _handleClick(e) { if (this.clickSafe === true) { e.preventDefault(); e.stopPropagation(); e.nativeEvent.stopPropagation(); } } }, { key: "_handleSwipe", value: function _handleSwipe() { if (typeof this.touchObject.length !== "undefined" && this.touchObject.length > 44) { this.clickSafe = true; } else { this.clickSafe = false; } if (Math.abs(this.touchObject.length) > 20) { if (this.touchObject.direction === 1) { this._nextSlide(); } else if (this.touchObject.direction === -1) { this._prevSlide(); } } this.touchObject = {}; } }, { key: "_swipeDirection", value: function _swipeDirection(touch) { var xDist = touch.x1 - touch.x2; var yDist = touch.y1 - touch.y2; var r = Math.atan2(yDist, xDist); var swipeAngle = Math.round(r * 180 / Math.PI); if (swipeAngle < 0) { swipeAngle = 360 - Math.abs(swipeAngle); } if (swipeAngle <= 45 && swipeAngle >= 0) { return 1; } if (swipeAngle <= 360 && swipeAngle >= 315) { return 1; } if (swipeAngle >= 135 && swipeAngle <= 225) { return -1; } return 0; } }, { key: "_getSlideIndex", value: function _getSlideIndex() { var _this2 = this; var index = 0; if (!parseInt(this.props.route.slide)) { _react.Children.toArray(this.props.children).forEach(function (slide, i) { if (slide.props.id === _this2.props.route.slide) { index = i; } }); } else { index = parseInt(this.props.route.slide); } return index; } }, { key: "_renderSlide", value: function _renderSlide() { var slide = this._getSlideIndex(); var child = _react.Children.toArray(this.props.children)[slide]; return (0, _react.cloneElement)(child, { dispatch: this.props.dispatch, fragments: this.props.fragment, key: slide, export: this.props.route.params.indexOf("export") !== -1, print: this.props.route.params.indexOf("print") !== -1, children: _react.Children.toArray(child.props.children), hash: this.props.route.slide, slideIndex: slide, lastSlide: this.state.lastSlide, transition: child.props.transition.length ? child.props.transition : this.props.transition, transitionDuration: child.props.transition.transitionDuration ? child.props.transitionDuration : this.props.transitionDuration }); } }, { key: "render", value: function render() { var globals = this.props.route.params.indexOf("export") !== -1 ? { body: Object.assign(this.context.styles.global.body, { minWidth: 1100, minHeight: 850, overflow: "auto" }), ".spectacle-presenter-next .fragment": { display: "none !important" } } : { ".spectacle-presenter-next .fragment": { display: "none !important" } }; var styles = { deck: { backgroundColor: this.props.route.params.indexOf("presenter") !== -1 || this.props.route.params.indexOf("overview") !== -1 ? "black" : "", position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }, transition: { height: "100%", width: "100%", perspective: 1000, transformStyle: "flat" } }; var componentToRender = void 0; var children = _react.Children.toArray(this.props.children); if (this.props.route.params.indexOf("presenter") !== -1) { componentToRender = _react2.default.createElement(_presenter2.default, { dispatch: this.props.dispatch, slides: children, slide: this._getSlideIndex(), hash: this.props.route.slide, route: this.props.route, lastSlide: this.state.lastSlide }); } else if (this.props.route.params.indexOf("export") !== -1) { componentToRender = _react2.default.createElement(_export2.default, { slides: children, route: this.props.route }); } else if (this.props.route.params.indexOf("overview") !== -1) { componentToRender = _react2.default.createElement(_overview2.default, { slides: children, slide: this._getSlideIndex(), route: this.props.route }); } else { componentToRender = _react2.default.createElement( TransitionGroup, { component: "div", style: [styles.transition] }, this._renderSlide() ); } var showControls = !this.state.fullscreen && !this.state.mobile && this.props.route.params.indexOf("export") === -1 && this.props.route.params.indexOf("overview") === -1 && this.props.route.params.indexOf("presenter") === -1; return _react2.default.createElement( "div", _extends({ className: "spectacle-deck", style: [styles.deck], onClick: this._handleClick }, this._getTouchEvents()), this.props.controls && showControls && _react2.default.createElement(_controls2.default, { currentSlide: this._getSlideIndex(), totalSlides: children.length, onPrev: this._prevSlide.bind(this), onNext: this._nextSlide.bind(this) }), componentToRender, this.props.route.params.indexOf("export") === -1 && this.props.route.params.indexOf("overview") === -1 ? _react2.default.createElement(_progress2.default, { items: children, currentSlide: this._getSlideIndex(), type: this.props.progress }) : "", this.props.route.params.indexOf("export") === -1 ? _react2.default.createElement(_fullscreen2.default, null) : "", this.props.globalStyles && _react2.default.createElement(_radium.Style, { rules: Object.assign(this.context.styles.global, globals) }) ); } }]); return Deck; }(_react.Component), _class2.displayName = "Deck", _class2.defaultProps = { transitionDuration: 500, progress: "pacman", controls: true, globalStyles: true }, _class2.propTypes = { controls: _react.PropTypes.bool, globalStyles: _react.PropTypes.bool, fragment: _react.PropTypes.object, dispatch: _react.PropTypes.func, children: _react.PropTypes.node, route: _react.PropTypes.object, transition: _react.PropTypes.array, transitionDuration: _react.PropTypes.number, progress: _react.PropTypes.oneOf(["pacman", "bar", "number", "none"]) }, _class2.contextTypes = { styles: _react.PropTypes.object, print: _react.PropTypes.object, history: _react.PropTypes.object, presenter: _react.PropTypes.bool, export: _react.PropTypes.bool, overview: _react.PropTypes.bool, store: _react.PropTypes.object, slide: _react.PropTypes.oneOfType([_react.PropTypes.number, _react.PropTypes.string]) }, _temp)) || _class) || _class); exports.default = Deck;