chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:25:07 +08:00
commit a26e856398
1681 changed files with 296950 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { NodeModulesExternal } from "@perspective-dev/esbuild-plugin/external.js";
import { build } from "@perspective-dev/esbuild-plugin/build.js";
import "zx/globals";
const BUILD = [
{
define: {
global: "window",
},
entryPoints: ["src/index.tsx"],
format: "esm",
loader: {},
outfile: "dist/esm/index.js",
plugins: [NodeModulesExternal()],
},
];
async function build_all() {
await Promise.all(BUILD.map(build)).catch(() => process.exit(1));
try {
await $`tsc --project ./tsconfig.json`.stdio(
"inherit",
"inherit",
"inherit",
);
} catch (e) {
console.error(e);
process.exit(1);
}
}
build_all();
+15
View File
@@ -0,0 +1,15 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as fs from "node:fs";
fs.rmSync("dist", { recursive: true, force: true });
+15
View File
@@ -0,0 +1,15 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import "zx/globals";
await $`typedoc --tsconfig tsconfig.json --out ../../docs/static/react`;
+51
View File
@@ -0,0 +1,51 @@
{
"name": "@perspective-dev/react",
"version": "4.5.2",
"description": "React component wrappers for `<perspective-viewer>` and `<perspective-workspace>`",
"keywords": [
"perspective",
"react",
"data",
"analytics",
"visualization"
],
"homepage": "https://perspective-dev.github.io",
"repository": {
"type": "git",
"url": "https://github.com/perspective-dev/perspective"
},
"license": "Apache-2.0",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/esm/index.d.ts",
"import": "./dist/esm/index.js",
"default": "./dist/esm/index.js"
},
"./package.json": "./package.json"
},
"type": "module",
"scripts": {
"build": "node ./build.mjs",
"clean": "node ./clean.mjs",
"docs": "node ./docs.mjs"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@perspective-dev/client": "workspace:",
"@perspective-dev/viewer": "workspace:",
"@perspective-dev/workspace": "workspace:",
"@types/react": "catalog:",
"react": "catalog:",
"react-dom": "catalog:"
},
"devDependencies": {
"@perspective-dev/esbuild-plugin": "workspace:",
"superstore-arrow": "catalog:",
"typescript": "catalog:",
"typedoc": "catalog:",
"zx": "catalog:"
}
}
+24
View File
@@ -0,0 +1,24 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
/**
*
* # See Also
*
* [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
* project from the Perspective GitHub repo.
*
* @module
*/
export * from "./viewer";
export * from "./workspace";
+27
View File
@@ -0,0 +1,27 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as React from "react";
export function usePspListener<A>(
el: HTMLElement | undefined | null,
event: string,
cb?: (x: A) => void,
) {
React.useEffect(() => {
if (!cb || !el) return;
const ctx = new AbortController();
const callback = (e: Event) => cb((e as CustomEvent).detail);
el?.addEventListener(event, callback, { signal: ctx.signal });
return () => ctx.abort();
}, [el, cb]);
}
+86
View File
@@ -0,0 +1,86 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as React from "react";
import type * as psp from "@perspective-dev/client";
import type * as pspViewer from "@perspective-dev/viewer";
import { usePspListener } from "./utils";
function PerspectiveViewerImpl(props: PerspectiveViewerProps) {
const [viewer, setViewer] =
React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);
React.useEffect(() => {
return () => {
viewer?.delete();
};
}, [viewer]);
React.useEffect(() => {
if (props.client) {
viewer?.load(props.client);
} else {
viewer?.eject();
}
}, [viewer, props.client]);
React.useEffect(() => {
if (props.client && props.config) {
viewer?.restore(props.config);
}
}, [viewer, props.client, JSON.stringify(props.config)]);
usePspListener(viewer, "perspective-click", props.onClick);
usePspListener(viewer, "perspective-select", props.onSelect);
usePspListener(viewer, "perspective-config-update", props.onConfigUpdate);
return (
<perspective-viewer
ref={setViewer}
id={props.id}
className={props.className}
hidden={props.hidden}
slot={props.slot}
style={props.style}
tabIndex={props.tabIndex}
title={props.title}
/>
);
}
/**
* Props for the `<PerspectiveViewer>` component.
*/
export interface PerspectiveViewerProps {
client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
config?: pspViewer.ViewerConfigUpdate;
onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
// Applicable props from `React.HTMLAttributes`, which we cannot extend
// directly because Perspective changes the signature of `onClick`.
className?: string | undefined;
hidden?: boolean | undefined;
id?: string | undefined;
slot?: string | undefined;
style?: React.CSSProperties | undefined;
tabIndex?: number | undefined;
title?: string | undefined;
}
/**
* A React wrapper component for `<perspective-viewer>` Custom Element.
*/
export const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(
PerspectiveViewerImpl,
);
+95
View File
@@ -0,0 +1,95 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import type * as psp from "@perspective-dev/client";
import type * as psp_viewer from "@perspective-dev/viewer";
import type * as pspWorkspace from "@perspective-dev/workspace";
import { PerspectiveWorkspaceConfig } from "@perspective-dev/workspace";
import * as utils from "./utils";
import * as React from "react";
export interface NewViewEventDetail {
config: psp_viewer.ViewerConfigUpdate;
widget: pspWorkspace.PerspectiveViewerWidget;
}
export interface ToggleGloalFilterEventDetail {
widget: pspWorkspace.PerspectiveViewerWidget;
isGlobalFilter: boolean;
}
export interface PerspectiveWorkspaceProps
extends React.HTMLAttributes<HTMLElement> {
client: psp.Client | Promise<psp.Client>;
layout: PerspectiveWorkspaceConfig;
onLayoutUpdate?: (layout: PerspectiveWorkspaceConfig) => void;
onNewView?: (detail: NewViewEventDetail) => void;
onToggleGlobalFilter?: (detail: ToggleGloalFilterEventDetail) => void;
}
const PerspectiveWorkspaceImpl = React.forwardRef<
pspWorkspace.HTMLPerspectiveWorkspaceElement | undefined,
PerspectiveWorkspaceProps
>(
(
{
client,
layout,
onLayoutUpdate,
onNewView,
onToggleGlobalFilter,
...htmlAttributes
},
ref,
) => {
const [workspace, setWorkspace] =
React.useState<pspWorkspace.HTMLPerspectiveWorkspaceElement>();
React.useImperativeHandle(ref, () => workspace, [workspace]);
React.useEffect(() => {
if (workspace && layout) {
workspace.restore(layout);
}
}, [workspace, layout]);
React.useEffect(() => {
if (workspace && client) {
workspace.load(client);
}
}, [workspace, client]);
utils.usePspListener(workspace, "workspace-new-view", onNewView);
utils.usePspListener(
workspace,
"workspace-layout-update",
onLayoutUpdate
? ({ layout }: { layout: PerspectiveWorkspaceConfig }) =>
workspace?.save().then((x) => onLayoutUpdate(x))
: undefined,
);
utils.usePspListener(
workspace,
"workspace-toggle-global-filter",
onToggleGlobalFilter,
);
return (
<perspective-workspace
ref={(r) => setWorkspace(r ?? undefined)}
{...htmlAttributes}
></perspective-workspace>
);
},
);
export const PerspectiveWorkspace = React.memo(PerspectiveWorkspaceImpl);
+106
View File
@@ -0,0 +1,106 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import perspective from "@perspective-dev/client";
import perspective_viewer from "@perspective-dev/viewer";
import "@perspective-dev/viewer-datagrid";
import "@perspective-dev/viewer-charts";
import "@perspective-dev/workspace";
// @ts-ignore
import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm?url";
// @ts-ignore
import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm?url";
await Promise.all([
perspective.init_server(fetch(SERVER_WASM)),
perspective_viewer.init_client(fetch(CLIENT_WASM)),
]);
import * as psp from "@perspective-dev/client";
import type * as pspViewer from "@perspective-dev/viewer";
// @ts-ignore
import SUPERSTORE_ARROW from "superstore-arrow/superstore.lz4.arrow?url";
import * as React from "react";
import {
PerspectiveViewer,
PerspectiveWorkspace,
} from "@perspective-dev/react";
import "@perspective-dev/viewer/dist/css/themes.css";
import "./index.css";
const WORKER = await perspective.worker();
async function createNewSuperstoreTable(): Promise<psp.Table> {
const req = fetch(SUPERSTORE_ARROW);
const resp = await req;
const buffer = await resp.arrayBuffer();
return await WORKER.table(buffer);
}
const CONFIG: pspViewer.ViewerConfigUpdate = {
group_by: ["State"],
};
interface ToolbarState {
mounted: boolean;
table?: Promise<psp.Table>;
config: pspViewer.ViewerConfigUpdate;
}
export const App: React.FC = () => {
const [state, setState] = React.useState<ToolbarState>(() => ({
mounted: true,
table: createNewSuperstoreTable(),
config: { ...CONFIG },
}));
React.useEffect(() => {
return () => {
state.table?.then((table) => table?.delete({ lazy: true }));
};
}, []);
const onConfigUpdate = (config: pspViewer.ViewerConfigUpdate) => {
console.log("Config Update Event", config);
setState({ ...state, config });
};
const onClick = (detail: pspViewer.PerspectiveClickEventDetail) => {
console.log("Click Event,", detail);
};
const onSelect = (detail: pspViewer.PerspectiveSelectEventDetail) => {
console.log("Select Event", detail);
};
return (
<div className="container">
{state.mounted && (
<>
<PerspectiveViewer table={state.table} />
<PerspectiveViewer
table={state.table}
config={state.config}
onClick={onClick}
onSelect={onSelect}
onConfigUpdate={onConfigUpdate}
/>
</>
)}
</div>
);
};
+68
View File
@@ -0,0 +1,68 @@
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
* ┃ This file is part of the Perspective library, distributed under the terms ┃
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
*/
body {
margin: 0;
background-color: #f0f0f0;
font-family: "ui-monospace", "SFMono-Regular", "SF Mono", "Menlo",
"Consolas", "Liberation Mono", monospace;
}
.container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: grid;
gap: 8px;
grid-template-columns: 1fr 1fr;
grid-template-rows: 45px 1fr;
}
perspective-viewer {
grid-row: 2;
}
.toolbar {
grid-row: 1;
grid-column-start: 1;
grid-column-end: 3;
display: flex;
flex-direction: row;
gap: 10px;
padding: 10px;
justify-content: stretch;
border-bottom: 1px solid #666;
}
button {
font-family: "ui-monospace", "SFMono-Regular", "SF Mono", "Menlo",
"Consolas", "Liberation Mono", monospace;
}
.workspace-container {
display: flex;
flex-direction: column;
.workspace-toolbar {
display: flex;
flex-direction: row;
}
perspective-workspace {
height: 100vh;
}
}
+93
View File
@@ -0,0 +1,93 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@playwright/experimental-ct-react";
import { App } from "./basic.story";
import { EmptyWorkspace, SingleView } from "./workspace.story";
test.describe("Perspective React", () => {
test("The viewer loads with data in it", async ({ page, mount }) => {
const comp = await mount(<App></App>);
const count = await page.evaluate(async () => {
await new Promise((x) => setTimeout(x, 1000));
return document.querySelectorAll("perspective-viewer").length;
});
expect(count).toBe(2);
});
test("React workspace functionality", async ({ page, mount }) => {
const comp = await mount(<EmptyWorkspace />);
const toggleMount = comp.locator("button.toggle-mount");
const addViewer = comp.locator("button.add-viewer");
const workspace = comp.locator("perspective-workspace");
const viewer = comp.locator("perspective-viewer");
await toggleMount.waitFor();
await addViewer.click();
await addViewer.click();
await addViewer.click();
await page.waitForFunction(
() =>
document.querySelector("perspective-workspace")!.children
.length === 3,
);
await expect(viewer).toHaveCount(3);
await toggleMount.click();
await workspace.waitFor({ state: "detached" });
// TODO: This test gets stuck in CI
await page.waitForTimeout(10);
await toggleMount.click();
await workspace.waitFor();
await page.waitForFunction(
() =>
document.querySelector("perspective-workspace")!.children
.length === 3,
);
await expect(viewer).toHaveCount(3);
});
test("Adding a viewer in single-document mode leaves SDM", async ({
page,
mount,
}) => {
const name = "abcdef";
const comp = await mount(<SingleView name={name} />);
const addViewer = comp.locator("button.add-viewer");
const viewer = comp.locator("perspective-viewer");
const settingsBtn = comp.locator(`perspective-viewer #settings_button`);
await settingsBtn.waitFor();
await addViewer.waitFor();
await addViewer.click();
await page.waitForFunction(
() =>
document.querySelector("perspective-workspace")!.children
.length === 2,
);
expect(await viewer.count()).toBe(2);
await settingsBtn.first().click();
const settingsPanel = viewer.locator("#settings_panel");
await settingsPanel.waitFor();
await addViewer.click();
await page.waitForFunction(
() =>
document.querySelector("perspective-workspace")!.children
.length === 3,
);
expect(await viewer.count()).toBe(3);
await settingsPanel.waitFor({ state: "detached" });
});
});
+154
View File
@@ -0,0 +1,154 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import "@perspective-dev/workspace";
import {
HTMLPerspectiveWorkspaceElement,
PerspectiveWorkspaceConfig,
} from "@perspective-dev/workspace";
import * as React from "react";
import { PerspectiveWorkspace } from "@perspective-dev/react";
import perspective_viewer from "@perspective-dev/viewer";
import "@perspective-dev/viewer-datagrid";
import "@perspective-dev/viewer-charts";
import "@perspective-dev/workspace";
import * as Workspace from "@perspective-dev/workspace";
import * as perspective from "@perspective-dev/client";
import "@perspective-dev/viewer/dist/css/themes.css";
import "@perspective-dev/workspace/dist/css/pro.css";
import "./index.css";
// @ts-ignore
import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm?url";
// @ts-ignore
import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm?url";
await Promise.all([
perspective.init_server(fetch(SERVER_WASM)),
perspective_viewer.init_client(fetch(CLIENT_WASM)),
]);
const CLIENT = await perspective.worker();
interface WorkspaceState {
layout: PerspectiveWorkspaceConfig;
mounted: boolean;
}
interface WorkspaceAppProps {
layout: PerspectiveWorkspaceConfig;
onSpecial?: () => void;
}
const WorkspaceApp: React.FC<WorkspaceAppProps> = (props) => {
const [state, setState] = React.useState<WorkspaceState>({
layout: props.layout,
mounted: true,
});
const onClickAddViewer = () => {
const name = window.crypto.randomUUID();
const data = `a,b,c\n${Math.random()},${Math.random()},${Math.random()}`;
CLIENT.table(data, { name });
const nextId = Workspace.genId(state.layout);
const layout = Workspace.addViewer(
state.layout,
{
table: name,
title: name,
},
nextId,
);
setState({
...state,
layout,
});
};
const onClickToggleMount = () =>
setState((old) => ({ ...old, mounted: !state.mounted }));
const onLayoutUpdate = (layout: PerspectiveWorkspaceConfig) => {
setState({ ...state, layout });
};
React.useEffect(() => {
setState((s) => ({
...s,
layout: props.layout,
}));
}, [props.layout]);
return (
<div className="workspace-container">
<div className="workspace-toolbar">
<button className="toggle-mount" onClick={onClickToggleMount}>
Toggle Mount
</button>
<button className="add-viewer" onClick={onClickAddViewer}>
Add Viewer
</button>
{props.onSpecial && (
<button className="special" onClick={props.onSpecial}>
Special Third Button
</button>
)}
</div>
{state.mounted && (
<PerspectiveWorkspace
client={CLIENT}
layout={state.layout}
onLayoutUpdate={onLayoutUpdate}
/>
)}
</div>
);
};
/// Renders the app with a default empty workspace
export const EmptyWorkspace: React.FC = () => {
return (
<WorkspaceApp
layout={{ sizes: [1], viewers: {}, detail: { main: null } }}
/>
);
};
export const SingleView: React.FC<{ name: string }> = ({ name }) => {
const _table = CLIENT.table("a,b,c\n1,2,3", { name });
const layout: PerspectiveWorkspaceConfig = {
sizes: [1],
detail: {
main: {
type: "tab-area",
currentIndex: 0,
widgets: [name],
},
},
viewers: {
[name]: {
table: name,
columns: ["a", "b", "c"],
title: name,
},
},
};
return <WorkspaceApp layout={layout} />;
};
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"strict": true,
"module": "ESNext",
"target": "ESNext",
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "./dist/esm",
"rootDir": "./src",
"moduleResolution": "bundler",
"skipLibCheck": true,
"jsx": "preserve",
},
"files": [
"src/index.tsx"
]
}