Skip to content

Commit

Permalink
feat: add event for data communicate (#62)
Browse files Browse the repository at this point in the history
* feat: add context for  data communication

* chore: changeset

* wip: event

* feat: event for data communicate

* chore: add changeset

* chore: update changesets
  • Loading branch information
lvisei authored Dec 23, 2024
1 parent 3a20fc2 commit e333060
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/slow-drinks-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@antv/gpt-vis': minor
---

feat: support event for data communicate
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@ant-design/graphs": "^2.0.2",
"@ant-design/icons": "^5.5.2",
"@ant-design/plots": "^2.3.2",
"@antv/event-emitter": "^0.1.3",
"@antv/l7": "^2.22.3",
"@antv/larkmap": "^1.5.1",
"@babel/runtime": "^7.26.0",
Expand Down
52 changes: 42 additions & 10 deletions src/GPTVis/Lite.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,67 @@
import React, { memo } from 'react';
import EventEmitter from '@antv/event-emitter';
import React, { memo, useEffect, useMemo } from 'react';
import type { Options } from 'react-markdown';
import Markdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import { GPTVisContext } from './hooks/useContext';
import { useEventPublish } from './hooks/useEvent';

export interface GPTVisLiteProps extends Options {
/** 自定义 markdown components样式 */
/**
* 自定义 markdown components
*/
components?:
| Options['components']
| {
[key: string]: (props: any) => React.ReactNode;
};
/**
* 🧪 订阅组件事件,实验性属性
* 用于子组件与容器组件通信
*/
eventSubs?: Record<string, (data?: any) => void>;
}

const GPTVisLite: React.FC<GPTVisLiteProps> = ({
children,
components,
rehypePlugins,
remarkPlugins,
eventSubs,
...rest
}) => {
const eventBus = useMemo(() => new EventEmitter(), []);
const contextValue = useMemo(() => ({ eventBus }), [eventBus]);

useEffect(() => {
if (eventSubs) {
const events = Object.keys(eventSubs);
for (const eventName of events) {
eventBus.on(eventName, eventSubs[eventName]);
}
return () => {
for (const eventName of events) {
eventBus.off(eventName, eventSubs[eventName]);
}
};
}
}, [eventBus, eventSubs]);

return (
<Markdown
components={components}
rehypePlugins={[rehypeRaw, ...(rehypePlugins ? rehypePlugins : [])]}
remarkPlugins={[remarkGfm, ...(remarkPlugins ? remarkPlugins : [])]}
{...rest}
>
{children}
</Markdown>
<GPTVisContext.Provider value={contextValue}>
<Markdown
components={components}
rehypePlugins={[rehypeRaw, ...(rehypePlugins ? rehypePlugins : [])]}
remarkPlugins={[remarkGfm, ...(remarkPlugins ? remarkPlugins : [])]}
{...rest}
>
{children}
</Markdown>
</GPTVisContext.Provider>
);
};

export { useEventPublish };

export default memo(GPTVisLite);
68 changes: 68 additions & 0 deletions src/GPTVis/demos/context-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { CodeBlockComponent } from '@antv/gpt-vis';
import { GPTVisLite, withChartCode } from '@antv/gpt-vis';
import React, { useCallback, useMemo, useState } from 'react';

export const MyContext = React.createContext(null as any);

export function useMyContext() {
const context = React.useContext(MyContext);
if (context === undefined || Object.keys(context).length === 0) {
throw new Error(`useMyContext must be used within a MyContext.Provider`);
}

return context;
}

/**
* 自定义代码块渲染器
*/
const MyUIRenderer: CodeBlockComponent = ({ children }) => {
const context = useMyContext();
console.log('context: ', context);
return (
<div style={{ backgroundColor: '#f0f0f0', padding: '10px' }}>
<p>{children}</p>
<button onClick={context?.onClick} type="button">
click
</button>
</div>
);
};
const customRenderers = { 'my-ui': MyUIRenderer };
const components = {
code: withChartCode({
languageRenderers: customRenderers, // register custom block renderer
components: {},
}),
};

const content = `
\`\`\`my-ui
my ui data ...
\`\`\`
`;

export default () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('handleClick');
setCount((pre) => pre + 1);
// do something
}, []);
const context = useMemo(() => ({ count: count, onClick: handleClick }), [count]);

return (
<>
<p>count: {count}</p>
<MyContext.Provider value={context}>
<div>
{/* other component ... */}
<div>
{/* other component ... */}
<GPTVisLite components={components}>{content}</GPTVisLite>
</div>
</div>
</MyContext.Provider>
</>
);
};
53 changes: 53 additions & 0 deletions src/GPTVis/demos/event.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { CodeBlockComponent } from '@antv/gpt-vis';
import { GPTVisLite, useEventPublish, withChartCode } from '@antv/gpt-vis';
import React, { useState } from 'react';

/**
* 自定义代码块渲染器
*/
const MyUIRenderer: CodeBlockComponent = ({ children }) => {
const eventPublish = useEventPublish();
return (
<div style={{ backgroundColor: '#f0f0f0', padding: '10px' }}>
<p>{children}</p>
<button
onClick={() => {
eventPublish('xxxclick', {});
}}
type="button"
>
click
</button>
</div>
);
};
const customRenderers = { 'my-ui': MyUIRenderer };
const components = {
code: withChartCode({
languageRenderers: customRenderers,
components: {},
}),
};

const content = `
\`\`\`my-ui
my ui data ...
\`\`\`
`;
export default () => {
const [count, setCount] = useState(0);
const onClick = (data: any) => {
console.log('data: ', data);
setCount((pre) => pre + 1);
// do something
};

return (
<>
<p>count: {count}</p>
<GPTVisLite eventSubs={{ xxxclick: onClick }} components={components}>
{content}
</GPTVisLite>
</>
);
};
17 changes: 17 additions & 0 deletions src/GPTVis/hooks/useContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type EventEmitter from '@antv/event-emitter';
import React from 'react';

type GPTVisContextValue = {
eventBus: EventEmitter;
};

export const GPTVisContext = React.createContext<GPTVisContextValue>(null as any);

export function useGPTVisContext<T = GPTVisContextValue>() {
const context = React.useContext(GPTVisContext);
if (context === undefined || Object.keys(context).length === 0) {
throw new Error(`useGPTVisContext must be used within a GPTVisContext.Provider`);
}

return context as T;
}
7 changes: 7 additions & 0 deletions src/GPTVis/hooks/useEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useGPTVisContext } from './useContext';

export const useEventPublish = () => {
const { eventBus } = useGPTVisContext();

return eventBus.emit.bind(eventBus);
};
8 changes: 8 additions & 0 deletions src/GPTVis/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ group:
order: 10
title: 其他
toc: content
demo: { cols: 2 }
---

# GPTVis 协议渲染器
Expand All @@ -26,6 +27,13 @@ GPTVis 协议的 Markdown 渲染器,基于 Markdown 语法扩展 `vis-chart`

<code src="./demos/code"></code>

## 容器组件通信

通过发布订阅组件事件与 Context 传递数据,来用于子组件与容器组件通信。

<code src="./demos/event">订阅组件事件</code>
<code src="./demos/context-provider">Context 传递数据</code>

## API

继承 [react-markdown](https://github.com/remarkjs/react-markdown#options) 组件全部属性。
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export { withChartCode, withDefaultChartCode } from './ChartCodeRender';
export type { CodeBlockComponent, WithChartCodeOptions } from './ChartCodeRender/type';
export { default as ConfigProvider, type ConfigProviderProps } from './ConfigProvider';
export { default as GPTVis, type GPTVisProps } from './GPTVis';
export { default as GPTVisLite, type GPTVisLiteProps } from './GPTVis/Lite';
export { default as GPTVisLite, useEventPublish, type GPTVisLiteProps } from './GPTVis/Lite';

export { default as version } from './version';

0 comments on commit e333060

Please sign in to comment.