Merge branch 'iframe-mode'

This commit is contained in:
Arunoda Susiripala 2016-03-21 21:58:17 +05:30
commit 20f7e217f4
13 changed files with 380 additions and 66 deletions

86
dist/client/data.js vendored
View File

@ -1,34 +1,76 @@
"use strict";
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends2 = require("babel-runtime/helpers/extends");
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _keys = require("babel-runtime/core-js/object/keys");
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
exports.setData = setData;
exports.watchData = watchData;
exports.getData = getData;
exports.getDataKey = getDataKey;
exports.getRequestKey = getRequestKey;
var _pageBus = require('page-bus');
var _pageBus2 = _interopRequireDefault(_pageBus);
var _uuid = require('uuid');
var _uuid2 = _interopRequireDefault(_uuid);
var _queryString = require('query-string');
var _queryString2 = _interopRequireDefault(_queryString);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var data = {};
var parsedQs = _queryString2.default.parse(window.location.search);
// We need to check whether we are inside a iframe or not.
// This is used by here and as well as in the UI
var iframeMode = Boolean(parsedQs.iframe);
// We need to create a unique Id for each page. We need to communicate
// using this id as a namespace. Otherwise, each every iframe will get the
// data.
// We create a new UUID if this is main page. Then, this is used by UI to
// create queryString param when creating the iframe.
// If we are in the iframe, we'll get it from the queryString.
var dataId = iframeMode ? parsedQs.dataId : _uuid2.default.v4();
var data = { iframeMode: iframeMode, dataId: dataId };
var handlers = [];
var bus = (0, _pageBus2.default)();
function setData(fields) {
(0, _keys2.default)(fields).forEach(function (key) {
data[key] = fields[key];
});
handlers.forEach(function (handler) {
return handler(getData());
});
// We only need to handle setData if we are in the main page. Otherwise,
// we don't need to handle data come from the live changes.
if (!iframeMode) {
// In page-bus, we must send non-identical data.
// Otherwise, it'll cache and won't trigger.
// That's why we are setting the __lastUpdated value here.
var __lastUpdated = Date.now();
var newData = (0, _extends3.default)({}, data, { __lastUpdated: __lastUpdated });
bus.emit(getDataKey(), (0, _stringify2.default)(newData));
handlers.forEach(function (handler) {
return handler(getData());
});
}
};
function watchData(fn) {
@ -41,4 +83,34 @@ function watchData(fn) {
function getData() {
return (0, _extends3.default)({}, data);
}
function getDataKey() {
return 'data-' + data.dataId;
}
function getRequestKey() {
return 'data-request-' + data.dataId;
}
if (iframeMode) {
// If this is the iframeMode, we need to listen for the data.
bus.on(getDataKey(), function (dataString) {
var data = JSON.parse(dataString);
handlers.forEach(function (handler) {
var newData = (0, _extends3.default)({}, data);
// we need to set the iframeMode value to true since original data
// doesn't have it.
newData.iframeMode = true;
handler(newData);
});
});
// We need to request for the initial data.
bus.emit(getRequestKey());
} else {
// look for initial data request and process it.
bus.on(getRequestKey(), function () {
bus.emit(getDataKey(), (0, _stringify2.default)(data));
});
}

17
dist/client/index.js vendored
View File

@ -19,17 +19,21 @@ var _ui2 = _interopRequireDefault(_ui);
var _data = require('./data');
var _papers = require('./papers');
var _papers2 = _interopRequireDefault(_papers);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var papers = {};
function paper(paperName, m) {
// XXX: Add a better way to create paper and mutate them.
m.hot.dispose(function () {
delete papers[paperName];
delete _papers2.default[paperName];
});
papers[paperName] = {};
_papers2.default[paperName] = {};
function block(name, fn) {
papers[paperName][name] = fn;
_papers2.default[paperName][name] = fn;
return { block: block };
}
@ -37,13 +41,12 @@ function paper(paperName, m) {
}
function getPapers() {
return papers;
return _papers2.default;
}
function renderMain(papers) {
var data = (0, _data.getData)();
data.error = null;
data.papers = papers;
data.selectedPaper = papers[data.selectedPaper] ? data.selectedPaper : (0, _keys2.default)(papers)[0];

7
dist/client/papers.js vendored Normal file
View File

@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var papers = {};
exports.default = papers;

View File

@ -50,9 +50,7 @@ var PaperControls = function (_React$Component) {
fontFamily: '"Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif',
padding: '10px',
marginRight: '10px',
color: '#444',
borderRight: '3px solid #DDD',
minHeight: '1500px'
color: '#444'
};
var h1Style = {

View File

@ -10,6 +10,7 @@ var _keys2 = _interopRequireDefault(_keys);
exports.default = renderUI;
exports.getControls = getControls;
exports.getIframe = getIframe;
exports.renderError = renderError;
exports.renderMain = renderMain;
@ -35,6 +36,10 @@ var _layout2 = _interopRequireDefault(_layout);
var _data = require('../data');
var _papers = require('../papers');
var _papers2 = _interopRequireDefault(_papers);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var rootEl = document.getElementById('root');
@ -51,9 +56,9 @@ function renderUI(data) {
'There is no blocks yet!'
);
var paper = data.papers[data.selectedPaper];
var paper = _papers2.default[data.selectedPaper];
if (paper) {
var block = data.papers[data.selectedPaper][data.selectedBlock];
var block = _papers2.default[data.selectedPaper][data.selectedBlock];
if (block) {
try {
main = block();
@ -68,23 +73,47 @@ function renderUI(data) {
function getControls(data) {
return _react2.default.createElement(_controls2.default, {
papers: data.papers,
papers: _papers2.default,
selectedPaper: data.selectedPaper,
selectedBlock: data.selectedBlock,
onPaper: setSelectedPaper,
onBlock: setSelectedBlock });
}
function getIframe(data) {
var iframeStyle = {
width: '100%',
height: '100%',
border: '0'
};
// We need to send iframe=true and dataId via queryString
// That's how our data layer can start communicate via the iframe.
var queryString = 'iframe=true&dataId=' + data.dataId;
return _react2.default.createElement('iframe', {
style: iframeStyle,
src: '/?' + queryString });
}
function renderError(data, error) {
var controls = getControls(data);
// We always need to render redbox in the mainPage if we get an error.
// Since this is an error, this affects to the main page as well.
var redBox = _react2.default.createElement(_redboxReact2.default, { error: error });
var controls = getControls(data);
var root = _react2.default.createElement(_layout2.default, { controls: controls, content: redBox });
_reactDom2.default.render(root, rootEl);
}
function renderMain(data, main) {
// Inside iframe, we simply render the main component.
if (data.iframeMode) {
return _reactDom2.default.render(main, rootEl);
}
// Inside the main page, we simply render iframe.
var controls = getControls(data);
var root = _react2.default.createElement(_layout2.default, { controls: controls, content: main });
var root = _react2.default.createElement(_layout2.default, { controls: controls, content: getIframe(data) });
_reactDom2.default.render(root, rootEl);
}
@ -92,7 +121,7 @@ function renderMain(data, main) {
function setSelectedPaper(paper) {
var data = (0, _data.getData)();
data.selectedPaper = paper;
data.selectedBlock = (0, _keys2.default)(data.papers[paper])[0];
data.selectedBlock = (0, _keys2.default)(_papers2.default[paper])[0];
(0, _data.setData)(data);
}

View File

@ -4,29 +4,105 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var Layout = function Layout(_ref) {
var controls = _ref.controls;
var content = _ref.content;
return _react2.default.createElement(
'div',
{ style: {} },
_react2.default.createElement(
'div',
{ style: { width: '250px', float: 'left' } },
controls
),
_react2.default.createElement(
'div',
{ style: { marginLeft: '250px' } },
content
)
);
};
var Layout = function (_React$Component) {
(0, _inherits3.default)(Layout, _React$Component);
function Layout() {
(0, _classCallCheck3.default)(this, Layout);
return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(Layout).apply(this, arguments));
}
(0, _createClass3.default)(Layout, [{
key: 'render',
value: function render() {
var _props = this.props;
var controls = _props.controls;
var content = _props.content;
var height = this.state.height;
var rootStyles = {
height: height
};
var controlsStyle = {
width: '240px',
float: 'left',
height: '100%',
overflowY: 'auto'
};
// borderRight: '3px solid #DDD',
var contentStyle = {
height: height,
marginLeft: '250px',
border: '1px solid #DDD',
borderRadius: '4px',
boxShadow: '0px 2px 6px -1px #b8b8b8'
};
return _react2.default.createElement(
'div',
{ style: rootStyles },
_react2.default.createElement(
'div',
{ style: controlsStyle },
controls
),
_react2.default.createElement(
'div',
{ style: contentStyle },
content
)
);
}
}, {
key: 'componentWillMount',
value: function componentWillMount() {
this.updateHeight();
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
window.addEventListener("resize", this.updateHeight.bind(this));
}
}, {
key: 'updateHeight',
value: function updateHeight() {
var _document = document;
var documentElement = _document.documentElement;
var body = _document.body;
var height = documentElement.clientHeight || body.clientHeight;
height -= 20;
this.setState({ height: height });
}
}]);
return Layout;
}(_react2.default.Component);
exports.default = Layout;

View File

@ -9,14 +9,17 @@
"babel-core": "^6.3.15",
"babel-loader": "^6.2.0",
"babel-preset-es2015": "^6.3.13",
"babel-preset-stage-2": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-2": "^6.3.13",
"babel-runtime": "^6.3.14",
"expect": "^1.6.0",
"express": "^4.13.3",
"node-libs-browser": "^0.5.2",
"page-bus": "^3.0.1",
"query-string": "^3.0.3",
"redbox-react": "^1.2.2",
"stack-source-map": "^1.0.4",
"uuid": "^2.0.1",
"webpack": "^1.9.11",
"webpack-dev-middleware": "^1.2.0",
"webpack-hot-middleware": "^2.2.0"

View File

@ -1,5 +1,23 @@
const data = {};
import createPageBus from 'page-bus';
import UUID from 'uuid';
import QS from 'query-string';
const parsedQs = QS.parse(window.location.search);
// We need to check whether we are inside a iframe or not.
// This is used by here and as well as in the UI
const iframeMode = Boolean(parsedQs.iframe);
// We need to create a unique Id for each page. We need to communicate
// using this id as a namespace. Otherwise, each every iframe will get the
// data.
// We create a new UUID if this is main page. Then, this is used by UI to
// create queryString param when creating the iframe.
// If we are in the iframe, we'll get it from the queryString.
const dataId = iframeMode? parsedQs.dataId : UUID.v4();
const data = {iframeMode, dataId};
const handlers = [];
const bus = createPageBus();
export function setData(fields) {
Object
@ -8,7 +26,17 @@ export function setData(fields) {
data[key] = fields[key]
});
handlers.forEach(handler => handler(getData()));
// We only need to handle setData if we are in the main page. Otherwise,
// we don't need to handle data come from the live changes.
if (!iframeMode) {
// In page-bus, we must send non-identical data.
// Otherwise, it'll cache and won't trigger.
// That's why we are setting the __lastUpdated value here.
const __lastUpdated = Date.now();
const newData = {...data, __lastUpdated};
bus.emit(getDataKey(), JSON.stringify(newData));
handlers.forEach(handler => handler(getData()));
}
};
export function watchData(fn) {
@ -22,3 +50,33 @@ export function watchData(fn) {
export function getData() {
return {...data};
}
export function getDataKey() {
return `data-${data.dataId}`;
}
export function getRequestKey() {
return `data-request-${data.dataId}`;
}
if (iframeMode) {
// If this is the iframeMode, we need to listen for the data.
bus.on(getDataKey(), function(dataString) {
const data = JSON.parse(dataString);
handlers.forEach(handler => {
const newData = {...data};
// we need to set the iframeMode value to true since original data
// doesn't have it.
newData.iframeMode = true;
handler(newData);
});
});
// We need to request for the initial data.
bus.emit(getRequestKey());
} else {
// look for initial data request and process it.
bus.on(getRequestKey(), function() {
bus.emit(getDataKey(), JSON.stringify(data));
})
}

View File

@ -5,12 +5,14 @@ import {
watchData
} from './data';
const papers = {};
import papers from './papers';
export function paper(paperName, m) {
// XXX: Add a better way to create paper and mutate them.
m.hot.dispose(() => {
delete papers[paperName];
});
papers[paperName] = {};
function block(name, fn) {
papers[paperName][name] = fn;
@ -27,7 +29,6 @@ export function getPapers() {
export function renderMain(papers) {
const data = getData();
data.error = null;
data.papers = papers;
data.selectedPaper =
(papers[data.selectedPaper])? data.selectedPaper : Object.keys(papers)[0];

2
src/client/papers.js Normal file
View File

@ -0,0 +1,2 @@
const papers = {};
export default papers;

View File

@ -7,9 +7,7 @@ export default class PaperControls extends React.Component {
fontFamily: '"Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif',
padding: '10px',
marginRight: '10px',
color: '#444',
borderRight: '3px solid #DDD',
minHeight: '1500px'
color: '#444'
};
const h1Style = {

View File

@ -4,6 +4,7 @@ import ReadBox from 'redbox-react';
import PaperControls from './controls';
import Layout from './layout';
import {setData, getData} from '../data';
import papers from '../papers';
const rootEl = document.getElementById('root');
@ -15,9 +16,9 @@ export default function renderUI(data) {
// default main
let main = (<p>There is no blocks yet!</p>);
const paper = data.papers[data.selectedPaper];
const paper = papers[data.selectedPaper];
if (paper) {
const block = data.papers[data.selectedPaper][data.selectedBlock];
const block = papers[data.selectedPaper][data.selectedBlock];
if (block) {
try {
main = block();
@ -33,7 +34,7 @@ export default function renderUI(data) {
export function getControls(data) {
return (
<PaperControls
papers={data.papers}
papers={papers}
selectedPaper={data.selectedPaper}
selectedBlock={data.selectedBlock}
onPaper={setSelectedPaper}
@ -41,16 +42,42 @@ export function getControls(data) {
);
}
export function getIframe(data) {
const iframeStyle = {
width: '100%',
height: '100%',
border: '0'
};
// We need to send iframe=true and dataId via queryString
// That's how our data layer can start communicate via the iframe.
const queryString = `iframe=true&dataId=${data.dataId}`;
return (
<iframe
style={iframeStyle}
src={`/?${queryString}`}/>
);
}
export function renderError(data, error) {
const controls = getControls(data);
// We always need to render redbox in the mainPage if we get an error.
// Since this is an error, this affects to the main page as well.
const redBox = (<ReadBox error={error}/>);
const root = (<Layout controls={controls} content={redBox}/>);
const controls = getControls(data);
const root = (<Layout controls={controls} content={redBox} />);
ReactDOM.render(root, rootEl);
}
export function renderMain(data, main) {
// Inside iframe, we simply render the main component.
if (data.iframeMode) {
return ReactDOM.render(main, rootEl);
}
// Inside the main page, we simply render iframe.
const controls = getControls(data);
const root = (<Layout controls={controls} content={main}/>);
const root = (<Layout controls={controls} content={getIframe(data)}/>);
ReactDOM.render(root, rootEl);
}
@ -58,7 +85,7 @@ export function renderMain(data, main) {
function setSelectedPaper(paper) {
const data = getData();
data.selectedPaper = paper;
data.selectedBlock = Object.keys(data.papers[paper])[0];
data.selectedBlock = Object.keys(papers[paper])[0];
setData(data);
}

View File

@ -1,14 +1,54 @@
import React from 'react';
const Layout = ({controls, content}) => (
<div style={{}}>
<div style={{width: '250px', float: 'left'}}>
{controls}
</div>
<div style={{marginLeft: '250px'}}>
{content}
</div>
</div>
);
class Layout extends React.Component {
render() {
const {controls, content} = this.props;
const {height} = this.state;
const rootStyles = {
height
};
const controlsStyle = {
width: '240px',
float: 'left',
height: '100%',
overflowY: 'auto',
// borderRight: '3px solid #DDD',
};
const contentStyle = {
height,
marginLeft: '250px',
border: '1px solid #DDD',
borderRadius: '4px',
boxShadow: '0px 2px 6px -1px #b8b8b8'
};
return (
<div style={rootStyles}>
<div style={controlsStyle}>
{controls}
</div>
<div style={contentStyle}>
{content}
</div>
</div>
);
}
componentWillMount() {
this.updateHeight();
}
componentDidMount() {
window.addEventListener("resize", this.updateHeight.bind(this));
}
updateHeight() {
const {documentElement, body} = document;
let height = documentElement.clientHeight|| body.clientHeight;
height -= 20;
this.setState({height});
}
}
export default Layout;