REMOVE loading of refs in node, load them in the browser always && ADD indicators to Sidebar

This commit is contained in:
Norbert de Langen 2020-02-26 16:19:24 +01:00
parent bb02da3f20
commit 331c08a27d
No known key found for this signature in database
GPG Key ID: 976651DA156C2825
5 changed files with 188 additions and 103 deletions

View File

@ -234,7 +234,7 @@ class ManagerProvider extends Component<ManagerProviderProps, State> {
`${url}/iframe.html`.match(source)
);
api.setRef(refId, data.stories);
api.setRef(refId, data);
break;
}

View File

@ -1,4 +1,5 @@
import { location } from 'global';
import { location, fetch } from 'global';
import { logger } from '@storybook/client-logger';
import {
transformStoriesRawToStoriesHash,
StoriesRaw,
@ -16,9 +17,20 @@ export interface SubState {
refs: Refs;
}
type Versions = string[];
export interface SetRefData {
stories?: StoriesRaw;
versions?: Versions;
startInjected?: true;
loginUrl?: string;
error?: any;
}
export interface SubAPI {
setRef: (id: string, stories: StoriesRaw) => void;
setRef: (id: string, data: SetRefData) => void;
getRefs: () => Refs;
checkRef: (ref: InceptionRef) => Promise<void>;
}
export type Mapper = (ref: InceptionRef, story: StoryInput) => StoryInput;
@ -28,7 +40,9 @@ export interface InceptionRef {
url: string;
startInjected?: boolean;
stories: StoriesHash;
versions?: Versions;
ready?: boolean;
error?: any;
}
export type Refs = Record<string, InceptionRef>;
@ -98,43 +112,78 @@ const map = (input: StoriesRaw, ref: InceptionRef, options: { mapper?: Mapper })
};
const initRefsApi = ({ store, provider }: Module) => {
const getRefs: SubAPI['getRefs'] = () => {
const { refs = {} } = provider.getConfig();
const api: SubAPI = {
checkRef: async ref => {
const { id, url } = ref;
return refs;
const response = await fetch(`${url}/data.json`).catch(() => false);
if (response) {
const { ok } = response;
if (ok) {
const data: SetRefData = await response
.json()
.catch(error => ({ startInjected: true, error }));
api.setRef(id, data);
} else {
api.setRef(id, { startInjected: true });
}
} else {
logger.warn('an auto-injected ref threw a cors-error');
api.setRef(id, { startInjected: true });
}
},
getRefs: () => {
const { refs: fromConfig = {} } = provider.getConfig();
const { refs: fromState = {} } = store.getState();
return { ...fromConfig, ...fromState };
},
setRef: (id, { stories, ...rest }) => {
const ref = api.getRefs()[id];
const after = stories
? namespace(
transformStoriesRawToStoriesHash(map(stories, ref, { mapper: defaultMapper }), {}),
ref
)
: undefined;
const result = { ...ref, stories: after, ...rest, ready: true };
store.setState({
refs: {
...(store.getState().refs || {}),
[id]: result,
},
});
},
};
const setRef: SubAPI['setRef'] = (id, data) => {
const ref = getRefs()[id];
const after = namespace(
transformStoriesRawToStoriesHash(map(data, ref, { mapper: defaultMapper }), {}),
ref
);
const refs = Object.entries(api.getRefs());
store.setState({
refs: {
...(store.getState().refs || {}),
[id]: { startInjected: true, ...ref, stories: after, ready: true },
},
});
};
const initialState: SubState['refs'] = Object.entries(getRefs()).reduce(
const initialState: SubState['refs'] = refs.reduce(
(acc, [key, data]) => ({
...acc,
[key]: {
startInjected: true,
title: data.id,
...data,
},
}),
{} as SubState['refs']
);
console.log(refs);
refs.forEach(([k, v]) => {
api.checkRef(v);
});
return {
api: {
setRef,
getRefs,
},
api,
state: {
refs: initialState,
},

View File

@ -1,16 +0,0 @@
import fetch, { Response, RequestInfo, RequestInit } from 'node-fetch';
function toJson(response: Response) {
return response.json();
}
export default async function<R = unknown>(url: RequestInfo, options: RequestInit = {}) {
const jsonHeaders = { 'Content-Type': 'application/json', Accept: 'application/json' };
Object.assign(options, { headers: Object.assign(jsonHeaders, options.headers) });
if (options.body) {
Object.assign(options, { body: JSON.stringify(options.body) });
}
return fetch(url, options).then(toJson) as Promise<R>;
}

View File

@ -1,8 +1,5 @@
import path from 'path';
import { logger } from '@storybook/node-logger';
import { transform } from '@storybook/api/dist/modules/refs';
import loadPresets from '../presets';
import fetch from '../common/fetch';
import loadCustomPresets from '../common/custom-presets';
async function getManagerWebpackConfig(options, presets) {
@ -11,46 +8,15 @@ async function getManagerWebpackConfig(options, presets) {
const entries = await presets.apply('managerEntries', [], options);
if (refs) {
const out = await Promise.all(
Object.entries(refs).map(async ([key, value]) => {
const url = typeof value === 'string' ? value : value.url;
const dataUrl = `${url}/data.json`;
Object.entries(refs).forEach(([key, value]) => {
const url = typeof value === 'string' ? value : value.url;
const title = typeof value === 'string' ? key : value.title;
logger.warn(`searching for data of ref: ${key} at: ${dataUrl}`);
const data = await fetch(dataUrl)
.then(r => {
if (r.error) {
throw new Error('');
}
return r;
})
.catch(e => {
return false;
});
refs[key] = {
id: key,
url,
...(data || {}),
};
return [key, !!data, data];
})
);
out.forEach(([key, hasData, data]) => {
if (!hasData) {
logger.warn(
`ref with id: "${key}" did not have data, the frame will be loaded immediately, which is bad for storybook performance`
);
refs[key].startInjected = true;
} else {
refs[key].startInjected = false;
refs[key].stories = transform(refs[key].stories, refs[key]);
}
refs[key] = {
id: key,
title,
url,
};
});
entries.push(path.resolve(path.join(options.configDir, `generated-refs.js`)));

View File

@ -13,6 +13,7 @@ import { transparentize } from 'polished';
import { styled } from '@storybook/theming';
import { StoriesHash, State, useStorybookApi, isRoot } from '@storybook/api';
import { Icons, WithTooltip, TooltipMessage } from '@storybook/components';
import { ListItem } from './Tree/ListItem';
import { toFiltered, getMains, getParents } from './Tree/utils';
import { Tree } from './Tree/Tree';
@ -25,6 +26,8 @@ type BooleanSet = Record<string, boolean>;
type Item = StoriesHash[keyof StoriesHash];
type DataSet = Record<string, Item>;
type FilteredType = 'filtered' | 'unfiltered';
interface RefProps {
storyId: string;
filter: string;
@ -93,14 +96,87 @@ const Components = {
List: styled.div({}),
};
export const Ref: FunctionComponent<RefType & RefProps> = ({
stories,
id: key,
title = key,
storyId,
filter,
isHidden = false,
}) => {
const ProblemPlacement = styled.div({
position: 'absolute',
top: 0,
right: 10,
width: 20,
height: 20,
});
const getTitle = (ref: RefType) => {
switch (true) {
case !!ref.error: {
return 'an error occured';
}
case !!ref.startInjected: {
return 'auto injected';
}
default: {
return ref.title;
}
}
};
const getDescription = (ref: RefType) => {
switch (true) {
case !!ref.error: {
return <pre>{ref.error.toString()}</pre>;
}
case !!ref.startInjected: {
return (
<div>
<p>this storybook was auto-injected,</p>
<p>this is bad for performance, please do XYZ</p>
</div>
);
}
default: {
return (
<div>
this ref was loaded from <a href={ref.url}>{ref.url}</a>
</div>
);
}
}
};
const getIcon = (ref: RefType) => {
switch (true) {
case !!ref.error: {
return <Icons width="20" height="20" icon="alert" />;
}
case !!ref.startInjected: {
return <Icons width="20" height="20" icon="info" />;
}
default: {
return <Icons width="20" height="20" icon="ellipsis" />;
}
}
};
const RefIndicator: FunctionComponent<RefType> = ref => {
const title = getTitle(ref);
const description = getDescription(ref);
const icon = getIcon(ref);
return (
<ProblemPlacement>
<WithTooltip
placement="top"
trigger="hover"
tooltip={<TooltipMessage title={title} desc={description} />}
>
{icon}
</WithTooltip>
</ProblemPlacement>
);
};
const Wrapper = styled.div({
position: 'relative',
});
export const Ref: FunctionComponent<RefType & RefProps> = ref => {
const { stories, id: key, title = key, storyId, filter, isHidden = false } = ref;
const { dataSet, expandedSet, length, others, roots, setExpanded, selectedSet } = useDataset(
stories,
filter,
@ -118,7 +194,8 @@ export const Ref: FunctionComponent<RefType & RefProps> = ({
const isMain = key === 'storybook_internal';
return (
<div>
<Wrapper>
{!isMain ? <RefIndicator {...ref} /> : null}
<ExpanderContext.Provider value={combo}>
{!isMain ? <RefHead>{title}</RefHead> : null}
{isLoading ? (
@ -160,12 +237,10 @@ export const Ref: FunctionComponent<RefType & RefProps> = ({
</Fragment>
)}
</ExpanderContext.Provider>
</div>
</Wrapper>
);
};
type FilteredType = 'filtered' | 'unfiltered';
const useExpanded = (
type: FilteredType,
parents: Item[],
@ -231,14 +306,25 @@ const useFiltered = (dataset: DataSet, filter: string, parents: Item[], storyId:
};
const useDataset = (dataset: DataSet = {}, filter: string, storyId: string) => {
const emptyInitial = useMemo(
() => ({
filtered: {},
unfiltered: {},
}),
[]
);
const datasetKeys = Object.keys(dataset);
const initial = useMemo(() => {
return Object.keys(dataset).reduce(
(acc, k) => ({
filtered: { ...acc.filtered, [k]: true },
unfiltered: { ...acc.unfiltered, [k]: false },
}),
{ filtered: {} as BooleanSet, unfiltered: {} as BooleanSet }
);
if (datasetKeys.length) {
return Object.keys(dataset).reduce(
(acc, k) => ({
filtered: { ...acc.filtered, [k]: true },
unfiltered: { ...acc.unfiltered, [k]: false },
}),
{ filtered: {} as BooleanSet, unfiltered: {} as BooleanSet }
);
}
return emptyInitial;
}, [dataset]);
const type: FilteredType = filter.length >= 2 ? 'filtered' : 'unfiltered';