Skip to content
This repository has been archived by the owner on Nov 10, 2017. It is now read-only.

Add option to use storybook with more than one user #132

Merged
merged 7 commits into from
Mar 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"shelljs": "^0.7.3",
"style-loader": "^0.13.1",
"url-loader": "^0.5.7",
"uuid": "^3.0.1",
"webpack": "^1.13.2",
"webpack-dev-middleware": "^1.8.3",
"webpack-hot-middleware": "^2.12.2"
Expand Down
30 changes: 23 additions & 7 deletions src/bin/storybook-start.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import Server from '../server';
program
.option('-h, --host <host>', 'host to listen on')
.option('-p, --port <port>', 'port to listen on')
.option('-s, --secured', 'whether server is running on https')
.option('-c, --config-dir [dir-name]', 'storybook config directory')
.option('-e, --environment [environment]', 'DEVELOPMENT/PRODUCTION environment for webpack')
.option('-r, --reset-cache', 'reset react native packager')
.option('--skip-packager', 'run only storybook server')
.option('-i, --manual-id', 'allow multiple users to work with same storybook')
.parse(process.argv);

const projectDir = path.resolve();
Expand All @@ -18,7 +23,14 @@ if (program.host) {
listenAddr.push(program.host);
}

const server = new Server({projectDir, configDir});
const server = new Server({
projectDir,
configDir,
environment: program.environment,
manualId: program.manualId,
secured: program.secured
});

server.listen(...listenAddr, function (err) {
if (err) {
throw err;
Expand All @@ -27,11 +39,15 @@ server.listen(...listenAddr, function (err) {
console.info(`\nReact Native Storybook started on => ${address}\n`);
});

const projectRoots = configDir === projectDir ? [configDir] : [configDir, projectDir];
if (!program.skipPackager) {
const projectRoots = configDir === projectDir ? [configDir] : [configDir, projectDir];

// RN packager
shelljs.exec([
'node node_modules/react-native/local-cli/cli.js start',
`--projectRoots ${projectRoots.join(',')}`,
`--root ${projectDir}`,
].join(' '), {async: true});
shelljs.exec([
'node node_modules/react-native/local-cli/cli.js start',
`--projectRoots ${projectRoots.join(',')}`,
`--root ${projectDir}`,
program.resetCache && '--reset-cache'
].filter(x => x).join(' '), {async: true});

}
2 changes: 1 addition & 1 deletion src/manager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import renderStorybookUI from '@kadira/storybook-ui';
import Provider from './provider';

const rootEl = document.getElementById('root');
renderStorybookUI(rootEl, new Provider({ url: `ws://${location.host}` }));
renderStorybookUI(rootEl, new Provider({ url: location.host, options: window.storybookOptions}));
30 changes: 26 additions & 4 deletions src/manager/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,25 @@ import React from 'react';
import { Provider } from '@kadira/storybook-ui';
import createChannel from '@kadira/storybook-channel-websocket';
import addons from '@kadira/storybook-addons';
import uuid from 'uuid';

export default class ReactProvider extends Provider {
constructor({ url }) {
constructor({ url: domain, options }) {
super();
this.options = options;
this.selection = null;
this.channel = addons.getChannel();

const secured = options.secured;
const websocketType = secured ? 'wss' : 'ws';
let url = websocketType + '://' + domain;
if (options.manualId) {
const pairedId = uuid().substr(-6);

this.pairedId = pairedId;
url += '/pairedId=' + this.pairedId;
}

if (!this.channel) {
this.channel = createChannel({ url });
addons.setChannel(this.channel);
Expand All @@ -22,10 +35,19 @@ export default class ReactProvider extends Provider {
this.selection = { kind, story };
this.channel.emit('setCurrentStory', { kind, story });
const renderPreview = addons.getPreview();
if (renderPreview) {
return renderPreview(kind, story);

const innerPreview = renderPreview ? renderPreview(kind, story) : null;

if (this.options.manualId) {
return (
<div>
Your ID: { this.pairedId }
{ innerPreview }
</div>
);
}
return null;

return innerPreview;
}

handleAPI(api) {
Expand Down
9 changes: 8 additions & 1 deletion src/preview/components/StoryView/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ export default class StoryView extends Component {
constructor(props, ...args) {
super(props, ...args);
this.state = {storyFn: null, selection: {}};
this.props.events.on('story', this.selectStory.bind(this));

this.storyHandler = this.selectStory.bind(this);

this.props.events.on('story', this.storyHandler);
}

componentWillUnmount() {
this.props.events.removeListener('story', this.storyHandler);
}

selectStory(storyFn, selection) {
Expand Down
17 changes: 13 additions & 4 deletions src/preview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,20 @@ export default class Preview {
return () => {
let webUrl = null;
let channel = addons.getChannel();
if (!channel) {
if (params.resetStorybook || !channel) {
const host = params.host || 'localhost';
const port = params.port || 7007;
const url = `ws://${host}:${port}`;
webUrl = `http://${host}:${port}`;

const port = params.port !== false
? ':' + (params.port || 7007)
: '';

const query = params.query || '';
const secured = params.secured;
const websocketType = secured ? 'wss' : 'ws';
const httpType = secured ? 'https' : 'http';

const url = `${websocketType}://${host}${port}/${query}`;
webUrl = `${httpType}://${host}${port}`;
channel = createChannel({ url });
addons.setChannel(channel);
}
Expand Down
3 changes: 2 additions & 1 deletion src/server/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ const config = {
devtool: '#cheap-module-source-map',
entry: entries,
output: {
path: path.join(__dirname, 'dist'),
filename: 'static/[name].bundle.js',
// Here we set the publicPath to ''.
// This allows us to deploy storybook into subpaths like GitHub pages.
// This works with css and image loaders too.
// This is working for storybook since, we don't use pushState urls and
// relative URLs works always.
publicPath: '',
publicPath: '/',
},
plugins: [
new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }),
Expand Down
5 changes: 4 additions & 1 deletion src/server/index.html.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import url from 'url';

export default function (publicPath, settings) {
export default function (publicPath, options) {
return `
<!DOCTYPE html>
<html>
Expand Down Expand Up @@ -40,6 +40,9 @@ export default function (publicPath, settings) {
</head>
<body style="margin: 0;">
<div id="root"></div>
<script>
window.storybookOptions = ${JSON.stringify(options)};
</script>
<script src="${url.resolve(publicPath, 'static/manager.bundle.js')}"></script>
</body>
</html>
Expand Down
20 changes: 18 additions & 2 deletions src/server/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import express from 'express';
import querystring from 'querystring';
import http from 'http';
import ws from 'ws';
import storybook from './middleware';
Expand All @@ -8,15 +9,30 @@ export default class Server {
this.options = options;
this.httpServer = http.createServer();
this.expressApp = express();
this.expressApp.use(storybook(options.projectDir, options.configDir));
this.expressApp.use(storybook(options));
this.httpServer.on('request', this.expressApp);
this.wsServer = ws.Server({server: this.httpServer});
this.wsServer.on('connection', s => this.handleWS(s));
}

handleWS(socket) {

if (this.options.manualId) {
const params = socket.upgradeReq && socket.upgradeReq.url
? querystring.parse(socket.upgradeReq.url.substr(1))
: {};

if (params.pairedId) {
socket.pairedId = params.pairedId;
}
}

socket.on('message', data => {
this.wsServer.clients.forEach(c => c.send(data));
this.wsServer.clients.forEach(c => {
if (!this.options.manualId || (socket.pairedId && socket.pairedId === c.pairedId)) {
return c.send(data);
}
});
});
}

Expand Down
18 changes: 14 additions & 4 deletions src/server/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import baseConfig from './config/webpack.config';
import baseProductionConfig from './config/webpack.config.prod';
import loadConfig from './config';
import getIndexHtml from './index.html';

Expand All @@ -20,10 +21,13 @@ function getMiddleware(configDir) {
return function () {};
}

export default function (projectDir, configDir) {
export default function ({projectDir, configDir, ...options}) {
// Build the webpack configuration using the `baseConfig`
// custom `.babelrc` file and `webpack.config.js` files
const config = loadConfig('DEVELOPMENT', baseConfig, projectDir, configDir);
const environment = options.environment || 'DEVELOPMENT';
const isProd = environment === 'PRODUCTION';
const currentWebpackConfig = isProd ? baseProductionConfig : baseConfig;
const config = loadConfig(environment, currentWebpackConfig, projectDir, configDir);

// remove the leading '/'
let publicPath = config.output.publicPath;
Expand All @@ -43,10 +47,16 @@ export default function (projectDir, configDir) {
middlewareFn(router);

router.use(webpackDevMiddleware(compiler, devMiddlewareOptions));
router.use(webpackHotMiddleware(compiler));

if (!isProd) {
router.use(webpackHotMiddleware(compiler));
}

router.get('/', function (req, res) {
res.send(getIndexHtml(publicPath));
res.send(getIndexHtml(publicPath, {
manualId: options.manualId,
secured: options.secured,
}));
});

return router;
Expand Down