return decorateStory => { if (cache) { return cache; } let channel = null; if (isBrowser) { try { channel = addons.getChannel(); } catch (e) { channel = createChannel({ page: 'preview' }); addons.setChannel(channel); } } const storyStore = new StoryStore({ channel }); const clientApi = new ClientApi({ storyStore, decorateStory }); const { clearDecorators } = clientApi; const configApi = new ConfigApi({ clearDecorators, storyStore, channel, clientApi }); return { configApi, storyStore, channel, clientApi, showMain, showError, showException, }; };
return () => { let webUrl = null; let channel = null; try { channel = addons.getChannel(); } catch (e) { // getChannel throws if the channel is not defined, // which is fine in this case (we will define it below) } if (params.resetStorybook || !channel) { const host = params.host || parse(NativeModules.SourceCode.scriptURL).hostname; 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); } channel.on('getStories', () => this._sendSetStories()); channel.on('setCurrentStory', d => this._selectStory(d)); this._events.on('setCurrentStory', d => this._selectStory(d)); this._sendSetStories(); this._sendGetCurrentStory(); // finally return the preview component return params.onDeviceUI ? ( <OnDeviceUI stories={this._stories} events={this._events} url={webUrl} /> ) : ( <StoryView url={webUrl} events={this._events} /> ); };
export default function start(render, { decorateStory } = {}) { // check whether we're running on node/browser const isBrowser = navigator && navigator.userAgent && navigator.userAgent !== 'storyshots' && !(navigator.userAgent.indexOf('Node.js') > -1) && !(navigator.userAgent.indexOf('jsdom') > -1); const storyStore = new StoryStore(); const reduxStore = createStore(reducer); const context = { storyStore, reduxStore, decorateStory, showMain, showError, showException, }; const clientApi = new ClientApi(context); let channel; if (isBrowser) { // setup preview channel channel = createChannel({ page: 'preview' }); channel.on(Events.SET_CURRENT_STORY, data => { reduxStore.dispatch(Actions.selectStory(data.kind, data.story)); }); addons.setChannel(channel); Object.assign(context, { channel }); syncUrlWithStore(reduxStore); // Handle keyboard shortcuts window.onkeydown = handleKeyboardShortcuts(channel); } // Provide access to external scripts if `window` is defined. // NOTE this is different to isBrowser, primarily for the JSDOM use case if (typeof window !== 'undefined') { window.__STORYBOOK_CLIENT_API__ = clientApi; window.__STORYBOOK_ADDONS_CHANNEL__ = channel; // may not be defined } const { clearDecorators } = clientApi; const configApi = new ConfigApi({ clearDecorators, ...context }); let previousKind = ''; let previousStory = ''; let previousRevision = -1; const renderMain = forceRender => { if (storyStore.size() === 0) { showNopreview(); return; } const { selectedKind, selectedStory } = reduxStore.getState(); const revision = storyStore.getRevision(); const story = storyStore.getStoryWithContext(selectedKind, selectedStory); if (!story) { showNopreview(); return; } // Render story only if selectedKind or selectedStory has changed. // renderMain() gets executed after each action. Actions will cause the whole // story to re-render without this check. // https://github.com/storybooks/react-storybook/issues/116 // However, we do want the story to re-render if the store itself has changed // (which happens at the moment when HMR occurs) if ( !forceRender && revision === previousRevision && selectedKind === previousKind && previousStory === selectedStory ) { return; } if (!forceRender) { // Scroll to top of the page when changing story document.documentElement.scrollTop = 0; } previousRevision = revision; previousKind = selectedKind; previousStory = selectedStory; render({ ...context, story, selectedKind, selectedStory, forceRender, }); }; // initialize the UI const renderUI = forceRender => { if (isBrowser) { const { error } = reduxStore.getState(); if (error) { showException(error); return; } try { renderMain(forceRender); addons.getChannel().emit(Events.STORY_RENDERED); } catch (ex) { showException(ex); } } }; const forceReRender = () => renderUI(true); if (isBrowser) { channel.on(Events.FORCE_RE_RENDER, forceReRender); } renderUI(); reduxStore.subscribe(renderUI); return { context, clientApi, configApi, forceReRender }; }