Skip to content

Commit

Permalink
Add options to stories in the story store, and render them in app layers
Browse files Browse the repository at this point in the history
  • Loading branch information
tmeasday committed Jan 9, 2018
1 parent ba66d77 commit 4ee31ea
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 66 deletions.
68 changes: 45 additions & 23 deletions app/angular/src/client/preview/angular/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Type, enableProdMode, NgModule, Component, NgModuleRef } from '@angular/core';
import {
Type,
enableProdMode,
NgModule,
Component,
NgModuleRef,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { BrowserModule } from '@angular/platform-browser';
Expand All @@ -7,12 +13,17 @@ import { ErrorComponent } from './components/error.component';
import { NoPreviewComponent } from './components/no-preview.component';
import { STORY } from './app.token';
import { getAnnotations, getParameters, getPropMetadata } from './utils';
import { NgModuleMetadata, NgStory, IGetStoryWithContext, IContext, NgProvidedData } from './types';
import {
NgModuleMetadata,
NgStory,
IGetStoryWithContext,
NgProvidedData,
} from './types';

let platform: any = null;
let promises: Promise<NgModuleRef<any>>[] = [];

type IRenderStoryFn = (story: IGetStoryWithContext, context: IContext, reRender?: boolean) => void;
type IRenderStoryFn = (story: IGetStoryWithContext, reRender?: boolean) => void;
type IRenderErrorFn = (error: Error) => void;

interface IModule extends Type<any> {
Expand All @@ -29,7 +40,7 @@ interface IComponent extends Type<any> {
const debounce = (
func: IRenderStoryFn | IRenderErrorFn,
wait: number = 100,
immediate: boolean = false
immediate: boolean = false,
): (() => void) => {
let timeout: any;
return function() {
Expand Down Expand Up @@ -73,7 +84,12 @@ const getComponentMetadata = ({
(<any>propsMetadata)[key] = (<any>propsMeta)[key];
});

const { imports = [], schemas = [], declarations = [], providers = [] } = moduleMetadata;
const {
imports = [],
schemas = [],
declarations = [],
providers = [],
} = moduleMetadata;

return {
component,
Expand All @@ -94,7 +110,7 @@ const getAnnotatedComponent = (
meta: NgModule,
component: any,
propsMeta: { [p: string]: any },
params: any[]
params: any[],
): IComponent => {
const NewComponent: any = function(...args: any[]) {
component.call(this, ...args);
Expand All @@ -118,12 +134,15 @@ const getModule = (
schemas: [],
declarations: [],
providers: [],
}
},
): IModule => {
const moduleMeta = new NgModule({
declarations: [...declarations, ...moduleMetadata.declarations],
imports: [BrowserModule, FormsModule, ...moduleMetadata.imports],
providers: [{ provide: STORY, useValue: Object.assign({}, data) }, ...moduleMetadata.providers],
providers: [
{ provide: STORY, useValue: Object.assign({}, data) },
...moduleMetadata.providers,
],
entryComponents: [...entryComponents],
schemas: [...moduleMetadata.schemas],
bootstrap: [...bootstrap],
Expand All @@ -134,23 +153,26 @@ const getModule = (
return NewModule;
};

const initModule = (
currentStory: IGetStoryWithContext,
context: IContext,
reRender: boolean
): IModule => {
const { component, componentMeta, props, propsMeta, params, moduleMeta } = getComponentMetadata(
currentStory(context)
);
const initModule = (currentStory: IGetStory, reRender: boolean): IModule => {
const {
component,
componentMeta,
props,
propsMeta,
params,
moduleMeta,
} = getComponentMetadata(currentStory());

if (!componentMeta) {
throw new Error('No component metadata available');
}

const AnnotatedComponent = getAnnotatedComponent(componentMeta, component, propsMeta, [
...params,
...moduleMeta.providers.map(provider => [provider]),
]);
const AnnotatedComponent = getAnnotatedComponent(
componentMeta,
component,
propsMeta,
[...params, ...moduleMeta.providers.map(provider => [provider])],
);

const story = {
component: AnnotatedComponent,
Expand All @@ -163,7 +185,7 @@ const initModule = (
[AnnotatedComponent],
[AppComponent],
story,
moduleMeta
moduleMeta,
);
};

Expand Down Expand Up @@ -208,6 +230,6 @@ export const renderNoPreview = debounce(() => {
draw(Module);
});

export const renderNgApp = debounce((story, context, reRender) => {
draw(initModule(story, context, reRender), reRender);
export const renderNgApp = debounce((story, reRender) => {
draw(initModule(story, reRender), reRender);
});
6 changes: 1 addition & 5 deletions app/angular/src/client/preview/angular/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,4 @@ export interface NgError {

export type NgProvidedData = NgStory | NgError;

export interface IContext {
[p: string]: any;
}

export type IGetStoryWithContext = (context: IContext) => NgStory;
export type IGetStory = () => NgStory;
8 changes: 2 additions & 6 deletions app/angular/src/client/preview/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function renderMain(data, storyStore, forceRender) {

const { selectedKind, selectedStory } = data;

const story = storyStore.getStory(selectedKind, selectedStory);
const story = storyStore.getStoryWithContext(selectedKind, selectedStory);
if (!story) {
renderNoPreview();
return null;
Expand All @@ -47,11 +47,7 @@ export function renderMain(data, storyStore, forceRender) {
previousKind = selectedKind;
previousStory = selectedStory;
}
const context = {
kind: selectedKind,
story: selectedStory,
};
return renderNgApp(story, context, reRender);
return renderNgApp(story, reRender);
}

export default function renderPreview({ reduxStore, storyStore }, forceRender = false) {
Expand Down
3 changes: 1 addition & 2 deletions app/react-native/src/preview/components/StoryView/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,9 @@ export default class StoryView extends Component {
return this.renderHelp();
}
const { kind, story } = this.state.selection;
const context = { kind, story };
return (
<View key={`${kind}:::${story}`} style={style.main}>
{this.state.storyFn(context)}
{this.state.storyFn()}
</View>
);
}
Expand Down
4 changes: 2 additions & 2 deletions app/react-native/src/preview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default class Preview {
const fileName = this._stories.getStoryFileName(kind);

const stories = this._stories.getStories(kind).map(name => {
const render = this._stories.getStory(kind, name);
const render = this._stories.getStoryWithContext(kind, name);
return { name, render };
});

Expand Down Expand Up @@ -112,7 +112,7 @@ export default class Preview {

_selectStory(selection) {
const { kind, story } = selection;
const storyFn = this._stories.getStory(kind, story);
const storyFn = this._stories.getStoryWithContext(kind, story);
this._events.emit('story', storyFn, selection);
}
}
10 changes: 3 additions & 7 deletions app/react/src/client/preview/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function renderMain(data, storyStore, forceRender) {
const { selectedKind, selectedStory } = data;

const revision = storyStore.getRevision();
const story = storyStore.getStory(selectedKind, selectedStory);
const story = storyStore.getStoryWithContext(selectedKind, selectedStory);
if (!story) {
ReactDOM.render(noPreview, rootEl);
return null;
Expand Down Expand Up @@ -77,12 +77,8 @@ export function renderMain(data, storyStore, forceRender) {
previousStory = selectedStory;
ReactDOM.unmountComponentAtNode(rootEl);

const context = {
kind: selectedKind,
story: selectedStory,
};

const element = story(context);
console.log(story);
const element = story();

if (!element) {
const error = {
Expand Down
17 changes: 9 additions & 8 deletions app/vue/src/client/preview/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ function renderErrorDisplay(error) {
return h(
'div',
{ attrs: { id: 'error-display' } },
error ? [h(ErrorDisplay, { props: { message: error.message, stack: error.stack } })] : []
error
? [
h(ErrorDisplay, {
props: { message: error.message, stack: error.stack },
}),
]
: []
);
},
});
Expand Down Expand Up @@ -54,7 +60,7 @@ export function renderMain(data, storyStore, forceRender) {

const { selectedKind, selectedStory } = data;

const story = storyStore.getStory(selectedKind, selectedStory);
const story = storyStore.getStoryWithContext(selectedKind, selectedStory);

// Unmount the previous story only if selectedKind or selectedStory has changed.
// renderMain() gets executed after each action. Actions will cause the whole
Expand All @@ -71,12 +77,7 @@ export function renderMain(data, storyStore, forceRender) {
return;
}

const context = {
kind: selectedKind,
story: selectedStory,
};

const component = story ? story(context) : NoPreview;
const component = story ? story() : NoPreview;

if (!component) {
const error = {
Expand Down
12 changes: 12 additions & 0 deletions examples/official-storybook/stories/core.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { storiesOf } from '@storybook/react';

storiesOf('core/options', module).add(
'passed to story',
({ options: { myOption } }) => (
<div>
Value of <code>myOption</code> is '<code>{myOption}</code>'
</div>
),
{ myOption: 'value' }
);
14 changes: 6 additions & 8 deletions lib/core/src/client/preview/client_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default class ClientApi {
};
});

api.add = (storyName, getStory) => {
api.add = (storyName, getStory, options) => {
if (typeof storyName !== 'string') {
throw new Error(`Invalid or missing storyName provided for a "${kind}" story.`);
}
Expand All @@ -83,12 +83,10 @@ export default class ClientApi {
const fileName = m ? m.filename : null;

// Add the fully decorated getStory function.
this._storyStore.addStory(
kind,
storyName,
this._decorateStory(getStory, decorators),
fileName
);
this._storyStore.addStory(kind, storyName, this._decorateStory(getStory, decorators), {
...options,
fileName,
});
return api;
};

Expand All @@ -105,7 +103,7 @@ export default class ClientApi {
const fileName = this._storyStore.getStoryFileName(kind);

const stories = this._storyStore.getStories(kind).map(name => {
const render = this._storyStore.getStory(kind, name);
const render = this._storyStore.getStoryWithContext(kind, name);
return { name, render };
});

Expand Down
35 changes: 30 additions & 5 deletions lib/core/src/client/preview/story_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ export default class StoryStore extends EventEmitter {
this._revision += 1;
}

addStory(kind, name, fn, fileName) {
addStory(kind, name, fn, options) {
if (!this._data[kind]) {
this._data[kind] = {
kind,
fileName,
fileName: options.fileName,
index: getId(),
stories: {},
};
Expand All @@ -37,9 +37,10 @@ export default class StoryStore extends EventEmitter {
name,
index: getId(),
fn,
options,
};

this.emit('storyAdded', kind, name, fn);
this.emit('storyAdded', kind, name, fn, options);
}

getStoryKinds() {
Expand Down Expand Up @@ -70,7 +71,7 @@ export default class StoryStore extends EventEmitter {
return storiesKind.fileName;
}

getStory(kind, name) {
getStoryAndOptions(kind, name) {
const storiesKind = this._data[kind];
if (!storiesKind) {
return null;
Expand All @@ -81,7 +82,31 @@ export default class StoryStore extends EventEmitter {
return null;
}

return storyInfo.fn;
const { fn, options } = storyInfo;
return {
story: fn,
options,
};
}

getStory(kind, name) {
const data = this.getStoryAndOptions(kind, name);
return data && data.story;
}

getStoryWithContext(kind, name) {
const data = this.getStoryAndOptions(kind, name);
if (!data) {
return null;
}

const { story, options } = data;
return () =>
story({
kind,
name,
options,
});
}

removeStoryKind(kind) {
Expand Down

0 comments on commit 4ee31ea

Please sign in to comment.