From 2fa2564633c54e417cc095c0427d9a3e68d12bf4 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 14 Jul 2020 13:19:53 +0200 Subject: [PATCH 1/2] move code responsible for restructuring docs-only stories to the rendering section, so refs get this behavior as well. --- .../sidebar/Tree/State.test.ts} | 2 +- lib/ui/src/components/sidebar/Tree/State.tsx | 130 +++++++++++++++++- lib/ui/src/containers/sidebar.tsx | 130 +----------------- 3 files changed, 132 insertions(+), 130 deletions(-) rename lib/ui/src/{containers/sidebar.test.ts => components/sidebar/Tree/State.test.ts} (98%) diff --git a/lib/ui/src/containers/sidebar.test.ts b/lib/ui/src/components/sidebar/Tree/State.test.ts similarity index 98% rename from lib/ui/src/containers/sidebar.test.ts rename to lib/ui/src/components/sidebar/Tree/State.test.ts index a4e6734a864d..d4707b58aec2 100644 --- a/lib/ui/src/containers/sidebar.test.ts +++ b/lib/ui/src/components/sidebar/Tree/State.test.ts @@ -1,5 +1,5 @@ import { StoriesHash } from '@storybook/api'; -import { collapseDocsOnlyStories, collapseAllStories } from './sidebar'; +import { collapseDocsOnlyStories, collapseAllStories } from './State'; type Item = StoriesHash[keyof StoriesHash]; diff --git a/lib/ui/src/components/sidebar/Tree/State.tsx b/lib/ui/src/components/sidebar/Tree/State.tsx index d2daeb968b34..9d241f66ea5d 100644 --- a/lib/ui/src/components/sidebar/Tree/State.tsx +++ b/lib/ui/src/components/sidebar/Tree/State.tsx @@ -1,8 +1,127 @@ +import { DOCS_MODE } from 'global'; +import rfdc from 'rfdc'; import React, { useMemo, useState, useEffect } from 'react'; -import { isRoot } from '@storybook/api'; +import { isRoot, Story, StoriesHash } from '@storybook/api'; import { toFiltered, getMains, getParents } from './utils'; import { BooleanSet, FilteredType, Item, DataSet } from '../RefHelpers'; +export type ItemType = StoriesHash[keyof StoriesHash]; + +export const collapseAllStories = (stories: StoriesHash) => { + // keep track of component IDs that have been rewritten to the ID of their first leaf child + const componentIdToLeafId: Record = {}; + + // 1) remove all leaves + const leavesRemoved = Object.values(stories).filter( + (item) => !(item.isLeaf && stories[item.parent].isComponent) + ); + + // 2) make all components leaves and rewrite their ID's to the first leaf child + const componentsFlattened = leavesRemoved.map((item) => { + const { id, isComponent, isRoot, children, ...rest } = item; + + // this is a folder, so just leave it alone + if (!isComponent) { + return item; + } + + const nonLeafChildren: string[] = []; + const leafChildren: string[] = []; + children.forEach((child) => + (stories[child].isLeaf ? leafChildren : nonLeafChildren).push(child) + ); + + if (leafChildren.length === 0) { + return item; // pass through, we'll handle you later + } + + const leafId = leafChildren[0]; + const component = { + ...rest, + id: leafId, + kind: (stories[leafId] as Story).kind, + isRoot: false, + isLeaf: true, + isComponent: true, + children: [] as string[], + }; + componentIdToLeafId[id] = leafId; + + // this is a component, so it should not have any non-leaf children + if (nonLeafChildren.length !== 0) { + throw new Error( + `Unexpected '${item.id}': ${JSON.stringify({ isComponent, nonLeafChildren })}` + ); + } + + return component; + }); + + // 3) rewrite all the children as needed + const childrenRewritten = componentsFlattened.map((item) => { + if (item.isLeaf) { + return item; + } + + const { children, ...rest } = item; + const rewritten = children.map((child) => componentIdToLeafId[child] || child); + + return { children: rewritten, ...rest }; + }); + + const result = {} as StoriesHash; + childrenRewritten.forEach((item) => { + result[item.id] = item as Item; + }); + return result; +}; + +export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => { + // keep track of component IDs that have been rewritten to the ID of their first leaf child + const componentIdToLeafId: Record = {}; + const docsOnlyStoriesRemoved = Object.values(storiesHash).filter((item) => { + if (item.isLeaf && item.parameters && item.parameters.docsOnly) { + componentIdToLeafId[item.parent] = item.id; + return false; // filter it out + } + return true; + }); + + const docsOnlyComponentsCollapsed = docsOnlyStoriesRemoved.map((item) => { + // collapse docs-only components + const { isComponent, children, id } = item; + if (isComponent && children.length === 1) { + const leafId = componentIdToLeafId[id]; + if (leafId) { + const collapsed = { + ...item, + id: leafId, + isLeaf: true, + children: [] as string[], + }; + return collapsed; + } + } + + // update groups + if (children) { + const rewritten = children.map((child) => componentIdToLeafId[child] || child); + return { ...item, children: rewritten }; + } + + // pass through stories unmodified + return item; + }); + + const result = {} as StoriesHash; + docsOnlyComponentsCollapsed.forEach((item) => { + result[item.id] = item as Item; + }); + return result; +}; + +const clone = rfdc({ circles: true }); + export const ExpanderContext = React.createContext<{ setExpanded: React.Dispatch>>; expandedSet: BooleanSet; @@ -75,7 +194,14 @@ const useFiltered = (dataset: DataSet, filter: string, parents: Item[], storyId: ); }; -export const useDataset = (dataset: DataSet = {}, filter: string, storyId: string) => { +export const useDataset = (storiesHash: DataSet = {}, filter: string, storyId: string) => { + const dataset = useMemo(() => { + // protect against mutation + const copy = clone(storiesHash); + + return DOCS_MODE ? collapseAllStories(copy) : collapseDocsOnlyStories(copy); + }, [DOCS_MODE, storiesHash]); + const emptyInitial = useMemo( () => ({ filtered: {}, diff --git a/lib/ui/src/containers/sidebar.tsx b/lib/ui/src/containers/sidebar.tsx index 817236188cee..fbc2f38c28f0 100755 --- a/lib/ui/src/containers/sidebar.tsx +++ b/lib/ui/src/containers/sidebar.tsx @@ -1,129 +1,12 @@ -import { DOCS_MODE } from 'global'; -import React, { FunctionComponent, useMemo } from 'react'; -import rfdc from 'rfdc'; +import React, { FunctionComponent } from 'react'; -import { Consumer, Combo, StoriesHash, Story } from '@storybook/api'; +import { Consumer, Combo, StoriesHash } from '@storybook/api'; import SidebarComponent from '../components/sidebar/Sidebar'; import { useMenu } from './menu'; export type Item = StoriesHash[keyof StoriesHash]; -export const collapseAllStories = (stories: StoriesHash) => { - // keep track of component IDs that have been rewritten to the ID of their first leaf child - const componentIdToLeafId: Record = {}; - - // 1) remove all leaves - const leavesRemoved = Object.values(stories).filter( - (item) => !(item.isLeaf && stories[item.parent].isComponent) - ); - - // 2) make all components leaves and rewrite their ID's to the first leaf child - const componentsFlattened = leavesRemoved.map((item) => { - const { id, isComponent, isRoot, children, ...rest } = item; - - // this is a folder, so just leave it alone - if (!isComponent) { - return item; - } - - const nonLeafChildren: string[] = []; - const leafChildren: string[] = []; - children.forEach((child) => - (stories[child].isLeaf ? leafChildren : nonLeafChildren).push(child) - ); - - if (leafChildren.length === 0) { - return item; // pass through, we'll handle you later - } - - const leafId = leafChildren[0]; - const component = { - ...rest, - id: leafId, - kind: (stories[leafId] as Story).kind, - isRoot: false, - isLeaf: true, - isComponent: true, - children: [] as string[], - }; - componentIdToLeafId[id] = leafId; - - // this is a component, so it should not have any non-leaf children - if (nonLeafChildren.length !== 0) { - throw new Error( - `Unexpected '${item.id}': ${JSON.stringify({ isComponent, nonLeafChildren })}` - ); - } - - return component; - }); - - // 3) rewrite all the children as needed - const childrenRewritten = componentsFlattened.map((item) => { - if (item.isLeaf) { - return item; - } - - const { children, ...rest } = item; - const rewritten = children.map((child) => componentIdToLeafId[child] || child); - - return { children: rewritten, ...rest }; - }); - - const result = {} as StoriesHash; - childrenRewritten.forEach((item) => { - result[item.id] = item as Item; - }); - return result; -}; - -export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => { - // keep track of component IDs that have been rewritten to the ID of their first leaf child - const componentIdToLeafId: Record = {}; - const docsOnlyStoriesRemoved = Object.values(storiesHash).filter((item) => { - if (item.isLeaf && item.parameters && item.parameters.docsOnly) { - componentIdToLeafId[item.parent] = item.id; - return false; // filter it out - } - return true; - }); - - const docsOnlyComponentsCollapsed = docsOnlyStoriesRemoved.map((item) => { - // collapse docs-only components - const { isComponent, children, id } = item; - if (isComponent && children.length === 1) { - const leafId = componentIdToLeafId[id]; - if (leafId) { - const collapsed = { - ...item, - id: leafId, - isLeaf: true, - children: [] as string[], - }; - return collapsed; - } - } - - // update groups - if (children) { - const rewritten = children.map((child) => componentIdToLeafId[child] || child); - return { ...item, children: rewritten }; - } - - // pass through stories unmodified - return item; - }); - - const result = {} as StoriesHash; - docsOnlyComponentsCollapsed.forEach((item) => { - result[item.id] = item as Item; - }); - return result; -}; - -const clone = rfdc({ circles: true }); - const Sidebar: FunctionComponent<{}> = React.memo(() => { const mapper = ({ state, api }: Combo) => { const { @@ -137,19 +20,12 @@ const Sidebar: FunctionComponent<{}> = React.memo(() => { refs, } = state; - const stories = useMemo(() => { - // protect against mutation - const copy = clone(storiesHash); - - return DOCS_MODE ? collapseAllStories(copy) : collapseDocsOnlyStories(copy); - }, [DOCS_MODE, storiesHash]); - const menu = useMenu(api, isFullscreen, showPanel, showNav, enableShortcuts); return { title: name, url, - stories, + stories: storiesHash, storiesFailed, storiesConfigured, refs, From d65af63cc201ddb37003d850bcd4badc2198b85d Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 14 Jul 2020 13:56:48 +0200 Subject: [PATCH 2/2] FIX linting issue --- lib/ui/src/components/sidebar/Tree/State.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ui/src/components/sidebar/Tree/State.tsx b/lib/ui/src/components/sidebar/Tree/State.tsx index 9d241f66ea5d..eaeba8a6f4d1 100644 --- a/lib/ui/src/components/sidebar/Tree/State.tsx +++ b/lib/ui/src/components/sidebar/Tree/State.tsx @@ -1,7 +1,7 @@ import { DOCS_MODE } from 'global'; import rfdc from 'rfdc'; import React, { useMemo, useState, useEffect } from 'react'; -import { isRoot, Story, StoriesHash } from '@storybook/api'; +import { isRoot as isRootFn, Story, StoriesHash } from '@storybook/api'; import { toFiltered, getMains, getParents } from './utils'; import { BooleanSet, FilteredType, Item, DataSet } from '../RefHelpers'; @@ -236,7 +236,7 @@ export const useDataset = (storiesHash: DataSet = {}, filter: string, storyId: s const topLevel = useMemo( () => Object.values(filteredSet).filter( - (i) => (i.depth === 0 && !isRoot(i)) || (!isRoot(i) && isRoot(filteredSet[i.parent])) + (i) => (i.depth === 0 && !isRootFn(i)) || (!isRootFn(i) && isRootFn(filteredSet[i.parent])) ), [filteredSet] ); @@ -253,7 +253,7 @@ export const useDataset = (storiesHash: DataSet = {}, filter: string, storyId: s () => getMains(filteredSet).reduce( (acc, item) => { - return isRoot(item) + return isRootFn(item) ? { ...acc, roots: [...acc.roots, item] } : { ...acc, others: [...acc.others, item] }; },