Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
ndelangen committed May 25, 2022
1 parent 7c5b878 commit ec41d93
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 273 deletions.
6 changes: 3 additions & 3 deletions lib/addons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
},
"license": "MIT",
"sideEffects": false,
"main": "dist/cjs/public_api.js",
"module": "dist/esm/public_api.js",
"types": "dist/types/public_api.d.ts",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/types/index.d.ts",
"files": [
"dist/**/*",
"README.md",
Expand Down
164 changes: 10 additions & 154 deletions lib/addons/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,158 +1,14 @@
import global from 'global';
import type { ReactElement } from 'react';
import { Channel } from '@storybook/channels';
import type { API } from '@storybook/api';
import type { RenderData as RouterData } from '@storybook/router';
import { logger } from '@storybook/client-logger';
import type { ThemeVars } from '@storybook/theming';
import { mockChannel } from './storybook-channel-mock';
import { types, Types } from './types';

export { Channel };

export interface RenderOptions {
active?: boolean;
key?: string;
}

export interface Addon {
title: (() => string) | string;
type?: Types;
id?: string;
route?: (routeOptions: RouterData) => string;
match?: (matchOptions: RouterData) => boolean;
render: (renderOptions: RenderOptions) => ReactElement<any>;
paramKey?: string;
disabled?: boolean;
hidden?: boolean;
}

export type Loader = (api: API) => void;

interface Loaders {
[key: string]: Loader;
}
export interface Collection {
[key: string]: Addon;
}
interface Elements {
[key: string]: Collection;
}
interface ToolbarConfig {
hidden?: boolean;
}
export interface Config {
theme?: ThemeVars;
toolbar?: {
[id: string]: ToolbarConfig;
};
[key: string]: any;
}

export class AddonStore {
constructor() {
this.promise = new Promise((res) => {
this.resolve = () => res(this.getChannel());
}) as Promise<Channel>;
}

private loaders: Loaders = {};

private elements: Elements = {};

private config: Config = {};

private channel: Channel | undefined;

private serverChannel: Channel | undefined;

private promise: any;

private resolve: any;

getChannel = (): Channel => {
// this.channel should get overwritten by setChannel. If it wasn't called (e.g. in non-browser environment), set a mock instead.
if (!this.channel) {
this.setChannel(mockChannel());
}

return this.channel;
};

getServerChannel = (): Channel => {
if (!this.serverChannel) {
throw new Error('Accessing non-existent serverChannel');
}

return this.serverChannel;
};

ready = (): Promise<Channel> => this.promise;

hasChannel = (): boolean => !!this.channel;

hasServerChannel = (): boolean => !!this.serverChannel;

setChannel = (channel: Channel): void => {
this.channel = channel;
this.resolve();
};

setServerChannel = (channel: Channel): void => {
this.serverChannel = channel;
};

getElements = (type: Types): Collection => {
if (!this.elements[type]) {
this.elements[type] = {};
}
return this.elements[type];
};

addPanel = (name: string, options: Addon): void => {
this.add(name, {
type: types.PANEL,
...options,
});
};

add = (name: string, addon: Addon) => {
const { type } = addon;
const collection = this.getElements(type);
collection[name] = { id: name, ...addon };
};

setConfig = (value: Config) => {
Object.assign(this.config, value);
};

getConfig = () => this.config;

register = (name: string, registerCallback: (api: API) => void): void => {
if (this.loaders[name]) {
logger.warn(`${name} was loaded twice, this could have bad side-effects`);
}
this.loaders[name] = registerCallback;
};

loadAddons = (api: any) => {
Object.values(this.loaders).forEach((value) => value(api));
};
}

// Enforce addons store to be a singleton
const KEY = '__STORYBOOK_ADDONS';

function getAddonsStore(): AddonStore {
if (!global[KEY]) {
global[KEY] = new AddonStore();
}
return global[KEY];
}

// There can only be 1 default export per entry point and it has to be directly from index
// Exporting this twice in order to to be able to import it like { addons } instead of 'addons'
// prefer import { addons } from '@storybook/addons' over import addons from '@storybook/addons'
//
// See public_api.ts
// See main.ts
import { addons } from './main';

export * from './make-decorator';
export * from './main';
export * from './types';
export * from './storybook-channel-mock';
export * from './hooks';

export const addons = getAddonsStore();
export default addons;
158 changes: 158 additions & 0 deletions lib/addons/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import global from 'global';
import type { ReactElement } from 'react';
import { Channel } from '@storybook/channels';
import type { API } from '@storybook/api';
import type { RenderData as RouterData } from '@storybook/router';
import { logger } from '@storybook/client-logger';
import type { ThemeVars } from '@storybook/theming';
import { mockChannel } from './storybook-channel-mock';
import { types, Types } from './types';

export { Channel };

export interface RenderOptions {
active?: boolean;
key?: string;
}

export interface Addon {
title: (() => string) | string;
type?: Types;
id?: string;
route?: (routeOptions: RouterData) => string;
match?: (matchOptions: RouterData) => boolean;
render: (renderOptions: RenderOptions) => ReactElement<any>;
paramKey?: string;
disabled?: boolean;
hidden?: boolean;
}

export type Loader = (api: API) => void;

interface Loaders {
[key: string]: Loader;
}
export interface Collection {
[key: string]: Addon;
}
interface Elements {
[key: string]: Collection;
}
interface ToolbarConfig {
hidden?: boolean;
}
export interface Config {
theme?: ThemeVars;
toolbar?: {
[id: string]: ToolbarConfig;
};
[key: string]: any;
}

export class AddonStore {
constructor() {
this.promise = new Promise((res) => {
this.resolve = () => res(this.getChannel());
}) as Promise<Channel>;
}

private loaders: Loaders = {};

private elements: Elements = {};

private config: Config = {};

private channel: Channel | undefined;

private serverChannel: Channel | undefined;

private promise: any;

private resolve: any;

getChannel = (): Channel => {
// this.channel should get overwritten by setChannel. If it wasn't called (e.g. in non-browser environment), set a mock instead.
if (!this.channel) {
this.setChannel(mockChannel());
}

return this.channel;
};

getServerChannel = (): Channel => {
if (!this.serverChannel) {
throw new Error('Accessing non-existent serverChannel');
}

return this.serverChannel;
};

ready = (): Promise<Channel> => this.promise;

hasChannel = (): boolean => !!this.channel;

hasServerChannel = (): boolean => !!this.serverChannel;

setChannel = (channel: Channel): void => {
this.channel = channel;
this.resolve();
};

setServerChannel = (channel: Channel): void => {
this.serverChannel = channel;
};

getElements = (type: Types): Collection => {
if (!this.elements[type]) {
this.elements[type] = {};
}
return this.elements[type];
};

addPanel = (name: string, options: Addon): void => {
this.add(name, {
type: types.PANEL,
...options,
});
};

add = (name: string, addon: Addon) => {
const { type } = addon;
const collection = this.getElements(type);
collection[name] = { id: name, ...addon };
};

setConfig = (value: Config) => {
Object.assign(this.config, value);
};

getConfig = () => this.config;

register = (name: string, registerCallback: (api: API) => void): void => {
if (this.loaders[name]) {
logger.warn(`${name} was loaded twice, this could have bad side-effects`);
}
this.loaders[name] = registerCallback;
};

loadAddons = (api: any) => {
Object.values(this.loaders).forEach((value) => value(api));
};
}

// Enforce addons store to be a singleton
const KEY = '__STORYBOOK_ADDONS';

function getAddonsStore(): AddonStore {
if (!global[KEY]) {
global[KEY] = new AddonStore();
}
return global[KEY];
}

// Exporting this twice in order to to be able to import it like { addons } instead of 'addons'
// prefer import { addons } from '@storybook/addons' over import addons from '@storybook/addons'
//
// See public_api.ts

export const addons = getAddonsStore();
14 changes: 0 additions & 14 deletions lib/addons/src/public_api.ts

This file was deleted.

4 changes: 3 additions & 1 deletion lib/api/shortcut.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './dist/types/lib/shortcut.d';
export type KeyCollection = string[];

export function shortcutToHumanString(shortcut: KeyCollection): string
1 change: 0 additions & 1 deletion lib/ui/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"extends": "../../tsconfig.json",
"baseUrl": ".",
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"]
},
Expand Down
10 changes: 9 additions & 1 deletion scripts/bundle-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ async function removeDist() {
await fs.remove('dist');
}

async function mapper() {
await fs.emptyDir(path.join(process.cwd(), 'dist', 'types'));
await fs.writeFile(
path.join(process.cwd(), 'dist', 'types', 'index.d.ts'),
`export * from '../../src/index';`
);
}

async function build(options: Options) {
const { input, externals, cwd, optimized } = options;
const setting: RollupOptions = {
Expand Down Expand Up @@ -174,7 +182,7 @@ export async function run({ cwd, flags }: { cwd: string; flags: string[] }) {
await Promise.all([
//
build(options),
...(options.optimized ? [dts(options)] : []),
...(options.optimized ? [dts(options)] : [mapper()]),
]);

console.timeEnd(message);
Expand Down
Loading

0 comments on commit ec41d93

Please sign in to comment.