mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-07 23:22:10 +08:00
REMOVE loading of refs in node, load them in the browser always && ADD indicators to Sidebar
This commit is contained in:
parent
bb02da3f20
commit
331c08a27d
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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>;
|
||||
}
|
@ -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`)));
|
||||
|
@ -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';
|
||||
|
Loading…
x
Reference in New Issue
Block a user