REFACTOR preview so it renders many iframes

This commit is contained in:
Norbert de Langen 2020-02-14 15:21:44 +01:00
parent 845c1db058
commit 2e2ec0fe79
No known key found for this signature in database
GPG Key ID: 976651DA156C2825
3 changed files with 118 additions and 65 deletions

View File

@ -0,0 +1,58 @@
import React, { Fragment, FunctionComponent, useMemo, useEffect, useRef } from 'react';
import { Global, CSSObject } from '@storybook/theming';
import { IFrame } from './iframe';
import { FramesRendererProps } from './utils/types';
import { stringifyQueryParams } from './utils/stringifyQueryParams';
export const FramesRenderer: FunctionComponent<FramesRendererProps> = ({
refs,
story,
scale,
viewMode,
queryParams,
storyId,
}) => {
const stringifiedQueryParams = stringifyQueryParams(queryParams);
const active = story && story.refId ? `storybook-ref-${story.refId}` : 'storybook-preview-iframe';
const styles = useMemo<CSSObject>(() => {
return {
...Object.values(refs).reduce(
(acc, r) => ({
...acc,
[`#storybook-ref-${r.id}`]: {
visibility: active === `storybook-ref-${r.id}` ? 'visible' : 'hidden',
},
}),
{} as CSSObject
),
'#storybook-preview-iframe': {
visibility: active === 'storybook-preview-iframe' ? 'visible' : 'hidden',
},
};
}, [storyId, story, refs]);
const frames = useRef<Record<string, string>>({
'storybook-preview-iframe': `iframe.html?id=${storyId}&viewMode=${viewMode}${stringifiedQueryParams}`,
});
useEffect(() => {
Object.values(refs)
.filter(r => r.startInjected || (story && r.id === story.refId))
.forEach(r => {
frames.current = {
...frames.current,
[`storybook-ref-${r.id}`]: `${r.url}/iframe.html?id=${storyId}&viewMode=${viewMode}${stringifiedQueryParams}`,
};
});
}, [storyId, story, refs]);
return (
<Fragment>
<Global styles={styles} />
{Object.entries(frames.current).map(([id, src]) => (
<IFrame key={id} id={id} title={id} src={src} allowFullScreen scale={scale} />
))}
</Fragment>
);
};

View File

@ -1,101 +1,87 @@
import React, { Fragment, FunctionComponent, useMemo, useEffect } from 'react';
import merge from '@storybook/api/dist/lib/merge';
import { Helmet } from 'react-helmet-async';
import { API, Consumer, Combo } from '@storybook/api';
import { SET_CURRENT_STORY } from '@storybook/core-events';
import addons, { types, Addon } from '@storybook/addons';
import merge from '@storybook/api/dist/lib/merge';
import { Loader } from '@storybook/components';
import { Helmet } from 'react-helmet-async';
import { Location } from '@storybook/router';
import * as S from './utils/components';
import { ZoomProvider, ZoomConsumer } from './tools/zoom';
import { IFrame } from './iframe';
import { PreviewProps, ApplyWrappersProps, IframeRenderer } from './utils/types';
import { defaultWrappers, ApplyWrappers } from './wrappers';
import { stringifyQueryParams } from './utils/stringifyQueryParams';
import { ToolbarComp } from './toolbar';
import { FramesRenderer } from './FramesRenderer';
export const renderIframe: IframeRenderer = (
storyId,
viewMode,
id,
baseUrl,
scale,
queryParams
) => (
<IFrame
key="iframe"
id="storybook-preview-iframe"
title={id || 'preview'}
src={`${baseUrl}?id=${storyId}&viewMode=${viewMode}${stringifyQueryParams(queryParams)}`}
allowFullScreen
scale={scale}
/>
);
import { PreviewProps } from './utils/types';
const getWrapper = (getFn: API['getElements']) => Object.values(getFn<Addon>(types.PREVIEW));
const getTabs = (getFn: API['getElements']) => Object.values(getFn<Addon>(types.TAB));
export const getTools = (getFn: API['getElements']) => Object.values(getFn<Addon>(types.TOOL));
export const getToolsExtra = (getFn: API['getElements']) =>
Object.values(getFn<Addon>(types.TOOLEXTRA));
const mapper = ({ state, api }: Combo) => ({
const canvasMapper = ({ state, api }: Combo) => ({
storyId: state.storyId,
viewMode: state.viewMode,
customCanvas: api.renderPreview,
queryParams: state.customQueryParams,
getElements: api.getElements,
isLoading: !state.storiesConfigured,
story: api.getData(state.storyId),
refs: state.refs,
});
const createCanvas = (id: string, baseUrl = 'iframe.html', withLoader = true): Addon => ({
id: 'canvas',
title: 'Canvas',
route: p => `/story/${p.storyId}`,
match: p => !!(p.viewMode && p.viewMode.match(/^(story|docs)$/)),
render: p => {
route: ({ storyId }) => `/story/${storyId}`,
match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)),
render: ({ active, key }) => {
return (
<Consumer filter={mapper}>
<Consumer filter={canvasMapper} key={key}>
{({
story,
refs,
customCanvas,
storyId,
viewMode,
queryParams,
getElements,
isLoading,
}: ReturnType<typeof mapper>) => (
}: ReturnType<typeof canvasMapper>) => (
<ZoomConsumer>
{({ value: scale }) => {
const wrappers = [...defaultWrappers, ...getWrapper(getElements)];
const wrappers = useMemo(() => [...defaultWrappers, ...getWrapper(getElements)], [
getElements,
...defaultWrappers,
]);
const data = [storyId, viewMode, id, baseUrl, scale, queryParams] as Parameters<
IframeRenderer
>;
const content = customCanvas ? (
customCanvas(storyId, viewMode, id, baseUrl, scale, queryParams)
) : (
<FramesRenderer
refs={refs}
scale={scale}
story={story}
viewMode={viewMode}
queryParams={queryParams}
storyId={storyId}
/>
);
const content = customCanvas ? customCanvas(...data) : renderIframe(...data);
const props = {
viewMode,
active: p.active,
wrappers,
id,
storyId,
baseUrl,
queryParams,
scale,
customCanvas,
} as ApplyWrappersProps;
const isLoading =
(storyId && !story) || (story && story.refId && !refs[story.refId].startInjected);
return (
<>
{withLoader && isLoading && <Loader id="preview-loader" role="progressbar" />}
<ApplyWrappers {...props}>{content}</ApplyWrappers>
<ApplyWrappers
id={id}
storyId={storyId}
viewMode={viewMode}
active={active}
wrappers={wrappers}
>
{content}
</ApplyWrappers>
</>
);
}}
@ -148,7 +134,11 @@ const Preview: FunctionComponent<PreviewProps> = props => {
useEffect(() => {
if (story) {
api.emit(SET_CURRENT_STORY, { storyId: story.id, viewMode });
api.emit(SET_CURRENT_STORY, {
storyId: story.knownAs || story.id,
viewMode,
options: { target: story.refId },
});
}
}, [story, viewMode]);
@ -160,7 +150,7 @@ const Preview: FunctionComponent<PreviewProps> = props => {
</Helmet>
)}
<ZoomProvider>
<ToolbarComp story={story} api={api} isShown={isToolshown} tabs={tabs} />
<ToolbarComp key="tools" story={story} api={api} isShown={isToolshown} tabs={tabs} />
<S.FrameWrap key="frame" offset={isToolshown ? 40 : 0}>
{tabs.map(({ render: Render, match, ...t }, i) => {
// @ts-ignore

View File

@ -17,7 +17,7 @@ export interface PreviewProps {
path: string;
location: State['location'];
queryParams: State['customQueryParams'];
customCanvas?: IframeRenderer;
customCanvas?: CustomCanvasRenderer;
description: string;
baseUrl: string;
withLoader: boolean;
@ -41,13 +41,9 @@ export interface ApplyWrappersProps {
id: string;
storyId: string;
active: boolean;
baseUrl: string;
scale: number;
queryParams: Record<string, any>;
customCanvas?: IframeRenderer;
}
export type IframeRenderer = (
export type CustomCanvasRenderer = (
storyId: string,
viewMode: State['viewMode'],
id: string,
@ -55,3 +51,12 @@ export type IframeRenderer = (
scale: number,
queryParams: Record<string, any>
) => ReactNode;
export interface FramesRendererProps {
story: Story | Group;
storyId: string;
scale: number;
viewMode: ViewMode;
queryParams: State['customQueryParams'];
refs: State['refs'];
}