diff --git a/examples/official-storybook/config.js b/examples/official-storybook/config.js index b5f8ca2e8a2b..51db2828b8b6 100644 --- a/examples/official-storybook/config.js +++ b/examples/official-storybook/config.js @@ -51,7 +51,6 @@ addParameters({ options: { hierarchySeparator: /\/|\./, hierarchyRootSeparator: '|', - // theme: themes.dark, }, viewports: { ...INITIAL_VIEWPORTS, diff --git a/examples/official-storybook/stories/core/parameters.stories.js b/examples/official-storybook/stories/core/parameters.stories.js index 41b1e09052ef..b5ab588400a5 100644 --- a/examples/official-storybook/stories/core/parameters.stories.js +++ b/examples/official-storybook/stories/core/parameters.stories.js @@ -17,7 +17,7 @@ export default { // Given we sort of control the props, should we export a prop type? export const passed = ({ // eslint-disable-next-line react/prop-types - parameters, + parameters: { options, ...parameters }, }) =>
Parameters are {JSON.stringify(parameters, null, 2)}; passed.title = 'passed to story'; passed.parameters = { storyParameter: 'storyParameter' }; diff --git a/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap b/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap index 9e5833589004..5e5b364366f2 100644 --- a/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap +++ b/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap @@ -2653,10 +2653,6 @@ exports[`Storyshots Core|Events Force re-render 1`] = ` exports[`Storyshots Core|Parameters passed to story 1`] = `
Parameters are { - "options": { - "hierarchyRootSeparator": "|", - "hierarchySeparator": {} - }, "a11y": { "configure": {}, "options": { diff --git a/lib/channel-postmessage/src/index.ts b/lib/channel-postmessage/src/index.ts index 25d672a6e238..b870100cd0e6 100644 --- a/lib/channel-postmessage/src/index.ts +++ b/lib/channel-postmessage/src/index.ts @@ -56,7 +56,7 @@ export class PostmsgTransport { }); } - const data = stringify({ key: KEY, event }, { maxDepth: 10 }); + const data = stringify({ key: KEY, event }, { maxDepth: 15 }); // TODO: investigate http://blog.teamtreehouse.com/cross-domain-messaging-with-postmessage // might replace '*' with document.location ? diff --git a/lib/theming/src/ensure.ts b/lib/theming/src/ensure.ts index c206a73b0c9a..70fe47718127 100644 --- a/lib/theming/src/ensure.ts +++ b/lib/theming/src/ensure.ts @@ -9,6 +9,12 @@ import isEqual from 'lodash.isequal'; import light from './themes/light'; import { Theme } from './base'; +const base = { + ...light, + animation: {}, + brand: {}, +}; + // merge with concatenating arrays, but no duplicates const merge = (a: any, b: any) => mergeWith({}, a, b, (objValue: any, srcValue: any) => { @@ -32,7 +38,7 @@ export const ensure = (input: any): Theme => { if (!input) { return light; } else { - const missing = deletedDiff(light, input); + const missing = deletedDiff(base, input); if (Object.keys(missing).length) { logger.warn( stripIndent` diff --git a/lib/ui/package.json b/lib/ui/package.json index b7166ff9b364..031d5d423e94 100644 --- a/lib/ui/package.json +++ b/lib/ui/package.json @@ -55,7 +55,7 @@ "react-resize-detector": "^3.2.1", "recompose": "^0.30.0", "semver": "^5.6.0", - "to-camel-case": "^1.0.0", + "telejson": "^2.1.1", "util-deprecate": "^1.0.2" }, "devDependencies": { diff --git a/lib/ui/src/components/layout/container.js b/lib/ui/src/components/layout/container.js index be43e8248087..33f23593ea94 100644 --- a/lib/ui/src/components/layout/container.js +++ b/lib/ui/src/components/layout/container.js @@ -270,7 +270,7 @@ class Layout extends Component { constructor(props) { super(props); - const { options } = this.props; + const { bounds, options } = props; const { resizerNav, resizerPanel } = persistance.get(); @@ -278,7 +278,10 @@ class Layout extends Component { isDragging: false, resizerNav: resizerNav || { x: 200, y: 0 }, resizerPanel: - resizerPanel || (options.panelPosition === 'bottom' ? { x: 0, y: 400 } : { x: 400, y: 0 }), + resizerPanel || + (options.panelPosition === 'bottom' + ? { x: 0, y: bounds.height - 400 } + : { x: bounds.width - 400, y: 0 }), }; } @@ -323,7 +326,7 @@ class Layout extends Component { } } if (isPanelRight && !isPanelHidden) { - if (bounds.width - 200 < panelX) { + if (bounds.width - 200 < panelX || panelX === 0) { mutation.resizerPanel = { x: bounds.width - 200, y: 0, diff --git a/lib/ui/src/core/addons.js b/lib/ui/src/core/addons.js index 996233d5467f..7ebc7cc5ac4a 100644 --- a/lib/ui/src/core/addons.js +++ b/lib/ui/src/core/addons.js @@ -22,14 +22,14 @@ export default ({ provider, store }) => { return ensurePanel(api.getPanels(), selectedPanel, selectedPanel); }, setSelectedPanel: panelName => { - store.setState({ selectedPanel: panelName }); + store.setState({ selectedPanel: panelName }, { persistence: 'session' }); }, }; return { api, state: { - selectedPanel: ensurePanel(api.getPanels()), + selectedPanel: ensurePanel(api.getPanels(), store.getState().selectedPanel), }, }; }; diff --git a/lib/ui/src/core/context.js b/lib/ui/src/core/context.js index 7e03f6a0e8f4..04e13d1d6305 100644 --- a/lib/ui/src/core/context.js +++ b/lib/ui/src/core/context.js @@ -50,7 +50,6 @@ export class Provider extends Component { // Initialize the state to be the initial (persisted) state of the store. // This gives the modules the chance to read the persisted state, apply their defaults // and override if necessary - this.state = store.getInitialState(); const apiData = { navigate, @@ -62,6 +61,8 @@ export class Provider extends Component { storyId, }; + this.state = store.getInitialState(); + this.modules = [ initChannel, initAddons, @@ -90,7 +91,9 @@ export class Provider extends Component { api.on(SET_STORIES, data => { api.setStories(data.stories); - const options = api.getParameters(storyId, 'options'); + const options = storyId + ? api.getParameters(storyId, 'options') + : api.getParameters(Object.keys(data.stories)[0], 'options'); api.setOptions(options); }); diff --git a/lib/ui/src/core/init-provider-api.js b/lib/ui/src/core/init-provider-api.js index f0267136cc62..b3414c4add6c 100644 --- a/lib/ui/src/core/init-provider-api.js +++ b/lib/ui/src/core/init-provider-api.js @@ -1,86 +1,6 @@ -import pick from 'lodash.pick'; -import deprecate from 'util-deprecate'; - -import { create } from '@storybook/theming'; - -const deprecationMessage = (optionsMap, prefix) => - `The options { ${Object.keys(optionsMap).join(', ')} } are deprecated -- use ${ - prefix ? `${prefix}'s` : '' - } { ${Object.values(optionsMap).join(', ')} } instead.`; - -const deprecatedThemeOptions = { - name: 'brandTitle', - url: 'brandUrl', -}; -const applyDeprecatedThemeOptions = deprecate(({ name, url, theme }) => { - const vars = { - brandTitle: name, - brandUrl: url, - brandImage: null, - }; - - return { theme: create(vars, theme) }; -}, deprecationMessage(deprecatedThemeOptions)); -const checkDeprecatedThemeOptions = options => { - if (Object.keys(deprecatedThemeOptions).find(key => !!options[key])) { - return applyDeprecatedThemeOptions(options); - } - return {}; -}; - -const deprecatedLayoutOptions = { - goFullScreen: 'isFullscreen', - showStoriesPanel: 'showNav', - showAddonPanel: 'showPanel', - addonPanelInRight: 'panelPosition', -}; -const applyDeprecatedLayoutOptions = deprecate(options => { - const layoutUpdate = {}; - - ['goFullScreen', 'showStoriesPanel', 'showAddonPanel'].forEach(option => { - if (typeof options[option] !== 'undefined') { - layoutUpdate[deprecatedLayoutOptions[option]] = options[option]; - } - }); - if (options.addonPanelInRight) { - layoutUpdate.panelPosition = 'right'; - } - return layoutUpdate; -}, deprecationMessage(deprecatedLayoutOptions)); -const checkDeprecatedLayoutOptions = options => { - if (Object.keys(deprecatedLayoutOptions).find(key => typeof options[key] !== 'undefined')) { - return applyDeprecatedLayoutOptions(options); - } - return {}; -}; - -export default ({ provider, api, store }) => { +export default ({ provider, api }) => { const providerAPI = { ...api, - - setOptions: options => { - const { layout, ui, selectedPanel } = store.getState(); - - if (options) { - const updatedLayout = { - ...layout, - ...pick(options, Object.keys(layout)), - ...checkDeprecatedLayoutOptions(options), - }; - - const updatedUi = { - ...ui, - ...pick(options, Object.keys(ui)), - ...checkDeprecatedThemeOptions(options), - }; - - store.setState({ - layout: updatedLayout, - ui: updatedUi, - selectedPanel: options.panel || options.selectedPanel || selectedPanel, - }); - } - }, }; provider.handleAPI(providerAPI); diff --git a/lib/ui/src/core/initial-state.js b/lib/ui/src/core/initial-state.js index 5ac502039fc2..5eee085d6286 100644 --- a/lib/ui/src/core/initial-state.js +++ b/lib/ui/src/core/initial-state.js @@ -1,21 +1,6 @@ -import { themes } from '@storybook/theming'; - import merge from '../libs/merge'; const initial = { - ui: { - enableShortcuts: true, - sortStoriesByKind: false, - sidebarAnimations: true, - theme: themes.normal, - }, - layout: { - isToolshown: true, - isFullscreen: false, - showPanel: true, - showNav: true, - panelPosition: 'bottom', - }, customQueryParams: {}, storiesConfigured: false, }; diff --git a/lib/ui/src/core/layout.js b/lib/ui/src/core/layout.js index 80a9523899e2..d6bf8ef8ce9f 100644 --- a/lib/ui/src/core/layout.js +++ b/lib/ui/src/core/layout.js @@ -1,3 +1,64 @@ +import pick from 'lodash.pick'; + +import deprecate from 'util-deprecate'; + +import { create, themes } from '@storybook/theming'; +import merge from '../libs/merge'; + +const deprecatedThemeOptions = { + name: 'brandTitle', + url: 'brandUrl', +}; +const deprecatedLayoutOptions = { + goFullScreen: 'isFullscreen', + showStoriesPanel: 'showNav', + showAddonPanel: 'showPanel', + addonPanelInRight: 'panelPosition', +}; + +const deprecationMessage = (optionsMap, prefix) => + `The options { ${Object.keys(optionsMap).join(', ')} } are deprecated -- use ${ + prefix ? `${prefix}'s` : '' + } { ${Object.values(optionsMap).join(', ')} } instead.`; + +const applyDeprecatedThemeOptions = deprecate(({ name, url, theme }) => { + const vars = { + brandTitle: name, + brandUrl: url, + brandImage: null, + }; + + return { theme: create(vars, theme) }; +}, deprecationMessage(deprecatedThemeOptions)); + +const applyDeprecatedLayoutOptions = deprecate(options => { + const layoutUpdate = {}; + + ['goFullScreen', 'showStoriesPanel', 'showAddonPanel'].forEach(option => { + if (typeof options[option] !== 'undefined') { + layoutUpdate[deprecatedLayoutOptions[option]] = options[option]; + } + }); + if (options.addonPanelInRight) { + layoutUpdate.panelPosition = 'right'; + } + return layoutUpdate; +}, deprecationMessage(deprecatedLayoutOptions)); + +const checkDeprecatedThemeOptions = options => { + if (Object.keys(deprecatedThemeOptions).find(key => !!options[key])) { + return applyDeprecatedThemeOptions(options); + } + return {}; +}; + +const checkDeprecatedLayoutOptions = options => { + if (Object.keys(deprecatedLayoutOptions).find(key => typeof options[key] !== 'undefined')) { + return applyDeprecatedLayoutOptions(options); + } + return {}; +}; + export default function({ store }) { const api = { toggleFullscreen(toggled) { @@ -69,7 +130,54 @@ export default function({ store }) { }; }); }, + + setOptions: options => { + const { layout, ui, selectedPanel } = store.getState(); + + if (options) { + const updatedLayout = { + ...layout, + ...pick(options, Object.keys(layout)), + ...checkDeprecatedLayoutOptions(options), + }; + + const updatedUi = { + ...ui, + ...pick(options, Object.keys(ui)), + ...checkDeprecatedThemeOptions(options), + }; + + store.setState( + { + layout: updatedLayout, + ui: updatedUi, + selectedPanel: options.panel || options.selectedPanel || selectedPanel, + }, + { persistence: 'permanent' } + ); + } + }, }; - return { api }; + const fromState = pick(store.getState(), 'layout', 'ui', 'selectedPanel'); + + const initial = { + ui: { + enableShortcuts: true, + sortStoriesByKind: false, + sidebarAnimations: true, + theme: themes.normal, + }, + layout: { + isToolshown: true, + isFullscreen: false, + showPanel: true, + showNav: true, + panelPosition: 'bottom', + }, + }; + + const state = merge(fromState, initial); + + return { api, state }; } diff --git a/lib/ui/src/core/store.js b/lib/ui/src/core/store.js index c87ada758e15..4bab42572701 100644 --- a/lib/ui/src/core/store.js +++ b/lib/ui/src/core/store.js @@ -1,16 +1,17 @@ // TODO -- make this TS? import { localStorage, sessionStorage } from 'global'; +import { parse, stringify } from 'telejson'; export const STORAGE_KEY = '@storybook/ui/store'; function get(storage) { const serialized = storage.getItem(STORAGE_KEY); - return serialized ? JSON.parse(serialized) : {}; + return serialized ? parse(serialized) : {}; } function set(storage, value) { - storage.setItem(STORAGE_KEY, JSON.stringify(value)); + storage.setItem(STORAGE_KEY, stringify(value, { maxDepth: 50 })); } function update(storage, patch) { diff --git a/lib/ui/src/core/stories.js b/lib/ui/src/core/stories.js index 0ca4f0d4b5da..94cfd00204be 100644 --- a/lib/ui/src/core/stories.js +++ b/lib/ui/src/core/stories.js @@ -1,26 +1,6 @@ -import mergeWith from 'lodash.mergewith'; -import isEqual from 'lodash.isequal'; import { toId, sanitize } from '@storybook/router/utils'; -const merge = (a, b) => - mergeWith({}, a, b, (objValue, srcValue) => { - if (Array.isArray(srcValue) && Array.isArray(objValue)) { - srcValue.forEach(s => { - const existing = objValue.find(o => o === s || isEqual(o, s)); - if (!existing) { - objValue.push(s); - } - }); - - return objValue; - } - if (Array.isArray(objValue)) { - // eslint-disable-next-line no-console - console.log('the types mismatch, picking', objValue); - return objValue; - } - return undefined; - }); +import merge from '../libs/merge'; const initStoriesApi = ({ store,