From 967a3e9a2715d8f3a835b1cf9a78f85353dc5b0c Mon Sep 17 00:00:00 2001 From: Vladimir Novikov Date: Tue, 14 Jan 2020 19:07:46 +0300 Subject: [PATCH 001/182] addon-knobs disableForceUpdate option --- addons/knobs/src/registerKnobs.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/knobs/src/registerKnobs.ts b/addons/knobs/src/registerKnobs.ts index 0c2be7e5b105..1dee9de26b8b 100644 --- a/addons/knobs/src/registerKnobs.ts +++ b/addons/knobs/src/registerKnobs.ts @@ -22,7 +22,9 @@ function setPaneKnobs(timestamp: boolean | number = +new Date()) { const resetAndForceUpdate = () => { knobStore.markAllUnused(); - forceReRender(); + if (!manager.options.disableForceUpdate) { + forceReRender(); + } }; // Increase performance by reducing how frequently the story is recreated during knob changes From 9fea73ab4babd8602c2906f6d894aba65a65e6e0 Mon Sep 17 00:00:00 2001 From: Vladimir Novikov Date: Sun, 19 Jan 2020 01:27:34 +0300 Subject: [PATCH 002/182] allow disabling force-update and debounce by knob options --- addons/knobs/src/registerKnobs.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/addons/knobs/src/registerKnobs.ts b/addons/knobs/src/registerKnobs.ts index 1dee9de26b8b..40980934e736 100644 --- a/addons/knobs/src/registerKnobs.ts +++ b/addons/knobs/src/registerKnobs.ts @@ -22,9 +22,7 @@ function setPaneKnobs(timestamp: boolean | number = +new Date()) { const resetAndForceUpdate = () => { knobStore.markAllUnused(); - if (!manager.options.disableForceUpdate) { - forceReRender(); - } + forceReRender(); }; // Increase performance by reducing how frequently the story is recreated during knob changes @@ -39,10 +37,12 @@ function knobChanged(change: KnobStoreKnob) { const knobOptions = knobStore.get(name); knobOptions.value = value; - if (!manager.options.disableDebounce) { - debouncedResetAndForceUpdate(); - } else { - resetAndForceUpdate(); + if (!manager.options.disableForceUpdate && !knobOptions.disableForceUpdate) { + if (!manager.options.disableDebounce && !knobOptions.disableDebounce) { + debouncedResetAndForceUpdate(); + } else { + resetAndForceUpdate(); + } } } From f5a14a5ee5865ff395a62e685d3faa693027363b Mon Sep 17 00:00:00 2001 From: Vladimir Novikov Date: Sun, 19 Jan 2020 01:30:27 +0300 Subject: [PATCH 003/182] update typings for disableDebounce and disableForceUpdate knob options --- addons/knobs/src/type-defs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/knobs/src/type-defs.ts b/addons/knobs/src/type-defs.ts index ddc8c2de34e3..a2fd1e4b5b4a 100644 --- a/addons/knobs/src/type-defs.ts +++ b/addons/knobs/src/type-defs.ts @@ -18,7 +18,7 @@ export type Mutable = { -readonly [P in keyof T]: T[P] extends readonly (infer U)[] ? U[] : T[P]; }; -type KnobPlus = K & { type: T; groupId?: string }; +type KnobPlus = K & { type: T; groupId?: string; disableDebounce?: boolean; disableForceUpdate?: boolean }; export type Knob = T extends 'text' ? KnobPlus> From a234c2ccc769f1881da557188a05282e3fd16ef0 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sat, 1 Feb 2020 19:27:40 +0800 Subject: [PATCH 004/182] Addon-knobs: Fix typescript types for disableForceUpdate --- addons/knobs/src/KnobManager.ts | 1 + addons/knobs/src/type-defs.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/addons/knobs/src/KnobManager.ts b/addons/knobs/src/KnobManager.ts index f59430f73e37..8ef58c373fda 100644 --- a/addons/knobs/src/KnobManager.ts +++ b/addons/knobs/src/KnobManager.ts @@ -49,6 +49,7 @@ function escapeStrings(obj: any): any { interface KnobManagerOptions { escapeHTML?: boolean; disableDebounce?: boolean; + disableForceUpdate?: boolean; } export default class KnobManager { diff --git a/addons/knobs/src/type-defs.ts b/addons/knobs/src/type-defs.ts index a2fd1e4b5b4a..469dbdc6f83b 100644 --- a/addons/knobs/src/type-defs.ts +++ b/addons/knobs/src/type-defs.ts @@ -18,7 +18,12 @@ export type Mutable = { -readonly [P in keyof T]: T[P] extends readonly (infer U)[] ? U[] : T[P]; }; -type KnobPlus = K & { type: T; groupId?: string; disableDebounce?: boolean; disableForceUpdate?: boolean }; +type KnobPlus = K & { + type: T; + groupId?: string; + disableDebounce?: boolean; + disableForceUpdate?: boolean; +}; export type Knob = T extends 'text' ? KnobPlus> From cdd599dcff2aae16f4b57f117fa615f9af68f479 Mon Sep 17 00:00:00 2001 From: Behrang <18451+behrangsa@users.noreply.github.com> Date: Sun, 9 Feb 2020 18:22:02 +1100 Subject: [PATCH 005/182] Fixed Galaxy S9's viewport size Galaxy S9's viewport size is 740x360. See https://yesviz.com/devices/s9/. --- addons/viewport/src/defaults.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/viewport/src/defaults.ts b/addons/viewport/src/defaults.ts index 8a67ec92d3a4..6fd59c4e3eee 100644 --- a/addons/viewport/src/defaults.ts +++ b/addons/viewport/src/defaults.ts @@ -92,8 +92,8 @@ export const INITIAL_VIEWPORTS: ViewportMap = { galaxys9: { name: 'Galaxy S9', styles: { - height: '1480px', - width: '720px', + height: '740px', + width: '360px', }, type: 'mobile', }, From 2f38e8e89055c566f69c1c021d52d2afcff42d7e Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Mon, 10 Feb 2020 20:24:55 +0100 Subject: [PATCH 006/182] CLEANUP splitting into multiple files --- .../src/components/preview/PreviewProps.tsx | 57 +++++ lib/ui/src/components/preview/getTools.tsx | 141 ++++++++++++ lib/ui/src/components/preview/preview.tsx | 204 +----------------- lib/ui/src/containers/preview.tsx | 3 +- 4 files changed, 209 insertions(+), 196 deletions(-) create mode 100644 lib/ui/src/components/preview/PreviewProps.tsx create mode 100644 lib/ui/src/components/preview/getTools.tsx diff --git a/lib/ui/src/components/preview/PreviewProps.tsx b/lib/ui/src/components/preview/PreviewProps.tsx new file mode 100644 index 000000000000..fc9dabe124a3 --- /dev/null +++ b/lib/ui/src/components/preview/PreviewProps.tsx @@ -0,0 +1,57 @@ +import { State, API } from '@storybook/api'; +import { ViewMode } from '@storybook/api/dist/modules/addons'; +import { FunctionComponent, ReactNode } from 'react'; + +export interface PreviewProps { + api: API; + storyId: string; + viewMode: ViewMode; + docsOnly: boolean; + options: { + isFullscreen: boolean; + isToolshown: boolean; + }; + id: string; + path: string; + location: State['location']; + queryParams: State['customQueryParams']; + getElements: API['getElements']; + customCanvas?: IframeRenderer; + description: string; + baseUrl: string; + parameters: Record; + withLoader: boolean; +} + +export interface WrapperProps { + index: number; + children: ReactNode; + id: string; + storyId: string; + active: boolean; +} + +export type Wrapper = { + render: FunctionComponent; +}; + +export interface ActualPreviewProps { + wrappers: Wrapper[]; + viewMode: State['viewMode']; + id: string; + storyId: string; + active: boolean; + baseUrl: string; + scale: number; + queryParams: Record; + customCanvas?: IframeRenderer; +} + +export type IframeRenderer = ( + storyId: string, + viewMode: State['viewMode'], + id: string, + baseUrl: string, + scale: number, + queryParams: Record +) => ReactNode; diff --git a/lib/ui/src/components/preview/getTools.tsx b/lib/ui/src/components/preview/getTools.tsx new file mode 100644 index 000000000000..467a804795db --- /dev/null +++ b/lib/ui/src/components/preview/getTools.tsx @@ -0,0 +1,141 @@ +import window from 'global'; +import React, { Fragment } from 'react'; +import memoize from 'memoizerific'; +import copy from 'copy-to-clipboard'; +import { State, API } from '@storybook/api'; +import { types, Addon } from '@storybook/addons'; +import { Icons, IconButton, TabButton, TabBar, Separator } from '@storybook/components'; +import { ZoomConsumer, Zoom } from './zoom'; +import { getElementList, DesktopOnly, stringifyQueryParams } from './preview'; + +import * as S from './components'; + +export const getTools = memoize(10)( + ( + getElements: API['getElements'], + queryParams: State['customQueryParams'], + panels: Partial[], + api: API, + options, + storyId: string, + viewMode: State['viewMode'], + docsOnly: boolean, + location: State['location'], + path: string, + baseUrl: string + ) => { + const tools = getElementList(getElements, types.TOOL, [ + panels.filter(p => !p.hidden).length > 1 + ? ({ + render: () => ( + + + {panels + .filter(p => !p.hidden) + .map((t, index) => { + const to = t.route({ storyId, viewMode, path, location }); + const isActive = path === to; + return ( + + + {t.title} + + + ); + })} + + + + ), + } as Partial) + : null, + { + match: p => p.viewMode === 'story', + render: () => ( + + + {({ set, value }) => ( + set(value * v)} reset={() => set(1)} /> + )} + + + + ), + }, + ]); + const extraTools = getElementList(getElements, types.TOOLEXTRA, [ + { + match: p => p.viewMode === 'story', + render: () => ( + + + + + + ), + }, + { + match: p => p.viewMode === 'story', + render: () => ( + + + + ), + }, + { + match: p => p.viewMode === 'story', + render: () => ( + + copy( + `${window.location.origin}${ + window.location.pathname + }${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}` + ) + } + title="Copy canvas link" + > + + + ), + }, + ]); + // if its a docsOnly page, even the 'story' view mode is considered 'docs' + const filter = (item: Partial) => + item && + (!item.match || + item.match({ + storyId, + viewMode: docsOnly && viewMode === 'story' ? 'docs' : viewMode, + location, + path, + })); + const displayItems = (list: Partial[]) => + list.reduce( + (acc, item, index) => + item ? ( + // @ts-ignore + + {acc} + {item.render({}) || item} + + ) : ( + acc + ), + null + ); + const left = displayItems(tools.filter(filter)); + const right = displayItems(extraTools.filter(filter)); + return { left, right }; + } +); diff --git a/lib/ui/src/components/preview/preview.tsx b/lib/ui/src/components/preview/preview.tsx index 2dd60a1ae68b..f95983a0a1ee 100644 --- a/lib/ui/src/components/preview/preview.tsx +++ b/lib/ui/src/components/preview/preview.tsx @@ -1,32 +1,31 @@ -import window from 'global'; -import React, { Component, Fragment, FunctionComponent, ReactNode } from 'react'; +import React, { Component, Fragment, FunctionComponent } from 'react'; import memoize from 'memoizerific'; -import copy from 'copy-to-clipboard'; import { styled } from '@storybook/theming'; -import { Consumer, State, API, Combo } from '@storybook/api'; +import { Consumer, API, Combo } from '@storybook/api'; import { SET_CURRENT_STORY } from '@storybook/core-events'; import addons, { types, Types, Addon } from '@storybook/addons'; import merge from '@storybook/api/dist/lib/merge'; -import { Icons, IconButton, Loader, TabButton, TabBar, Separator } from '@storybook/components'; +import { Loader } from '@storybook/components'; import { Helmet } from 'react-helmet-async'; -import { ViewMode } from '@storybook/api/dist/modules/addons'; import { Toolbar } from './toolbar'; import * as S from './components'; -import { ZoomProvider, ZoomConsumer, Zoom } from './zoom'; +import { ZoomProvider, ZoomConsumer } from './zoom'; import { IFrame } from './iframe'; +import { PreviewProps, ActualPreviewProps, Wrapper, IframeRenderer } from './PreviewProps'; +import { getTools } from './getTools'; -const DesktopOnly = styled.span({ +export const DesktopOnly = styled.span({ // Hides full screen icon at mobile breakpoint defined in app.js '@media (max-width: 599px)': { display: 'none', }, }); -const stringifyQueryParams = (queryParams: Record) => +export const stringifyQueryParams = (queryParams: Record) => Object.entries(queryParams).reduce((acc, [k, v]) => { return `${acc}&${k}=${v}`; }, ''); @@ -42,42 +41,12 @@ const renderIframe: IframeRenderer = (storyId, viewMode, id, baseUrl, scale, que /> ); -type IframeRenderer = ( - storyId: string, - viewMode: State['viewMode'], - id: string, - baseUrl: string, - scale: number, - queryParams: Record -) => ReactNode; - -const getElementList = memoize( +export const getElementList = memoize( 10 )((getFn: API['getElements'], type: Types, base: Partial[]) => base.concat(Object.values(getFn(type))) ); -interface WrapperProps { - index: number; - children: ReactNode; - id: string; - storyId: string; - active: boolean; -} -type Wrapper = { render: FunctionComponent }; - -interface ActualPreviewProps { - wrappers: Wrapper[]; - viewMode: State['viewMode']; - id: string; - storyId: string; - active: boolean; - baseUrl: string; - scale: number; - queryParams: Record; - customCanvas?: IframeRenderer; -} - const ActualPreview: FunctionComponent = ({ wrappers, viewMode, @@ -123,140 +92,6 @@ const defaultWrappers = [ } as Wrapper, ]; -const getTools = memoize(10)( - ( - getElements: API['getElements'], - queryParams: State['customQueryParams'], - panels: Partial[], - api: API, - options, - storyId: string, - viewMode: State['viewMode'], - docsOnly: boolean, - location: State['location'], - path: string, - baseUrl: string - ) => { - const tools = getElementList(getElements, types.TOOL, [ - panels.filter(p => !p.hidden).length > 1 - ? ({ - render: () => ( - - - {panels - .filter(p => !p.hidden) - .map((t, index) => { - const to = t.route({ storyId, viewMode, path, location }); - const isActive = path === to; - return ( - - - {t.title} - - - ); - })} - - - - ), - } as Partial) - : null, - { - match: p => p.viewMode === 'story', - render: () => ( - - - {({ set, value }) => ( - set(value * v)} reset={() => set(1)} /> - )} - - - - ), - }, - ]); - - const extraTools = getElementList(getElements, types.TOOLEXTRA, [ - { - match: p => p.viewMode === 'story', - render: () => ( - - - - - - ), - }, - { - match: p => p.viewMode === 'story', - render: () => ( - - - - ), - }, - { - match: p => p.viewMode === 'story', - render: () => ( - - copy( - `${window.location.origin}${ - window.location.pathname - }${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}` - ) - } - title="Copy canvas link" - > - - - ), - }, - ]); - // if its a docsOnly page, even the 'story' view mode is considered 'docs' - const filter = (item: Partial) => - item && - (!item.match || - item.match({ - storyId, - viewMode: docsOnly && viewMode === 'story' ? 'docs' : viewMode, - location, - path, - })); - - const displayItems = (list: Partial[]) => - list.reduce( - (acc, item, index) => - item ? ( - // @ts-ignore - - {acc} - {item.render({}) || item} - - ) : ( - acc - ), - null - ); - - const left = displayItems(tools.filter(filter)); - const right = displayItems(extraTools.filter(filter)); - - return { left, right }; - } -); - const getDocumentTitle = (description: string) => { return description ? `${description} ⋅ Storybook` : 'Storybook'; }; @@ -265,27 +100,6 @@ const mapper = ({ state }: Combo) => ({ loading: !state.storiesConfigured, }); -export interface PreviewProps { - api: API; - storyId: string; - viewMode: ViewMode; - docsOnly: boolean; - options: { - isFullscreen: boolean; - isToolshown: boolean; - }; - id: string; - path: string; - location: State['location']; - queryParams: State['customQueryParams']; - getElements: API['getElements']; - customCanvas?: IframeRenderer; - description: string; - baseUrl: string; - parameters: Record; - withLoader: boolean; -} - class Preview extends Component { shouldComponentUpdate({ storyId, diff --git a/lib/ui/src/containers/preview.tsx b/lib/ui/src/containers/preview.tsx index ce81e8edcafe..07ae4ec83cb8 100644 --- a/lib/ui/src/containers/preview.tsx +++ b/lib/ui/src/containers/preview.tsx @@ -4,7 +4,8 @@ import React from 'react'; import { Consumer, Combo } from '@storybook/api'; import { StoriesHash } from '@storybook/api/dist/modules/stories'; -import { Preview, PreviewProps } from '../components/preview/preview'; +import { Preview } from '../components/preview/preview'; +import { PreviewProps } from '../components/preview/PreviewProps'; const nonAlphanumSpace = /[^a-z0-9 ]/gi; const doubleSpace = /\s\s/gi; From 5bfcbf2a0a8158cd6197673b2ce817116c15788c Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Mon, 10 Feb 2020 20:51:08 +0100 Subject: [PATCH 007/182] REFACTOR preview --- lib/api/src/modules/addons.ts | 16 ++-- .../src/components/preview/ApplyWrappers.tsx | 39 ++++++++ .../src/components/preview/PreviewProps.tsx | 3 +- lib/ui/src/components/preview/preview.tsx | 89 ++++++------------- lib/ui/src/containers/preview.tsx | 1 + 5 files changed, 74 insertions(+), 74 deletions(-) create mode 100644 lib/ui/src/components/preview/ApplyWrappers.tsx diff --git a/lib/api/src/modules/addons.ts b/lib/api/src/modules/addons.ts index 6a822157bfba..12ab83f3c923 100644 --- a/lib/api/src/modules/addons.ts +++ b/lib/api/src/modules/addons.ts @@ -44,13 +44,11 @@ export interface Addon { disabled?: boolean; hidden?: boolean; } -export interface Collection { - [key: string]: Addon; +export interface Collection { + [key: string]: T; } -interface Panels { - [id: string]: Addon; -} +type Panels = Collection; type StateMerger = (input: S) => S; @@ -61,9 +59,9 @@ interface StoryInput { } export interface SubAPI { - getElements: (type: Types) => Collection; - getPanels: () => Collection; - getStoryPanels: () => Collection; + getElements: (type: Types) => Collection; + getPanels: () => Panels; + getStoryPanels: () => Panels; getSelectedPanel: () => string; setSelectedPanel: (panelName: string) => void; setAddonState( @@ -102,7 +100,7 @@ export default ({ provider, store }: Module) => { const { parameters } = storyInput; - const filteredPanels: Collection = {}; + const filteredPanels: Collection = {} as Collection; Object.entries(allPanels).forEach(([id, panel]) => { const { paramKey } = panel; if (paramKey && parameters && parameters[paramKey] && parameters[paramKey].disabled) { diff --git a/lib/ui/src/components/preview/ApplyWrappers.tsx b/lib/ui/src/components/preview/ApplyWrappers.tsx new file mode 100644 index 000000000000..4ce078195486 --- /dev/null +++ b/lib/ui/src/components/preview/ApplyWrappers.tsx @@ -0,0 +1,39 @@ +import React, { Fragment, FunctionComponent } from 'react'; +import { styled } from '@storybook/theming'; +import { ApplyWrappersProps, Wrapper } from './PreviewProps'; + +export const ApplyWrappers: FunctionComponent = ({ + wrappers, + id, + storyId, + active, + children, +}) => { + return ( + + {wrappers.reduceRight( + (acc, wrapper, index) => wrapper.render({ index, children: acc, id, storyId, active }), + children + )} + + ); +}; +const IframeWrapper = styled.div(({ theme }) => ({ + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + right: 0, + width: '100%', + height: '100%', + background: theme.background.content, +})); +export const defaultWrappers = [ + { + render: p => ( + + ), + } as Wrapper, +]; diff --git a/lib/ui/src/components/preview/PreviewProps.tsx b/lib/ui/src/components/preview/PreviewProps.tsx index fc9dabe124a3..b3dc5807b5ea 100644 --- a/lib/ui/src/components/preview/PreviewProps.tsx +++ b/lib/ui/src/components/preview/PreviewProps.tsx @@ -7,6 +7,7 @@ export interface PreviewProps { storyId: string; viewMode: ViewMode; docsOnly: boolean; + isLoading: boolean; options: { isFullscreen: boolean; isToolshown: boolean; @@ -35,7 +36,7 @@ export type Wrapper = { render: FunctionComponent; }; -export interface ActualPreviewProps { +export interface ApplyWrappersProps { wrappers: Wrapper[]; viewMode: State['viewMode']; id: string; diff --git a/lib/ui/src/components/preview/preview.tsx b/lib/ui/src/components/preview/preview.tsx index f95983a0a1ee..4a91635fc04f 100644 --- a/lib/ui/src/components/preview/preview.tsx +++ b/lib/ui/src/components/preview/preview.tsx @@ -1,7 +1,7 @@ -import React, { Component, Fragment, FunctionComponent } from 'react'; +import React, { Component, Fragment } from 'react'; import memoize from 'memoizerific'; import { styled } from '@storybook/theming'; -import { Consumer, API, Combo } from '@storybook/api'; +import { API } from '@storybook/api'; import { SET_CURRENT_STORY } from '@storybook/core-events'; import addons, { types, Types, Addon } from '@storybook/addons'; import merge from '@storybook/api/dist/lib/merge'; @@ -16,8 +16,9 @@ import * as S from './components'; import { ZoomProvider, ZoomConsumer } from './zoom'; import { IFrame } from './iframe'; -import { PreviewProps, ActualPreviewProps, Wrapper, IframeRenderer } from './PreviewProps'; +import { PreviewProps, ApplyWrappersProps, IframeRenderer } from './PreviewProps'; import { getTools } from './getTools'; +import { defaultWrappers, ApplyWrappers } from './ApplyWrappers'; export const DesktopOnly = styled.span({ // Hides full screen icon at mobile breakpoint defined in app.js @@ -25,12 +26,20 @@ export const DesktopOnly = styled.span({ display: 'none', }, }); + export const stringifyQueryParams = (queryParams: Record) => Object.entries(queryParams).reduce((acc, [k, v]) => { return `${acc}&${k}=${v}`; }, ''); -const renderIframe: IframeRenderer = (storyId, viewMode, id, baseUrl, scale, queryParams) => ( +export const renderIframe: IframeRenderer = ( + storyId, + viewMode, + id, + baseUrl, + scale, + queryParams +) => (