@perspective-dev/react 4.0.1 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.d.ts +2 -21
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +4 -4
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/viewer.d.ts +24 -0
- package/dist/esm/workspace.d.ts +22 -0
- package/package.json +2 -1
- package/src/index.tsx +2 -84
- package/src/utils.tsx +27 -0
- package/src/viewer.tsx +86 -0
- package/src/workspace.tsx +94 -0
- package/test/js/basic.story.tsx +11 -7
- package/test/js/index.css +14 -0
- package/test/js/react.spec.tsx +62 -2
- package/test/js/workspace.story.tsx +154 -0
package/dist/esm/index.d.ts
CHANGED
|
@@ -7,24 +7,5 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import type * as pspViewer from "@perspective-dev/viewer";
|
|
13
|
-
export interface PerspectiveViewerProps {
|
|
14
|
-
table?: psp.Table | Promise<psp.Table>;
|
|
15
|
-
config?: pspViewer.ViewerConfigUpdate;
|
|
16
|
-
onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
|
|
17
|
-
onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
|
|
18
|
-
onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
|
|
19
|
-
className?: string | undefined;
|
|
20
|
-
hidden?: boolean | undefined;
|
|
21
|
-
id?: string | undefined;
|
|
22
|
-
slot?: string | undefined;
|
|
23
|
-
style?: React.CSSProperties | undefined;
|
|
24
|
-
tabIndex?: number | undefined;
|
|
25
|
-
title?: string | undefined;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* A React wrapper component for `<perspective-viewer>` Custom Element.
|
|
29
|
-
*/
|
|
30
|
-
export declare const PerspectiveViewer: React.FC<PerspectiveViewerProps>;
|
|
10
|
+
export * from "./viewer";
|
|
11
|
+
export * from "./workspace";
|
package/dist/esm/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as o from"react";import*as d from"react";function n(e,t,s){d.useEffect(()=>{if(!s||!e)return;let p=new AbortController,c=a=>s(a.detail);return e?.addEventListener(t,c,{signal:p.signal}),()=>p.abort()},[e,s])}import{jsx as g}from"react/jsx-runtime";function m(e){let[t,s]=o.useState(null);return o.useEffect(()=>()=>{t?.delete()},[t]),o.useEffect(()=>{e.client?t?.load(e.client):t?.eject()},[t,e.client]),o.useEffect(()=>{e.client&&e.config&&t?.restore(e.config)},[t,e.client,JSON.stringify(e.config)]),n(t,"perspective-click",e.onClick),n(t,"perspective-select",e.onSelect),n(t,"perspective-config-update",e.onConfigUpdate),g("perspective-viewer",{ref:s,id:e.id,className:e.className,hidden:e.hidden,slot:e.slot,style:e.style,tabIndex:e.tabIndex,title:e.title})}var C=o.memo(m);import*as r from"react";import{jsx as k}from"react/jsx-runtime";var P=r.forwardRef(({client:e,layout:t,onLayoutUpdate:s,onNewView:p,onToggleGlobalFilter:c,...a},f)=>{let[i,v]=r.useState();return r.useImperativeHandle(f,()=>i,[i]),r.useEffect(()=>{i&&t&&i.restore(t)},[i,t]),r.useEffect(()=>{i&&e&&i.load(e)},[i,e]),n(i,"workspace-new-view",p),n(i,"workspace-layout-update",s?({layout:l})=>i?.save().then(u=>s(u)):void 0),n(i,"workspace-toggle-global-filter",c),k("perspective-workspace",{ref:l=>v(l??void 0),...a})}),W=r.memo(P);export{C as PerspectiveViewer,W as PerspectiveWorkspace};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/
|
|
4
|
-
"sourcesContent": ["// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\n/**\n *\n * # See Also\n *\n * [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)\n * project from the Perspective GitHub repo.\n *\n * @module\n */\n\nimport * as React from \"react\";\nimport type * as psp from \"@perspective-dev/client\";\nimport type * as pspViewer from \"@perspective-dev/viewer\";\n\nfunction usePspListener<A>(\n viewer: HTMLElement | null,\n name: string,\n f?: (x: A) => void,\n) {\n React.useEffect(() => {\n if (!f) return;\n const ctx = new AbortController();\n const callback = (e: Event) => f((e as CustomEvent).detail);\n viewer?.addEventListener(name, callback, { signal: ctx.signal });\n return () => ctx.abort();\n }, [viewer, f]);\n}\n\nexport interface PerspectiveViewerProps {\n table?: psp.Table | Promise<psp.Table>;\n config?: pspViewer.ViewerConfigUpdate;\n onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;\n onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;\n onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;\n\n // Applicable props from `React.HTMLAttributes`, which we cannot extend\n // directly because Perspective changes the signature of `onClick`.\n className?: string | undefined;\n hidden?: boolean | undefined;\n id?: string | undefined;\n slot?: string | undefined;\n style?: React.CSSProperties | undefined;\n tabIndex?: number | undefined;\n title?: string | undefined;\n}\n\nfunction PerspectiveViewerImpl(props: PerspectiveViewerProps) {\n const [viewer, setViewer] =\n React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);\n\n React.useEffect(() => {\n return () => {\n viewer?.delete();\n };\n }, [viewer]);\n\n React.useEffect(() => {\n if (props.table) {\n viewer?.load(props.table);\n } else {\n viewer?.eject();\n }\n }, [viewer, props.table]);\n\n React.useEffect(() => {\n if (props.table && props.config) {\n viewer?.restore(props.config);\n }\n }, [viewer, props.table, JSON.stringify(props.config)]);\n\n usePspListener(viewer, \"perspective-click\", props.onClick);\n usePspListener(viewer, \"perspective-select\", props.onSelect);\n usePspListener(viewer, \"perspective-config-update\", props.onConfigUpdate);\n\n return (\n <perspective-viewer\n ref={setViewer}\n id={props.id}\n className={props.className}\n hidden={props.hidden}\n slot={props.slot}\n style={props.style}\n tabIndex={props.tabIndex}\n title={props.title}\n />\n );\n}\n\n/**\n * A React wrapper component for `<perspective-viewer>` Custom Element.\n */\nexport const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(\n PerspectiveViewerImpl,\n);\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": ["React", "
|
|
3
|
+
"sources": ["../../src/viewer.tsx", "../../src/utils.tsx", "../../src/workspace.tsx"],
|
|
4
|
+
"sourcesContent": ["// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\nimport type * as psp from \"@perspective-dev/client\";\nimport type * as pspViewer from \"@perspective-dev/viewer\";\nimport { usePspListener } from \"./utils\";\n\nfunction PerspectiveViewerImpl(props: PerspectiveViewerProps) {\n const [viewer, setViewer] =\n React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);\n\n React.useEffect(() => {\n return () => {\n viewer?.delete();\n };\n }, [viewer]);\n\n React.useEffect(() => {\n if (props.client) {\n viewer?.load(props.client);\n } else {\n viewer?.eject();\n }\n }, [viewer, props.client]);\n\n React.useEffect(() => {\n if (props.client && props.config) {\n viewer?.restore(props.config);\n }\n }, [viewer, props.client, JSON.stringify(props.config)]);\n\n usePspListener(viewer, \"perspective-click\", props.onClick);\n usePspListener(viewer, \"perspective-select\", props.onSelect);\n usePspListener(viewer, \"perspective-config-update\", props.onConfigUpdate);\n\n return (\n <perspective-viewer\n ref={setViewer}\n id={props.id}\n className={props.className}\n hidden={props.hidden}\n slot={props.slot}\n style={props.style}\n tabIndex={props.tabIndex}\n title={props.title}\n />\n );\n}\n\n/**\n * Props for the `<PerspectiveViewer>` component.\n */\nexport interface PerspectiveViewerProps {\n client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;\n config?: pspViewer.ViewerConfigUpdate;\n onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;\n onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;\n onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;\n\n // Applicable props from `React.HTMLAttributes`, which we cannot extend\n // directly because Perspective changes the signature of `onClick`.\n className?: string | undefined;\n hidden?: boolean | undefined;\n id?: string | undefined;\n slot?: string | undefined;\n style?: React.CSSProperties | undefined;\n tabIndex?: number | undefined;\n title?: string | undefined;\n}\n\n/**\n * A React wrapper component for `<perspective-viewer>` Custom Element.\n */\nexport const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(\n PerspectiveViewerImpl,\n);\n", "// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\n\nexport function usePspListener<A>(\n el: HTMLElement | undefined | null,\n event: string,\n cb?: (x: A) => void,\n) {\n React.useEffect(() => {\n if (!cb || !el) return;\n const ctx = new AbortController();\n const callback = (e: Event) => cb((e as CustomEvent).detail);\n el?.addEventListener(event, callback, { signal: ctx.signal });\n return () => ctx.abort();\n }, [el, cb]);\n}\n", "// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport type * as psp from \"@perspective-dev/client\";\nimport type * as psp_viewer from \"@perspective-dev/viewer\";\nimport type * as pspWorkspace from \"@perspective-dev/workspace\";\nimport { PerspectiveWorkspaceConfig } from \"@perspective-dev/workspace\";\nimport * as utils from \"./utils\";\nimport * as React from \"react\";\n\nexport interface NewViewEventDetail {\n config: psp_viewer.ViewerConfigUpdate;\n widget: pspWorkspace.PerspectiveViewerWidget;\n}\n\nexport interface ToggleGloalFilterEventDetail {\n widget: pspWorkspace.PerspectiveViewerWidget;\n isGlobalFilter: boolean;\n}\n\ninterface PerspectiveWorkspaceProps extends React.HTMLAttributes<HTMLElement> {\n client: psp.Client | Promise<psp.Client>;\n layout: PerspectiveWorkspaceConfig;\n onLayoutUpdate?: (layout: PerspectiveWorkspaceConfig) => void;\n onNewView?: (detail: NewViewEventDetail) => void;\n onToggleGlobalFilter?: (detail: ToggleGloalFilterEventDetail) => void;\n}\n\nconst PerspectiveWorkspaceImpl = React.forwardRef<\n pspWorkspace.HTMLPerspectiveWorkspaceElement | undefined,\n PerspectiveWorkspaceProps\n>(\n (\n {\n client,\n layout,\n onLayoutUpdate,\n onNewView,\n onToggleGlobalFilter,\n ...htmlAttributes\n },\n ref,\n ) => {\n const [workspace, setWorkspace] =\n React.useState<pspWorkspace.HTMLPerspectiveWorkspaceElement>();\n\n React.useImperativeHandle(ref, () => workspace, [workspace]);\n React.useEffect(() => {\n if (workspace && layout) {\n workspace.restore(layout);\n }\n }, [workspace, layout]);\n\n React.useEffect(() => {\n if (workspace && client) {\n workspace.load(client);\n }\n }, [workspace, client]);\n\n utils.usePspListener(workspace, \"workspace-new-view\", onNewView);\n utils.usePspListener(\n workspace,\n \"workspace-layout-update\",\n onLayoutUpdate\n ? ({ layout }: { layout: PerspectiveWorkspaceConfig }) =>\n workspace?.save().then((x) => onLayoutUpdate(x))\n : undefined,\n );\n\n utils.usePspListener(\n workspace,\n \"workspace-toggle-global-filter\",\n onToggleGlobalFilter,\n );\n\n return (\n <perspective-workspace\n ref={(r) => setWorkspace(r ?? undefined)}\n {...htmlAttributes}\n ></perspective-workspace>\n );\n },\n);\n\nexport const PerspectiveWorkspace = React.memo(PerspectiveWorkspaceImpl);\n"],
|
|
5
|
+
"mappings": "AAYA,UAAYA,MAAW,QCAvB,UAAYC,MAAW,QAEhB,SAASC,EACZC,EACAC,EACAC,EACF,CACQ,YAAU,IAAM,CAClB,GAAI,CAACA,GAAM,CAACF,EAAI,OAChB,IAAMG,EAAM,IAAI,gBACVC,EAAYC,GAAaH,EAAIG,EAAkB,MAAM,EAC3D,OAAAL,GAAI,iBAAiBC,EAAOG,EAAU,CAAE,OAAQD,EAAI,MAAO,CAAC,EACrD,IAAMA,EAAI,MAAM,CAC3B,EAAG,CAACH,EAAIE,CAAE,CAAC,CACf,CDoBQ,cAAAI,MAAA,oBA7BR,SAASC,EAAsBC,EAA+B,CAC1D,GAAM,CAACC,EAAQC,CAAS,EACd,WAAwD,IAAI,EAEtE,OAAM,YAAU,IACL,IAAM,CACTD,GAAQ,OAAO,CACnB,EACD,CAACA,CAAM,CAAC,EAEL,YAAU,IAAM,CACdD,EAAM,OACNC,GAAQ,KAAKD,EAAM,MAAM,EAEzBC,GAAQ,MAAM,CAEtB,EAAG,CAACA,EAAQD,EAAM,MAAM,CAAC,EAEnB,YAAU,IAAM,CACdA,EAAM,QAAUA,EAAM,QACtBC,GAAQ,QAAQD,EAAM,MAAM,CAEpC,EAAG,CAACC,EAAQD,EAAM,OAAQ,KAAK,UAAUA,EAAM,MAAM,CAAC,CAAC,EAEvDG,EAAeF,EAAQ,oBAAqBD,EAAM,OAAO,EACzDG,EAAeF,EAAQ,qBAAsBD,EAAM,QAAQ,EAC3DG,EAAeF,EAAQ,4BAA6BD,EAAM,cAAc,EAGpEF,EAAC,sBACG,IAAKI,EACL,GAAIF,EAAM,GACV,UAAWA,EAAM,UACjB,OAAQA,EAAM,OACd,KAAMA,EAAM,KACZ,MAAOA,EAAM,MACb,SAAUA,EAAM,SAChB,MAAOA,EAAM,MACjB,CAER,CA0BO,IAAMI,EAA4D,OACrEL,CACJ,EEpEA,UAAYM,MAAW,QAoEX,cAAAC,MAAA,oBAhDZ,IAAMC,EAAiC,aAInC,CACI,CACI,OAAAC,EACA,OAAAC,EACA,eAAAC,EACA,UAAAC,EACA,qBAAAC,EACA,GAAGC,CACP,EACAC,IACC,CACD,GAAM,CAACC,EAAWC,CAAY,EACpB,WAAuD,EAEjE,OAAM,sBAAoBF,EAAK,IAAMC,EAAW,CAACA,CAAS,CAAC,EACrD,YAAU,IAAM,CACdA,GAAaN,GACbM,EAAU,QAAQN,CAAM,CAEhC,EAAG,CAACM,EAAWN,CAAM,CAAC,EAEhB,YAAU,IAAM,CACdM,GAAaP,GACbO,EAAU,KAAKP,CAAM,CAE7B,EAAG,CAACO,EAAWP,CAAM,CAAC,EAEhBS,EAAeF,EAAW,qBAAsBJ,CAAS,EACzDM,EACFF,EACA,0BACAL,EACM,CAAC,CAAE,OAAAD,CAAO,IACNM,GAAW,KAAK,EAAE,KAAMG,GAAMR,EAAeQ,CAAC,CAAC,EACnD,MACV,EAEMD,EACFF,EACA,iCACAH,CACJ,EAGIN,EAAC,yBACG,IAAMa,GAAMH,EAAaG,GAAK,MAAS,EACtC,GAAGN,EACP,CAET,CACJ,EAEaO,EAA6B,OAAKb,CAAwB",
|
|
6
|
+
"names": ["React", "React", "usePspListener", "el", "event", "cb", "ctx", "callback", "e", "jsx", "PerspectiveViewerImpl", "props", "viewer", "setViewer", "usePspListener", "PerspectiveViewer", "React", "jsx", "PerspectiveWorkspaceImpl", "client", "layout", "onLayoutUpdate", "onNewView", "onToggleGlobalFilter", "htmlAttributes", "ref", "workspace", "setWorkspace", "usePspListener", "x", "r", "PerspectiveWorkspace"]
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function usePspListener<A>(el: HTMLElement | undefined | null, event: string, cb?: (x: A) => void): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type * as psp from "@perspective-dev/client";
|
|
3
|
+
import type * as pspViewer from "@perspective-dev/viewer";
|
|
4
|
+
/**
|
|
5
|
+
* Props for the `<PerspectiveViewer>` component.
|
|
6
|
+
*/
|
|
7
|
+
export interface PerspectiveViewerProps {
|
|
8
|
+
client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
|
|
9
|
+
config?: pspViewer.ViewerConfigUpdate;
|
|
10
|
+
onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
|
|
11
|
+
onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
|
|
12
|
+
onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
|
|
13
|
+
className?: string | undefined;
|
|
14
|
+
hidden?: boolean | undefined;
|
|
15
|
+
id?: string | undefined;
|
|
16
|
+
slot?: string | undefined;
|
|
17
|
+
style?: React.CSSProperties | undefined;
|
|
18
|
+
tabIndex?: number | undefined;
|
|
19
|
+
title?: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A React wrapper component for `<perspective-viewer>` Custom Element.
|
|
23
|
+
*/
|
|
24
|
+
export declare const PerspectiveViewer: React.FC<PerspectiveViewerProps>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type * as psp from "@perspective-dev/client";
|
|
2
|
+
import type * as psp_viewer from "@perspective-dev/viewer";
|
|
3
|
+
import type * as pspWorkspace from "@perspective-dev/workspace";
|
|
4
|
+
import { PerspectiveWorkspaceConfig } from "@perspective-dev/workspace";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
export interface NewViewEventDetail {
|
|
7
|
+
config: psp_viewer.ViewerConfigUpdate;
|
|
8
|
+
widget: pspWorkspace.PerspectiveViewerWidget;
|
|
9
|
+
}
|
|
10
|
+
export interface ToggleGloalFilterEventDetail {
|
|
11
|
+
widget: pspWorkspace.PerspectiveViewerWidget;
|
|
12
|
+
isGlobalFilter: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface PerspectiveWorkspaceProps extends React.HTMLAttributes<HTMLElement> {
|
|
15
|
+
client: psp.Client | Promise<psp.Client>;
|
|
16
|
+
layout: PerspectiveWorkspaceConfig;
|
|
17
|
+
onLayoutUpdate?: (layout: PerspectiveWorkspaceConfig) => void;
|
|
18
|
+
onNewView?: (detail: NewViewEventDetail) => void;
|
|
19
|
+
onToggleGlobalFilter?: (detail: ToggleGloalFilterEventDetail) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare const PerspectiveWorkspace: React.MemoExoticComponent<React.ForwardRefExoticComponent<PerspectiveWorkspaceProps & React.RefAttributes<pspWorkspace.HTMLPerspectiveWorkspaceElement | undefined>>>;
|
|
22
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perspective-dev/react",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [],
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@perspective-dev/client": "",
|
|
18
18
|
"@perspective-dev/viewer": "",
|
|
19
|
+
"@perspective-dev/workspace": "",
|
|
19
20
|
"@types/react": "^18",
|
|
20
21
|
"react": "^18",
|
|
21
22
|
"react-dom": "^18"
|
package/src/index.tsx
CHANGED
|
@@ -20,87 +20,5 @@
|
|
|
20
20
|
* @module
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import type * as pspViewer from "@perspective-dev/viewer";
|
|
26
|
-
|
|
27
|
-
function usePspListener<A>(
|
|
28
|
-
viewer: HTMLElement | null,
|
|
29
|
-
name: string,
|
|
30
|
-
f?: (x: A) => void,
|
|
31
|
-
) {
|
|
32
|
-
React.useEffect(() => {
|
|
33
|
-
if (!f) return;
|
|
34
|
-
const ctx = new AbortController();
|
|
35
|
-
const callback = (e: Event) => f((e as CustomEvent).detail);
|
|
36
|
-
viewer?.addEventListener(name, callback, { signal: ctx.signal });
|
|
37
|
-
return () => ctx.abort();
|
|
38
|
-
}, [viewer, f]);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface PerspectiveViewerProps {
|
|
42
|
-
table?: psp.Table | Promise<psp.Table>;
|
|
43
|
-
config?: pspViewer.ViewerConfigUpdate;
|
|
44
|
-
onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
|
|
45
|
-
onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
|
|
46
|
-
onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
|
|
47
|
-
|
|
48
|
-
// Applicable props from `React.HTMLAttributes`, which we cannot extend
|
|
49
|
-
// directly because Perspective changes the signature of `onClick`.
|
|
50
|
-
className?: string | undefined;
|
|
51
|
-
hidden?: boolean | undefined;
|
|
52
|
-
id?: string | undefined;
|
|
53
|
-
slot?: string | undefined;
|
|
54
|
-
style?: React.CSSProperties | undefined;
|
|
55
|
-
tabIndex?: number | undefined;
|
|
56
|
-
title?: string | undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function PerspectiveViewerImpl(props: PerspectiveViewerProps) {
|
|
60
|
-
const [viewer, setViewer] =
|
|
61
|
-
React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);
|
|
62
|
-
|
|
63
|
-
React.useEffect(() => {
|
|
64
|
-
return () => {
|
|
65
|
-
viewer?.delete();
|
|
66
|
-
};
|
|
67
|
-
}, [viewer]);
|
|
68
|
-
|
|
69
|
-
React.useEffect(() => {
|
|
70
|
-
if (props.table) {
|
|
71
|
-
viewer?.load(props.table);
|
|
72
|
-
} else {
|
|
73
|
-
viewer?.eject();
|
|
74
|
-
}
|
|
75
|
-
}, [viewer, props.table]);
|
|
76
|
-
|
|
77
|
-
React.useEffect(() => {
|
|
78
|
-
if (props.table && props.config) {
|
|
79
|
-
viewer?.restore(props.config);
|
|
80
|
-
}
|
|
81
|
-
}, [viewer, props.table, JSON.stringify(props.config)]);
|
|
82
|
-
|
|
83
|
-
usePspListener(viewer, "perspective-click", props.onClick);
|
|
84
|
-
usePspListener(viewer, "perspective-select", props.onSelect);
|
|
85
|
-
usePspListener(viewer, "perspective-config-update", props.onConfigUpdate);
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<perspective-viewer
|
|
89
|
-
ref={setViewer}
|
|
90
|
-
id={props.id}
|
|
91
|
-
className={props.className}
|
|
92
|
-
hidden={props.hidden}
|
|
93
|
-
slot={props.slot}
|
|
94
|
-
style={props.style}
|
|
95
|
-
tabIndex={props.tabIndex}
|
|
96
|
-
title={props.title}
|
|
97
|
-
/>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* A React wrapper component for `<perspective-viewer>` Custom Element.
|
|
103
|
-
*/
|
|
104
|
-
export const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(
|
|
105
|
-
PerspectiveViewerImpl,
|
|
106
|
-
);
|
|
23
|
+
export * from "./viewer";
|
|
24
|
+
export * from "./workspace";
|
package/src/utils.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
|
|
15
|
+
export function usePspListener<A>(
|
|
16
|
+
el: HTMLElement | undefined | null,
|
|
17
|
+
event: string,
|
|
18
|
+
cb?: (x: A) => void,
|
|
19
|
+
) {
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
if (!cb || !el) return;
|
|
22
|
+
const ctx = new AbortController();
|
|
23
|
+
const callback = (e: Event) => cb((e as CustomEvent).detail);
|
|
24
|
+
el?.addEventListener(event, callback, { signal: ctx.signal });
|
|
25
|
+
return () => ctx.abort();
|
|
26
|
+
}, [el, cb]);
|
|
27
|
+
}
|
package/src/viewer.tsx
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
import type * as psp from "@perspective-dev/client";
|
|
15
|
+
import type * as pspViewer from "@perspective-dev/viewer";
|
|
16
|
+
import { usePspListener } from "./utils";
|
|
17
|
+
|
|
18
|
+
function PerspectiveViewerImpl(props: PerspectiveViewerProps) {
|
|
19
|
+
const [viewer, setViewer] =
|
|
20
|
+
React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);
|
|
21
|
+
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
return () => {
|
|
24
|
+
viewer?.delete();
|
|
25
|
+
};
|
|
26
|
+
}, [viewer]);
|
|
27
|
+
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
if (props.client) {
|
|
30
|
+
viewer?.load(props.client);
|
|
31
|
+
} else {
|
|
32
|
+
viewer?.eject();
|
|
33
|
+
}
|
|
34
|
+
}, [viewer, props.client]);
|
|
35
|
+
|
|
36
|
+
React.useEffect(() => {
|
|
37
|
+
if (props.client && props.config) {
|
|
38
|
+
viewer?.restore(props.config);
|
|
39
|
+
}
|
|
40
|
+
}, [viewer, props.client, JSON.stringify(props.config)]);
|
|
41
|
+
|
|
42
|
+
usePspListener(viewer, "perspective-click", props.onClick);
|
|
43
|
+
usePspListener(viewer, "perspective-select", props.onSelect);
|
|
44
|
+
usePspListener(viewer, "perspective-config-update", props.onConfigUpdate);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<perspective-viewer
|
|
48
|
+
ref={setViewer}
|
|
49
|
+
id={props.id}
|
|
50
|
+
className={props.className}
|
|
51
|
+
hidden={props.hidden}
|
|
52
|
+
slot={props.slot}
|
|
53
|
+
style={props.style}
|
|
54
|
+
tabIndex={props.tabIndex}
|
|
55
|
+
title={props.title}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Props for the `<PerspectiveViewer>` component.
|
|
62
|
+
*/
|
|
63
|
+
export interface PerspectiveViewerProps {
|
|
64
|
+
client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
|
|
65
|
+
config?: pspViewer.ViewerConfigUpdate;
|
|
66
|
+
onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
|
|
67
|
+
onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
|
|
68
|
+
onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
|
|
69
|
+
|
|
70
|
+
// Applicable props from `React.HTMLAttributes`, which we cannot extend
|
|
71
|
+
// directly because Perspective changes the signature of `onClick`.
|
|
72
|
+
className?: string | undefined;
|
|
73
|
+
hidden?: boolean | undefined;
|
|
74
|
+
id?: string | undefined;
|
|
75
|
+
slot?: string | undefined;
|
|
76
|
+
style?: React.CSSProperties | undefined;
|
|
77
|
+
tabIndex?: number | undefined;
|
|
78
|
+
title?: string | undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A React wrapper component for `<perspective-viewer>` Custom Element.
|
|
83
|
+
*/
|
|
84
|
+
export const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(
|
|
85
|
+
PerspectiveViewerImpl,
|
|
86
|
+
);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import type * as psp from "@perspective-dev/client";
|
|
14
|
+
import type * as psp_viewer from "@perspective-dev/viewer";
|
|
15
|
+
import type * as pspWorkspace from "@perspective-dev/workspace";
|
|
16
|
+
import { PerspectiveWorkspaceConfig } from "@perspective-dev/workspace";
|
|
17
|
+
import * as utils from "./utils";
|
|
18
|
+
import * as React from "react";
|
|
19
|
+
|
|
20
|
+
export interface NewViewEventDetail {
|
|
21
|
+
config: psp_viewer.ViewerConfigUpdate;
|
|
22
|
+
widget: pspWorkspace.PerspectiveViewerWidget;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ToggleGloalFilterEventDetail {
|
|
26
|
+
widget: pspWorkspace.PerspectiveViewerWidget;
|
|
27
|
+
isGlobalFilter: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface PerspectiveWorkspaceProps extends React.HTMLAttributes<HTMLElement> {
|
|
31
|
+
client: psp.Client | Promise<psp.Client>;
|
|
32
|
+
layout: PerspectiveWorkspaceConfig;
|
|
33
|
+
onLayoutUpdate?: (layout: PerspectiveWorkspaceConfig) => void;
|
|
34
|
+
onNewView?: (detail: NewViewEventDetail) => void;
|
|
35
|
+
onToggleGlobalFilter?: (detail: ToggleGloalFilterEventDetail) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const PerspectiveWorkspaceImpl = React.forwardRef<
|
|
39
|
+
pspWorkspace.HTMLPerspectiveWorkspaceElement | undefined,
|
|
40
|
+
PerspectiveWorkspaceProps
|
|
41
|
+
>(
|
|
42
|
+
(
|
|
43
|
+
{
|
|
44
|
+
client,
|
|
45
|
+
layout,
|
|
46
|
+
onLayoutUpdate,
|
|
47
|
+
onNewView,
|
|
48
|
+
onToggleGlobalFilter,
|
|
49
|
+
...htmlAttributes
|
|
50
|
+
},
|
|
51
|
+
ref,
|
|
52
|
+
) => {
|
|
53
|
+
const [workspace, setWorkspace] =
|
|
54
|
+
React.useState<pspWorkspace.HTMLPerspectiveWorkspaceElement>();
|
|
55
|
+
|
|
56
|
+
React.useImperativeHandle(ref, () => workspace, [workspace]);
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
if (workspace && layout) {
|
|
59
|
+
workspace.restore(layout);
|
|
60
|
+
}
|
|
61
|
+
}, [workspace, layout]);
|
|
62
|
+
|
|
63
|
+
React.useEffect(() => {
|
|
64
|
+
if (workspace && client) {
|
|
65
|
+
workspace.load(client);
|
|
66
|
+
}
|
|
67
|
+
}, [workspace, client]);
|
|
68
|
+
|
|
69
|
+
utils.usePspListener(workspace, "workspace-new-view", onNewView);
|
|
70
|
+
utils.usePspListener(
|
|
71
|
+
workspace,
|
|
72
|
+
"workspace-layout-update",
|
|
73
|
+
onLayoutUpdate
|
|
74
|
+
? ({ layout }: { layout: PerspectiveWorkspaceConfig }) =>
|
|
75
|
+
workspace?.save().then((x) => onLayoutUpdate(x))
|
|
76
|
+
: undefined,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
utils.usePspListener(
|
|
80
|
+
workspace,
|
|
81
|
+
"workspace-toggle-global-filter",
|
|
82
|
+
onToggleGlobalFilter,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<perspective-workspace
|
|
87
|
+
ref={(r) => setWorkspace(r ?? undefined)}
|
|
88
|
+
{...htmlAttributes}
|
|
89
|
+
></perspective-workspace>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const PerspectiveWorkspace = React.memo(PerspectiveWorkspaceImpl);
|
package/test/js/basic.story.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import perspective from "@perspective-dev/client";
|
|
|
14
14
|
import perspective_viewer from "@perspective-dev/viewer";
|
|
15
15
|
import "@perspective-dev/viewer-datagrid";
|
|
16
16
|
import "@perspective-dev/viewer-d3fc";
|
|
17
|
+
import "@perspective-dev/workspace";
|
|
17
18
|
|
|
18
19
|
// @ts-ignore
|
|
19
20
|
import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm?url";
|
|
@@ -26,12 +27,21 @@ await Promise.all([
|
|
|
26
27
|
perspective_viewer.init_client(fetch(CLIENT_WASM)),
|
|
27
28
|
]);
|
|
28
29
|
|
|
29
|
-
import
|
|
30
|
+
import * as psp from "@perspective-dev/client";
|
|
30
31
|
import type * as pspViewer from "@perspective-dev/viewer";
|
|
31
32
|
|
|
32
33
|
// @ts-ignore
|
|
33
34
|
import SUPERSTORE_ARROW from "superstore-arrow/superstore.lz4.arrow?url";
|
|
34
35
|
|
|
36
|
+
import * as React from "react";
|
|
37
|
+
import {
|
|
38
|
+
PerspectiveViewer,
|
|
39
|
+
PerspectiveWorkspace,
|
|
40
|
+
} from "@perspective-dev/react";
|
|
41
|
+
|
|
42
|
+
import "@perspective-dev/viewer/dist/css/themes.css";
|
|
43
|
+
import "./index.css";
|
|
44
|
+
|
|
35
45
|
const WORKER = await perspective.worker();
|
|
36
46
|
|
|
37
47
|
async function createNewSuperstoreTable(): Promise<psp.Table> {
|
|
@@ -45,12 +55,6 @@ const CONFIG: pspViewer.ViewerConfigUpdate = {
|
|
|
45
55
|
group_by: ["State"],
|
|
46
56
|
};
|
|
47
57
|
|
|
48
|
-
import * as React from "react";
|
|
49
|
-
import { PerspectiveViewer } from "@perspective-dev/react";
|
|
50
|
-
|
|
51
|
-
import "@perspective-dev/viewer/dist/css/themes.css";
|
|
52
|
-
import "./index.css";
|
|
53
|
-
|
|
54
58
|
interface ToolbarState {
|
|
55
59
|
mounted: boolean;
|
|
56
60
|
table?: Promise<psp.Table>;
|
package/test/js/index.css
CHANGED
|
@@ -52,3 +52,17 @@ button {
|
|
|
52
52
|
font-family: "ui-monospace", "SFMono-Regular", "SF Mono", "Menlo",
|
|
53
53
|
"Consolas", "Liberation Mono", monospace;
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
.workspace-container {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
|
|
60
|
+
.workspace-toolbar {
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: row;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
perspective-workspace {
|
|
66
|
+
height: 100vh;
|
|
67
|
+
}
|
|
68
|
+
}
|
package/test/js/react.spec.tsx
CHANGED
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
import { test, expect } from "@playwright/experimental-ct-react";
|
|
14
|
-
import { PerspectiveViewer } from "@perspective-dev/react";
|
|
15
|
-
import React from "react";
|
|
16
14
|
|
|
17
15
|
import { App } from "./basic.story";
|
|
16
|
+
import { EmptyWorkspace, SingleView } from "./workspace.story";
|
|
18
17
|
|
|
19
18
|
test.describe("Perspective React", () => {
|
|
20
19
|
test("The viewer loads with data in it", async ({ page, mount }) => {
|
|
@@ -26,4 +25,65 @@ test.describe("Perspective React", () => {
|
|
|
26
25
|
|
|
27
26
|
expect(count).toBe(2);
|
|
28
27
|
});
|
|
28
|
+
|
|
29
|
+
test("React workspace functionality", async ({ page, mount }) => {
|
|
30
|
+
const comp = await mount(<EmptyWorkspace />);
|
|
31
|
+
const toggleMount = comp.locator("button.toggle-mount");
|
|
32
|
+
const addViewer = comp.locator("button.add-viewer");
|
|
33
|
+
const workspace = comp.locator("perspective-workspace");
|
|
34
|
+
const viewer = comp.locator("perspective-viewer");
|
|
35
|
+
await toggleMount.waitFor();
|
|
36
|
+
await addViewer.click();
|
|
37
|
+
await addViewer.click();
|
|
38
|
+
await addViewer.click();
|
|
39
|
+
await page.waitForFunction(
|
|
40
|
+
() =>
|
|
41
|
+
document.querySelector("perspective-workspace")!.children
|
|
42
|
+
.length === 3,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
await expect(viewer).toHaveCount(3);
|
|
46
|
+
await toggleMount.click();
|
|
47
|
+
await workspace.waitFor({ state: "detached" });
|
|
48
|
+
await toggleMount.click();
|
|
49
|
+
await workspace.waitFor();
|
|
50
|
+
await page.waitForFunction(
|
|
51
|
+
() =>
|
|
52
|
+
document.querySelector("perspective-workspace")!.children
|
|
53
|
+
.length === 3,
|
|
54
|
+
);
|
|
55
|
+
await expect(viewer).toHaveCount(3);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("Adding a viewer in single-document mode leaves SDM", async ({
|
|
59
|
+
page,
|
|
60
|
+
mount,
|
|
61
|
+
}) => {
|
|
62
|
+
const name = "abcdef";
|
|
63
|
+
const comp = await mount(<SingleView name={name} />);
|
|
64
|
+
const addViewer = comp.locator("button.add-viewer");
|
|
65
|
+
const viewer = comp.locator("perspective-viewer");
|
|
66
|
+
const settingsBtn = comp.locator(`perspective-viewer #settings_button`);
|
|
67
|
+
|
|
68
|
+
await settingsBtn.waitFor();
|
|
69
|
+
await addViewer.waitFor();
|
|
70
|
+
await addViewer.click();
|
|
71
|
+
await page.waitForFunction(
|
|
72
|
+
() =>
|
|
73
|
+
document.querySelector("perspective-workspace")!.children
|
|
74
|
+
.length === 2,
|
|
75
|
+
);
|
|
76
|
+
expect(await viewer.count()).toBe(2);
|
|
77
|
+
await settingsBtn.first().click();
|
|
78
|
+
const settingsPanel = viewer.locator("#settings_panel");
|
|
79
|
+
await settingsPanel.waitFor();
|
|
80
|
+
await addViewer.click();
|
|
81
|
+
await page.waitForFunction(
|
|
82
|
+
() =>
|
|
83
|
+
document.querySelector("perspective-workspace")!.children
|
|
84
|
+
.length === 3,
|
|
85
|
+
);
|
|
86
|
+
expect(await viewer.count()).toBe(3);
|
|
87
|
+
await settingsPanel.waitFor({ state: "detached" });
|
|
88
|
+
});
|
|
29
89
|
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
import "@perspective-dev/workspace";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
HTMLPerspectiveWorkspaceElement,
|
|
16
|
+
PerspectiveWorkspaceConfig,
|
|
17
|
+
} from "@perspective-dev/workspace";
|
|
18
|
+
|
|
19
|
+
import * as React from "react";
|
|
20
|
+
|
|
21
|
+
import { PerspectiveWorkspace } from "@perspective-dev/react";
|
|
22
|
+
|
|
23
|
+
import perspective_viewer from "@perspective-dev/viewer";
|
|
24
|
+
import "@perspective-dev/viewer-datagrid";
|
|
25
|
+
import "@perspective-dev/viewer-d3fc";
|
|
26
|
+
import "@perspective-dev/workspace";
|
|
27
|
+
import * as Workspace from "@perspective-dev/workspace";
|
|
28
|
+
|
|
29
|
+
import * as perspective from "@perspective-dev/client";
|
|
30
|
+
|
|
31
|
+
import "@perspective-dev/viewer/dist/css/themes.css";
|
|
32
|
+
import "@perspective-dev/workspace/dist/css/pro.css";
|
|
33
|
+
import "./index.css";
|
|
34
|
+
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm?url";
|
|
37
|
+
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm?url";
|
|
40
|
+
|
|
41
|
+
await Promise.all([
|
|
42
|
+
perspective.init_server(fetch(SERVER_WASM)),
|
|
43
|
+
perspective_viewer.init_client(fetch(CLIENT_WASM)),
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const CLIENT = await perspective.worker();
|
|
47
|
+
|
|
48
|
+
interface WorkspaceState {
|
|
49
|
+
layout: PerspectiveWorkspaceConfig;
|
|
50
|
+
mounted: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface WorkspaceAppProps {
|
|
54
|
+
layout: PerspectiveWorkspaceConfig;
|
|
55
|
+
onSpecial?: () => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const WorkspaceApp: React.FC<WorkspaceAppProps> = (props) => {
|
|
59
|
+
const [state, setState] = React.useState<WorkspaceState>({
|
|
60
|
+
layout: props.layout,
|
|
61
|
+
mounted: true,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const onClickAddViewer = () => {
|
|
65
|
+
const name = window.crypto.randomUUID();
|
|
66
|
+
const data = `a,b,c\n${Math.random()},${Math.random()},${Math.random()}`;
|
|
67
|
+
CLIENT.table(data, { name });
|
|
68
|
+
const nextId = Workspace.genId(state.layout);
|
|
69
|
+
const layout = Workspace.addViewer(
|
|
70
|
+
state.layout,
|
|
71
|
+
{
|
|
72
|
+
table: name,
|
|
73
|
+
title: name,
|
|
74
|
+
},
|
|
75
|
+
nextId,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
setState({
|
|
79
|
+
...state,
|
|
80
|
+
layout,
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const onClickToggleMount = () =>
|
|
85
|
+
setState((old) => ({ ...old, mounted: !state.mounted }));
|
|
86
|
+
|
|
87
|
+
const onLayoutUpdate = (layout: PerspectiveWorkspaceConfig) => {
|
|
88
|
+
setState({ ...state, layout });
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
React.useEffect(() => {
|
|
92
|
+
setState((s) => ({
|
|
93
|
+
...s,
|
|
94
|
+
layout: props.layout,
|
|
95
|
+
}));
|
|
96
|
+
}, [props.layout]);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className="workspace-container">
|
|
100
|
+
<div className="workspace-toolbar">
|
|
101
|
+
<button className="toggle-mount" onClick={onClickToggleMount}>
|
|
102
|
+
Toggle Mount
|
|
103
|
+
</button>
|
|
104
|
+
<button className="add-viewer" onClick={onClickAddViewer}>
|
|
105
|
+
Add Viewer
|
|
106
|
+
</button>
|
|
107
|
+
{props.onSpecial && (
|
|
108
|
+
<button className="special" onClick={props.onSpecial}>
|
|
109
|
+
Special Third Button
|
|
110
|
+
</button>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
{state.mounted && (
|
|
114
|
+
<PerspectiveWorkspace
|
|
115
|
+
client={CLIENT}
|
|
116
|
+
layout={state.layout}
|
|
117
|
+
onLayoutUpdate={onLayoutUpdate}
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/// Renders the app with a default empty workspace
|
|
125
|
+
export const EmptyWorkspace: React.FC = () => {
|
|
126
|
+
return (
|
|
127
|
+
<WorkspaceApp
|
|
128
|
+
layout={{ sizes: [1], viewers: {}, detail: { main: null } }}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const SingleView: React.FC<{ name: string }> = ({ name }) => {
|
|
134
|
+
const _table = CLIENT.table("a,b,c\n1,2,3", { name });
|
|
135
|
+
const layout: PerspectiveWorkspaceConfig = {
|
|
136
|
+
sizes: [1],
|
|
137
|
+
detail: {
|
|
138
|
+
main: {
|
|
139
|
+
type: "tab-area",
|
|
140
|
+
currentIndex: 0,
|
|
141
|
+
widgets: [name],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
viewers: {
|
|
145
|
+
[name]: {
|
|
146
|
+
table: name,
|
|
147
|
+
columns: ["a", "b", "c"],
|
|
148
|
+
title: name,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return <WorkspaceApp layout={layout} />;
|
|
154
|
+
};
|