Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encapsulate the store for each context on render #2

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 127 additions & 94 deletions packages/renderers/solid/src/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,129 @@ import type {
Args,
StoryFn,
} from '@storybook/types';
import type { ComponentsData, SolidRenderer, StoryContext } from './types';
import type { ContextStore, ComponentsData, SolidRenderer, StoryContext } from './types';

/**
* SolidJS store for handling fine grained updates
* of the components data as f.e. story args.
* encapsule store per context
*/
const [store, setStore] = createStore({} as ComponentsData);
const CONTEXT_STORES: {
[id: string]: ContextStore;
} = {};

function createContextStore(context: StoryContext<SolidRenderer, Args>): ContextStore {
/**
* SolidJS store for handling fine grained updates
* of the components data as f.e. story args.
*/
const [store, setStore] = createStore({} as ComponentsData);

let { globals, componentId, viewMode } = context;

/**
* Checks when the story requires to be remounted.
* Elements outside the story requires a whole re-render.
* e.g. dark theme, show grid, etc...
*/
const remount: ContextStore['remount'] = (force, context) => {
let flag = false;

// Story view mode has changed
if (viewMode !== context.viewMode) flag = true;

// Force flag is set to true.
if (force) flag = true;

// Globals refers to storybook visualization options.
if (!Object.is(globals, context.globals)) flag = true;

// Story main url id has changed
if (componentId !== context.componentId) {
flag = true;
unmountAll();
}

// Global values are updated when remount is true
if (flag === true) {
viewMode = context.viewMode;
globals = context.globals;
componentId = context.componentId;
}

return flag;
};

// Delay util fn
const delay = async (ms: number = 20) => {
await new Promise((resolve) => setTimeout(resolve, ms));
};
/**
* All stories are disposed.
*/
const disposeAllStories = () => {
Object.keys(store).forEach((storyId) => {
store[storyId]?.disposeFn?.();
});
};

// Global variables
let globals: StoryContext<SolidRenderer>['globals']; //Storybook view configurations.
let componentId: string; //Unique component story id.
let viewMode: string; //It can be story or docs.
/**
* Resets reactive store
*/
const cleanStore = () => {
setStore(reconcile({}));
};

/**
* Checks when the story requires to be remounted.
* Elements outside the story requires a whole re-render.
* e.g. dark theme, show grid, etc...
*/
const remount = (force: boolean, context: StoryContext<SolidRenderer>) => {
let flag = false;
/**
* Unmounts all the store and rendered solid apps
*/
const unmountAll = () => {
disposeAllStories();
cleanStore();
};

// Story view mode has changed
if (viewMode !== context.viewMode) flag = true;
/**
* Resets an specific story store.
*/
const cleanStoryStore = (storeId: string) => {
setStore({ [storeId]: { args: {}, rendered: false, disposeFn: () => {} } });
};

// Force flag is set to true.
if (force) flag = true;
/**
* Disposes an specific story.
*/
const disposeStory = (storeId: string) => {
store[storeId]?.disposeFn?.();
};

// Globals refers to storybook visualization options.
if (!Object.is(globals, context.globals)) flag = true;
/**
* This function resets the canvas and reactive store for an specific story.
*/
const remountStory = (storyId: string) => {
disposeStory(storyId);
cleanStoryStore(storyId);
};

// Story main url id has changed
if (componentId !== context.componentId) {
flag = true;
unmountAll();
}
/**
* Checks if the story store exists
*/
const storyIsRendered = (storyId: string) => Boolean(store[storyId]?.rendered);

return {
store,
setStore,
remount,
remountStory,
storyIsRendered,
};
}

// Global values are updated when remount is true
if (flag === true) {
viewMode = context.viewMode;
globals = context.globals;
componentId = context.componentId;
}
function contextStore(context: StoryContext<SolidRenderer, Args>): ContextStore {
return (CONTEXT_STORES[context.componentId] ||= createContextStore(context));
}

// prev story disposeFn list
const STORY_DISPOSERS: {
[id: string]: (() => void) | undefined;
} = {};

return flag;
// Delay util fn
const delay = async (ms: number = 20) => {
await new Promise((resolve) => setTimeout(resolve, ms));
};

/**
Expand All @@ -67,6 +141,7 @@ export const solidReactivityDecorator = (
storyFn: StoryFn<SolidRenderer, Args>,
context: StoryContext<SolidRenderer>
) => {
const { store } = contextStore(context);
let storyId = context.canvasElement.id;
context.args = store[storyId].args;
return storyFn(context.args as Args & StoryContext<SolidRenderer>, context);
Expand Down Expand Up @@ -97,57 +172,6 @@ export const render: ArgsStoryFn<SolidRenderer> = (_, context) => {
return <Component {...context.args} />;
};

/**
* All stories are disposed.
*/
let disposeAllStories = () => {
Object.keys(store).forEach((storyId) => {
store[storyId]?.disposeFn?.();
});
};

/**
* Resets reactive store
*/
const cleanStore = () => {
setStore(reconcile({}));
};

/**
* Unmounts all the store and rendered solid apps
*/
const unmountAll = () => {
disposeAllStories();
cleanStore();
};

/**
* Resets an specific story store.
*/
const cleanStoryStore = (storeId: string) => {
setStore({ [storeId]: { args: {}, rendered: false, disposeFn: () => {} } });
};

/**
* Disposes an specific story.
*/
const disposeStory = (storeId: string) => {
store[storeId]?.disposeFn?.();
};

/**
* This function resets the canvas and reactive store for an specific story.
*/
const remountStory = (storyId: string) => {
disposeStory(storyId);
cleanStoryStore(storyId);
};

/**
* Checks if the story store exists
*/
const storyIsRendered = (storyId: string) => Boolean(store[storyId]?.rendered);

/**
* Checks if the story is in docs mode.
*/
Expand All @@ -160,7 +184,8 @@ const isDocsMode = (context: StoryContext<SolidRenderer, Args>) =>
const renderSolidApp = (
storyId: string,
renderContext: RenderContext<SolidRenderer>,
canvasElement: SolidRenderer['canvasElement']
canvasElement: SolidRenderer['canvasElement'],
setStore: ContextStore['setStore'],
) => {
const { storyContext, unboundStoryFn, showMain, showException } =
renderContext;
Expand Down Expand Up @@ -201,10 +226,17 @@ export async function renderToCanvas(
let forceRemount = renderContext.forceRemount;
let storyId = storyContext.canvasElement.id;

// Initializes global default values for checking remounting.
if (viewMode === undefined) viewMode = storyContext.viewMode;
if (globals === undefined) globals = storyContext.globals;
if (componentId === undefined) componentId = storyContext.componentId;
// dispose every story except current story
const contextStoryId = [storyContext.id, storyId].join('/');
Object.keys(STORY_DISPOSERS).forEach((key) => {
if (key !== contextStoryId) {
STORY_DISPOSERS[key]?.();
delete STORY_DISPOSERS[key];
}
});

// Initializes
const { setStore, remount, remountStory, storyIsRendered } = contextStore(storyContext);

// Story is remounted given the conditions.
if (remount(forceRemount, storyContext)) {
Expand All @@ -220,7 +252,8 @@ export async function renderToCanvas(
// for rendering all the stories in docs mode when global changes.
if (isDocsMode(storyContext)) await delay();

const disposeFn = renderSolidApp(storyId, renderContext, canvasElement);
const disposeFn = renderSolidApp(storyId, renderContext, canvasElement, setStore);
setStore(storyId, (prev) => ({ ...prev, disposeFn }));
STORY_DISPOSERS[contextStoryId] = disposeFn;
}
}
11 changes: 10 additions & 1 deletion packages/renderers/solid/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Component, JSXElement } from 'solid-js';
import type { Args, WebRenderer } from '@storybook/types';
import type { SetStoreFunction } from 'solid-js/store';
import type { Args, StoryContext, WebRenderer } from '@storybook/types';

export type { RenderContext } from '@storybook/types';
export type { StoryContext } from '@storybook/types';
Expand All @@ -22,3 +23,11 @@ export type StoryFnSolidReturnType = JSXElement;
export type ComponentsData = {
[key: string]: { args: Args; rendered?: Boolean; disposeFn?: () => void };
};

export interface ContextStore {
readonly store: ComponentsData;
readonly setStore: SetStoreFunction<ComponentsData>;
remount(force: boolean, context: StoryContext<SolidRenderer>): boolean;
remountStory(storyId: string): void;
storyIsRendered(storyId: string): boolean;
}