ff-stream-web

git clone git://archive.git.mtrnord.blog/MTRNord/ff-stream-web.git
Log | Files | Refs | README | LICENSE

commit 4e17801d3c001dbd078c2410705616d1074956d3
parent 15b5bd5581a49e39b662938634816c81cb191b00
Author: Konstantin Tarkus <hello@tarkus.me>
Date:   Thu,  7 May 2015 13:21:04 +0300

Render the top-level component to <div id="app"></div>, also...

  * Update setViewport higher-order component
  * Move it to ./src/components/decorators
  * Update React Style Guide -> higher-order component example
  * Add Css utility class for rendering CSS

Ref #4

Diffstat:
MREADME.md | 3++-
Mdocs/react-style-guide.md | 17+++++++++++++++++
Msrc/app.js | 4++--
Msrc/components/App/App.js | 9+++------
Dsrc/components/App/setViewport.js | 44--------------------------------------------
Msrc/components/Footer/Footer.js | 19+++++++++++++++++--
Msrc/components/Footer/Footer.less | 4++++
Asrc/components/decorators/setViewport.js | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/templates/index.html | 2+-
Asrc/utils/Css.js | 42++++++++++++++++++++++++++++++++++++++++++
10 files changed, 146 insertions(+), 56 deletions(-)

diff --git a/README.md b/README.md @@ -3,8 +3,8 @@ [![Build Status](http://img.shields.io/travis/kriasoft/react-starter-kit/master.svg?style=flat-square)](http://travis-ci.org/kriasoft/react-starter-kit) [![Dependency Status](https://david-dm.org/kriasoft/react-starter-kit.svg?style=flat-square)](https://david-dm.org/kriasoft/react-starter-kit) [![devDependency Status](https://david-dm.org/kriasoft/react-starter-kit/dev-status.svg?style=flat-square)](https://david-dm.org/kriasoft/react-starter-kit#info=devDependencies) -[![Tips](http://img.shields.io/gratipay/koistya.svg?style=flat-square)](https://gratipay.com/koistya) [![Gitter](http://img.shields.io/badge/chat-online-brightgreen.svg?style=flat-square)](https://gitter.im/kriasoft/react-starter-kit) +[![Tips](http://img.shields.io/gratipay/koistya.svg?style=flat-square)](https://gratipay.com/koistya) > This project template is a skeleton for an [isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/) > web application (SPA) based on Facebook's [React](https://facebook.github.io/react/) @@ -34,6 +34,7 @@ │ ├── /core/ # Core components (Flux dispatcher, base classes, utilities) │ ├── /stores/ # Stores contain the application state and logic │ ├── /templates/ # HTML templates for server-side rendering, emails etc. +│ ├── /utils/ # Utility classes and functions │ ├── /app.js # Client-side startup script │ └── /server.js # Server-side startup script │── gulpfile.js # Configuration file for automated builds diff --git a/docs/react-style-guide.md b/docs/react-style-guide.md @@ -137,6 +137,7 @@ Put custom methods and properties at the bottom of the file, after the render() ##### Higher-order React component example: ```js +// setViewport.js import React, { Component } from 'react'; import { canUseDOM } from 'react/lib/ExecutionEnvironment'; @@ -180,3 +181,19 @@ function setViewport(ComposedComponent) { export default setViewport; ``` + +```js +// MyComponent.js +import React from 'react'; +import setViewport from './setViewport'; + +@setViewport +class MyComponent { + render() { + let { width, height } = this.props.viewport; + return <div>{'Viewport: ' + width + 'x' + height}</div>; + } +} + +export default MyComponent; +``` diff --git a/src/app.js b/src/app.js @@ -34,13 +34,13 @@ function run() { onPageNotFound: emptyFunction }; let element = React.createElement(App, props); - React.render(element, document.body); + React.render(element, document.getElementById('app')); // Update `Application.path` prop when `window.location` is changed Dispatcher.register((action) => { if (action.type === ActionTypes.CHANGE_LOCATION) { element = React.cloneElement(element, {path: action.path}); - React.render(element, document.body); + React.render(element, document.getElementById('app')); } }); } diff --git a/src/components/App/App.js b/src/components/App/App.js @@ -12,7 +12,6 @@ import RegisterPage from '../RegisterPage'; import NotFoundPage from '../NotFoundPage'; import Feedback from '../Feedback'; import Footer from '../Footer'; -import setViewport from './setViewport'; const pages = { ContentPage, ContactPage, LoginPage, RegisterPage, NotFoundPage }; @@ -20,7 +19,6 @@ class App { static propTypes = { path: PropTypes.string.isRequired, - viewport: PropTypes.object.isRequired, onSetTitle: PropTypes.func.isRequired, onSetMeta: PropTypes.func.isRequired, onPageNotFound: PropTypes.func.isRequired @@ -37,8 +35,7 @@ class App { } shouldComponentUpdate(nextProps) { - return this.props.path !== nextProps.path || - this.props.viewport !== nextProps.viewport; + return this.props.path !== nextProps.path; } render() { @@ -76,7 +73,7 @@ class App { <Header /> {this.component} <Feedback /> - <Footer viewport={this.props.viewport} /> + <Footer /> </div> ); } @@ -140,4 +137,4 @@ class App { } -export default setViewport(App); +export default App; diff --git a/src/components/App/setViewport.js b/src/components/App/setViewport.js @@ -1,44 +0,0 @@ -/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */ - -import React, { Component } from 'react'; // eslint-disable-line no-unused-vars -import { canUseDOM } from 'react/lib/ExecutionEnvironment'; - -function setViewport(ComposedComponent) { - return class AppViewport extends Component { - - constructor() { - super(); - - this.state = { - viewport: canUseDOM ? - {width: window.innerWidth, height: window.innerHeight} : - {width: 1366, height: 768} // Default size for server-side rendering - }; - - this.handleResize = () => { - let viewport = {width: window.innerWidth, height: window.innerHeight}; - if (this.state.viewport.width !== viewport.width || - this.state.viewport.height !== viewport.height) { - this.setState({viewport: viewport}); - } - }; - } - - componentDidMount() { - window.addEventListener('resize', this.handleResize); - window.addEventListener('orientationchange', this.handleResize); - } - - componentWillUnmount() { - window.removeEventListener('resize', this.handleResize); - window.removeEventListener('orientationchange', this.handleResize); - } - - render() { - return <ComposedComponent {...this.props} viewport={this.state.viewport}/>; - } - - }; -} - -export default setViewport; diff --git a/src/components/Footer/Footer.js b/src/components/Footer/Footer.js @@ -2,21 +2,36 @@ import React, { PropTypes } from 'react'; // eslint-disable-line no-unused-vars import './Footer.less'; +import Css from '../../utils/Css'; +import setViewport from '../decorators/setViewport'; // eslint-disable-line no-unused-vars +const css = new Css(); + +@setViewport class Footer { static propTypes = { - viewport: PropTypes.object.isRequired + viewport: PropTypes.shape({ + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired + }).isRequired }; render() { + // This is just an example how one can render CSS + let { width, height } = this.props.viewport; + css.set(`.Footer-viewport:after {content:' ${width}x${height}';}`); + return ( <div className="Footer"> <div className="Footer-container"> <span className="Footer-text">© Your Company</span> + <span className="Footer-spacer">·</span> <a className="Footer-link" href="/">Home</a> + <span className="Footer-spacer">·</span> <a className="Footer-link" href="/privacy">Privacy</a> - <span className="Footer-text Footer-text--muted">|&nbsp; {'Viewport: ' + this.props.viewport.width + 'x' + this.props.viewport.height}</span> + <span className="Footer-spacer"> | </span> + <span ref="viewport" className="Footer-viewport Footer-text Footer-text--muted">Viewport:</span> </div> </div> ); diff --git a/src/components/Footer/Footer.less b/src/components/Footer/Footer.less @@ -21,6 +21,10 @@ } } + &-spacer { + color: rgba(255, 255, 255, .3); + } + &-text, &-link { padding: 2px 5px; diff --git a/src/components/decorators/setViewport.js b/src/components/decorators/setViewport.js @@ -0,0 +1,58 @@ +/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */ + +import React, { Component } from 'react'; // eslint-disable-line no-unused-vars +import EventEmitter from 'eventemitter3'; +import { canUseDOM } from 'react/lib/ExecutionEnvironment'; + +let EE; +let viewport = {width: 1366, height: 768}; // Default size for server-side rendering +const RESIZE_EVENT = 'resize'; + +function handleWindowResize() { + if (viewport.width !== window.innerWidth || viewport.height !== window.innerHeight) { + viewport = {width: window.innerWidth, height: window.innerHeight}; + EE.emit(RESIZE_EVENT, viewport); + } +} + +function setViewport(ComposedComponent) { + return class Viewport extends Component { + + constructor() { + super(); + + this.state = { + viewport: canUseDOM ? {width: window.innerWidth, height: window.innerHeight} : viewport + }; + } + + componentDidMount() { + if (!EE) { + EE = new EventEmitter(); + window.addEventListener('resize', handleWindowResize); + window.addEventListener('orientationchange', handleWindowResize); + } + EE.on('resize', this.handleResize, this); + } + + componentWillUnmount() { + EE.removeListener(RESIZE_EVENT, this.handleResize, this); + if (!EE.listeners(RESIZE_EVENT, true)) { + window.removeEventListener('resize', handleWindowResize); + window.removeEventListener('orientationchange', handleWindowResize); + EE = null; + } + } + + render() { + return <ComposedComponent {...this.props} viewport={this.state.viewport}/>; + } + + handleResize(value) { + this.setState({viewport: value}); + } + + }; +} + +export default setViewport; diff --git a/src/templates/index.html b/src/templates/index.html @@ -13,7 +13,7 @@ <!--[if lt IE 8]> <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> <![endif]--> - <%= body %> + <div id="app"><%= body %></div> <!-- Google Analytics: change UA-XXXXX-X to be your site's ID. --> <script> (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= diff --git a/src/utils/Css.js b/src/utils/Css.js @@ -0,0 +1,42 @@ +/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */ + +import { canUseDOM } from 'react/lib/ExecutionEnvironment'; + +class Css { + + set(css) { + if (!canUseDOM) { + return; + } + + let elem; + + if (!this.styleElement) { + elem = this.styleElement = document.createElement('style'); + elem.setAttribute('type', 'text/css'); + + if ('textContent' in elem) { + elem.textContent = css; + } else { + elem.styleSheet.cssText = css; + } + + this.lastCss = css; + document.getElementsByTagName('head')[0].appendChild(elem); + + } else if (this.lastCss !== css) { + + elem = this.styleElement; + + if ('textContent' in elem) { + elem.textContent = css; + } else { + elem.styleSheet.cssText = css; + } + + } + } + +} + +export default Css;