Skip to content

Commit

Permalink
fix: synchronize story store between packages
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed Apr 11, 2020
1 parent 69f5762 commit d5ac9c8
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 45 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@

# Motivation

The `component-controls` package aims to facilitate designing, developing and testing of components.
This library is initially created by the technical co-founder of [AutomatedQA](http://automatedqa.com) (remnamed to SmartBear), creator of [AQtime](https://smartbear.com/product/aqtime-pro/overview/), [TestComplete](https://smartbear.com/product/testcomplete/overview/), with 20+ years experience creating software development lifecyle products. So it is to be expected the library will have a heavy testing/QA bias.

-
- Create a components development environment, with testing as a first-class feature.
- Decouple the user interface from loading of the 'stories' = modular design.
- Do not modify the source files at instrumentation time as much as possible to avoid random build/run-time errors. Exception only where absolutely necessary, ie instrumenting coverage or performance profiling probes.
- Built-in [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) instrumentation module.
- Ability to integrate with various user-interface shells - [storybook](https://storybook.js.org), [gatsby](https://www.gatsbyjs.org), [nextjs](https://nextjs.org), standalone.
- Create a community where being 'nice' is the norm and credit is given to all contributors.

# Inspiration

> If I have seen further than others, it is by standing upon the shoulders of giants.

There are many developments that have contribiuted to the creation of `component-controls`, and here we will list but a few of them:

- [storybook](https://storybook.js.org) is the original system that helps teams to design, develop and test components. The strong support for testing and the creation of an open [Component Story Format](https://github.com/storybookjs/csf) were an inspiration, as well as the [Storybook Addon Knobs](https://github.com/storybookjs/storybook/tree/next/addons/knobs) for providing configurable component properties.
Expand Down
19 changes: 11 additions & 8 deletions core/store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export interface StoryStore {
const UPDATE_STORY_MSG = 'component_controls_update_story';
const COMPONENT_CONTROLS_STORAGE = 'component-controls-store-data';

interface MessageType {
storyId: string;
moduleId: number;
}
class Store implements StoryStore {
loadedStore: StoriesStore | undefined;
channel: BroadcastChannel;
Expand All @@ -29,9 +33,9 @@ class Store implements StoryStore {
constructor(store?: StoriesStore) {
this.moduleId = Math.random();
this.loadedStore = store;
this.channel = new BroadcastChannel(UPDATE_STORY_MSG);
this.channel = new BroadcastChannel<MessageType>(UPDATE_STORY_MSG);
this.observers = [];
this.channel.onmessage = ({ storyId, moduleId }) => {
this.channel.onmessage = ({ storyId, moduleId }: MessageType) => {
if (storyId && moduleId) {
console.log(
'ON MESSAGE',
Expand Down Expand Up @@ -59,6 +63,7 @@ class Store implements StoryStore {
};

setStore = (store?: StoriesStore) => {
console.log('SET STORE');
this.loadedStore = store;
this.notifyObservers();
};
Expand All @@ -67,16 +72,15 @@ class Store implements StoryStore {
if (data) {
try {
const newStore = JSON.parse(data) as StoriesStore;

if (this.loadedStore && storyId) {
this.loadedStore.stories[storyId] = {
...newStore.stories[storyId],
renderFn: this.loadedStore.stories[storyId].renderFn,
...this.loadedStore.stories[storyId],
controls: { ...newStore.stories[storyId].controls },
};
} else {
console.log('OVERRIDE DATA');
this.loadedStore = newStore;
}
this.loadedStore = newStore;
} catch (e) {}
}
};
Expand Down Expand Up @@ -105,8 +109,7 @@ class Store implements StoryStore {
COMPONENT_CONTROLS_STORAGE,
JSON.stringify(this.loadedStore),
);
const message = { storyId, moduleId: this.moduleId };
console.log('POST MESSAGE', message);
const message: MessageType = { storyId, moduleId: this.moduleId };
this.channel.postMessage(message);
}
return this.loadedStore;
Expand Down
9 changes: 8 additions & 1 deletion integrations/storybook/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { addDecorator } from '@storybook/client-api';
import { makeDecorator } from '@storybook/addons';
import addons, { makeDecorator } from '@storybook/addons';
import { FORCE_RE_RENDER } from '@storybook/core-events';

import { store } from '@component-controls/store';
import { getControlValues } from '@component-controls/core';
import './context/RenderDocsPages';

store.addObserver(() => {
addons.getChannel().emit(FORCE_RE_RENDER);
});

addDecorator(
makeDecorator({
name: 'componnet-controls',
parameterName: 'controls',
wrapper: (storyFn, context) => {
const story = store.getStory(context.id);

const values =
story && story.controls ? getControlValues(story.controls) : undefined;
//@ts-ignore
Expand Down
9 changes: 1 addition & 8 deletions integrations/storybook/src/context/BlockContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import React from 'react';
import addons from '@storybook/addons';
import { FORCE_RE_RENDER } from '@storybook/core-events';
import { BlockContextProvider as BlocksContextProvider } from '@component-controls/blocks';
import { DocsContext } from '@storybook/addon-docs/blocks';

Expand All @@ -18,12 +16,7 @@ export const BlockContextProvider: React.FC<BlockContextProviderProps> = ({
} else {
storyId = id;
}
const channel = React.useMemo(() => addons.getChannel(), []);
const onRefresh = () => channel.emit(FORCE_RE_RENDER);
// this._channel.emit(Events.FORCE_RE_RENDER);
return (
<BlocksContextProvider storyId={storyId} onRefresh={onRefresh}>
{children}
</BlocksContextProvider>
<BlocksContextProvider storyId={storyId}>{children}</BlocksContextProvider>
);
};
4 changes: 2 additions & 2 deletions integrations/storybook/src/page/DocsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Subtitle,
Story,
Playground,
Stories,
// Stories,
Description,
ComponentSource,
PropsTable,
Expand All @@ -23,7 +23,7 @@ export const DocsPage: FC = () => {
</Playground>
<ControlsTable id="." />
<PropsTable of="." />
<Stories dark={true} />
{/* <Stories dark={true} /> */}
</>
);
};
16 changes: 11 additions & 5 deletions integrations/storybook/src/panel/ControlsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { BlockContextProvider } from '@component-controls/blocks';
import { API } from '@storybook/api';
import { FORCE_RE_RENDER } from '@storybook/core-events';
import { SET_CURRENT_STORY } from '@storybook/core-events';
import { ControlsTable } from '../blocks/ControlsTable';

export interface ControlsPanelProps {
Expand All @@ -12,12 +12,18 @@ export const ControlsPanel: React.FC<ControlsPanelProps> = ({
active,
api,
}) => {
const [storyId, setStoryId] = React.useState('');
const channel = React.useMemo(() => api.getChannel(), []);
React.useEffect(() => {
const onChangeStory = (props: any) => {
setStoryId(props.storyId);
};
channel.on(SET_CURRENT_STORY, onChangeStory);
return () => channel.off(SET_CURRENT_STORY, onChangeStory);
});

return active ? (
<BlockContextProvider
storyId="storybook-controls--text-default-prop"
onRefresh={() => channel.emit(FORCE_RE_RENDER)}
>
<BlockContextProvider storyId={storyId}>
<ControlsTable id="." />
</BlockContextProvider>
) : null;
Expand Down
33 changes: 17 additions & 16 deletions ui/blocks/src/context/block/BlockContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ export interface BlockContextInputProps {
* store mockup when running tests
*/
mockStore?: StoriesStore;
/**
* optional cllabel to invoke when the story data are changed
* for example when controls values are updated
*/
onRefresh?: () => void;
}

export interface Components {
Expand Down Expand Up @@ -80,26 +75,35 @@ export const BlockContextProvider: React.FC<BlockContextInputProps> = ({
children,
storyId,
mockStore,
onRefresh,
}) => {
const store = mockStore || storyStore.getStore();
const [story, setStory] = useState<Story | undefined>(
store ? store.stories[storyId] : undefined,
);
const [story, setStory] = useState<{ story?: Story; id: string }>({
story: store ? store.stories[storyId] : undefined,
id: storyId,
});

const refreshData = () => {
setStory(store ? { ...store.stories[storyId] } : undefined);
setStory({
story: store ? { ...store.stories[storyId] } : undefined,
id: storyId,
});
};
useEffect(() => {
const onChange = () => refreshData();
const onChange = (id?: string) => {
if (id === undefined || storyId === id) {
refreshData();
}
};
storyStore.addObserver(onChange);
return () => {
storyStore.removeObserver(onChange);
};
}, []);

useEffect(() => {
refreshData();
if (story.id !== storyId) {
refreshData();
}
}, [storyId, store]);

/**
Expand Down Expand Up @@ -166,9 +170,6 @@ export const BlockContextProvider: React.FC<BlockContextInputProps> = ({
const newValues = mergeControlValues(controls, propName, propValue);
storyStore.updateStoryProp(storyId, 'controls', newValues);
refreshData();
if (onRefresh) {
onRefresh();
}
}
};
const clickControl: ClickControlFn = (storyId: string, propName: string) => {
Expand All @@ -186,7 +187,7 @@ export const BlockContextProvider: React.FC<BlockContextInputProps> = ({
return (
<BlockContext.Provider
value={{
story,
story: story.story,
setControlValue,
clickControl,
getStory,
Expand Down
19 changes: 19 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9331,6 +9331,11 @@ ignore@^5.1.1:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==

immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=

[email protected]:
version "1.10.0"
resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
Expand Down Expand Up @@ -10883,6 +10888,13 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"

[email protected]:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
dependencies:
immediate "~3.0.5"

lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
Expand Down Expand Up @@ -11005,6 +11017,13 @@ loader-utils@^2.0.0:
emojis-list "^3.0.0"
json5 "^2.1.2"

localforage@^1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.3.tgz#0082b3ca9734679e1bd534995bdd3b24cf10f204"
integrity sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==
dependencies:
lie "3.1.1"

locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
Expand Down

0 comments on commit d5ac9c8

Please sign in to comment.