Skip to content

Commit 141ace2

Browse files
committed
PerspectiveWorkspace React component
Signed-off-by: Davis Silverman <[email protected]>
1 parent afe0dfd commit 141ace2

File tree

31 files changed

+1898
-177
lines changed

31 files changed

+1898
-177
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 esbuild from "esbuild";
14+
import fs from "fs";
15+
import path from "path";
16+
import { fileURLToPath } from "url";
17+
import { dirname } from "path";
18+
19+
const __dirname = dirname(fileURLToPath(import.meta.url));
20+
21+
await esbuild.build({
22+
entryPoints: ["src/index.tsx"],
23+
outdir: "dist",
24+
format: "esm",
25+
bundle: true,
26+
sourcemap: "linked",
27+
target: "es2022",
28+
loader: {
29+
".arrow": "file",
30+
".wasm": "file",
31+
},
32+
assetNames: "[name]",
33+
});
34+
35+
fs.writeFileSync(
36+
path.join(__dirname, "dist/index.html"),
37+
fs.readFileSync(path.join(__dirname, "src/index.html")).toString()
38+
);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
declare module "*.wasm" {
14+
const content: string;
15+
export default content;
16+
}
17+
18+
declare module "*.arrow" {
19+
const content: string;
20+
export default content;
21+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "react-workspace-example",
3+
"private": true,
4+
"version": "3.7.4",
5+
"description": "An example app using @finos/perspective-react and its workspace component",
6+
"type": "module",
7+
"scripts": {
8+
"build": "node build.js",
9+
"start": "node build.js && http-server dist"
10+
},
11+
"keywords": [],
12+
"license": "Apache-2.0",
13+
"dependencies": {
14+
"@finos/perspective": "workspace:^",
15+
"@finos/perspective-viewer": "workspace:^",
16+
"@finos/perspective-viewer-d3fc": "workspace:^",
17+
"@finos/perspective-viewer-datagrid": "workspace:^",
18+
"@finos/perspective-react": "workspace:^",
19+
"react": "^18.0.0",
20+
"react-dom": "^18.0.0",
21+
"superstore-arrow": "^3.0.0"
22+
},
23+
"devDependencies": {
24+
"esbuild": "^0.25.5",
25+
"http-server": "^14.1.1",
26+
"@types/react": "^18",
27+
"@types/react-dom": "^18"
28+
}
29+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
14+
body {
15+
margin: 0;
16+
background-color: #f0f0f0;
17+
font-family: "ui-monospace", "SFMono-Regular", "SF Mono", "Menlo",
18+
"Consolas", "Liberation Mono", monospace;
19+
}
20+
21+
.container {
22+
position: absolute;
23+
top: 0;
24+
left: 0;
25+
right: 0;
26+
bottom: 0;
27+
28+
display: grid;
29+
gap: 8px;
30+
grid-template-columns: 1fr 1fr;
31+
grid-template-rows: 45px 1fr;
32+
}
33+
34+
perspective-viewer {
35+
grid-row: 2;
36+
}
37+
38+
.toolbar {
39+
grid-row: 1;
40+
grid-column-start: 1;
41+
grid-column-end: 3;
42+
43+
display: flex;
44+
flex-direction: row;
45+
gap: 10px;
46+
padding: 10px;
47+
justify-content: stretch;
48+
border-bottom: 1px solid #666;
49+
}
50+
51+
button {
52+
font-family: "ui-monospace", "SFMono-Regular", "SF Mono", "Menlo",
53+
"Consolas", "Liberation Mono", monospace;
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+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!--
2+
3+
Copyright (c) 2017, the Perspective Authors.
4+
5+
This file is part of the Perspective library, distributed under the terms of
6+
the Apache License 2.0. The full license can be found in the LICENSE file.
7+
-->
8+
<!DOCTYPE html>
9+
<html>
10+
<head>
11+
<meta charset="UTF-8" />
12+
<title>Perspective React Example</title>
13+
<link rel="stylesheet" href="./index.css" />
14+
<script type="module" src="./index.js"></script>
15+
</head>
16+
<body>
17+
<div id="root"></div>
18+
</body>
19+
</html>
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
13+
// # [Perspective bootstrapping](https://perspective.finos.org/guide/how_to/javascript/importing.html)
14+
15+
// Here we're initializing the WASM interpreter that powers the perspective API
16+
// and viewer, as covered in the [user guide section on bundling](https://perspective.finos.org/guide/how_to/javascript/importing.html).
17+
// This example is written assuming that the bundler is configured
18+
// to treat these files as a "file" and returns a path as the default export.
19+
// Use ./build.js as an example. The type stubs are in ./globals.d.ts
20+
21+
import perspective from "@finos/perspective";
22+
import perspective_viewer from "@finos/perspective-viewer";
23+
import "@finos/perspective-viewer-datagrid";
24+
import "@finos/perspective-viewer-d3fc";
25+
26+
import SERVER_WASM from "@finos/perspective/dist/wasm/perspective-server.wasm";
27+
import CLIENT_WASM from "@finos/perspective-viewer/dist/wasm/perspective-viewer.wasm";
28+
29+
await Promise.all([
30+
perspective.init_server(fetch(SERVER_WASM)),
31+
perspective_viewer.init_client(fetch(CLIENT_WASM)),
32+
]);
33+
34+
// # Data Source
35+
36+
// Data source creates a static Web Worker instance of Perspective engine, and a
37+
// table creation function which both downloads data and loads it into the
38+
// engine.
39+
40+
import type * as psp from "@finos/perspective";
41+
import * as Workspace from "@finos/perspective-workspace";
42+
43+
import * as React from "react";
44+
import { createRoot } from "react-dom/client";
45+
import { PerspectiveWorkspace } from "@finos/perspective-react";
46+
import { PerspectiveWorkspaceConfig } from "@finos/perspective-workspace";
47+
48+
import "@finos/perspective-viewer/dist/css/pro.css";
49+
import "@finos/perspective-workspace/dist/css/pro.css";
50+
import "./index.css";
51+
52+
const CLIENT = await perspective.worker();
53+
54+
interface WorkspaceState {
55+
mounted: boolean;
56+
config: PerspectiveWorkspaceConfig<string>;
57+
tables: Record<string, Promise<psp.Table>>;
58+
/// This object is kept for the 'swap tables' button.
59+
/// It is a backup set of tables that correspond in keys to `tables`
60+
/// but with different data.
61+
swapTables: Record<string, Promise<psp.Table>>;
62+
/// if false use `tables` and true use `swapTables` in the workspace
63+
swap: boolean;
64+
}
65+
66+
const WorkspaceApp: React.FC = () => {
67+
const [state, setState] = React.useState<WorkspaceState>({
68+
mounted: true,
69+
tables: {},
70+
swapTables: {},
71+
config: {
72+
sizes: [],
73+
viewers: {},
74+
detail: undefined,
75+
},
76+
swap: false,
77+
});
78+
79+
const onClickAddViewer = React.useCallback(async () => {
80+
const name = window.crypto.randomUUID().slice(0, 8);
81+
const data = `a,b,c\n${Math.random()},${Math.random()},${Math.random()}`;
82+
const swapData = `a,b,c\n${Math.random()},${Math.random()},${Math.random()}\n${Math.random()},${Math.random()},${Math.random()}`;
83+
// dont assign internal names to the tables they are not used by the workspace
84+
const t = CLIENT.table(data);
85+
const swap = CLIENT.table(swapData);
86+
const config = Workspace.addViewer(state.config, {
87+
table: name,
88+
title: name,
89+
});
90+
const tables = { ...state.tables, [name]: t };
91+
const swapTables = { ...state.swapTables, [name]: swap };
92+
setState({
93+
...state,
94+
tables,
95+
config,
96+
swapTables,
97+
});
98+
}, [state]);
99+
100+
const onLayoutUpdate: (detail: {
101+
layout: PerspectiveWorkspaceConfig<string>;
102+
tables: Record<string, psp.Table | Promise<psp.Table>>;
103+
}) => void = React.useCallback(
104+
({ layout, tables }) => {
105+
const newTables = Object.fromEntries(
106+
Object.entries(tables).map(([k, v]) => [k, Promise.resolve(v)])
107+
);
108+
setState({
109+
...state,
110+
config: layout,
111+
tables: state.swap ? state.tables : newTables,
112+
swapTables: state.swap ? newTables : state.swapTables,
113+
});
114+
},
115+
[state]
116+
);
117+
118+
const onClickToggleMount = () =>
119+
setState((old) => ({ ...old, mounted: !state.mounted }));
120+
121+
// swaps the tables out but uses the same name of them.
122+
// this keeps the layout the same, but the data within each viewer changes
123+
const swapTables = React.useCallback(() => {
124+
setState({
125+
...state,
126+
swap: !state.swap,
127+
});
128+
}, [state]);
129+
130+
return (
131+
<div className="workspace-container">
132+
<div className="workspace-toolbar">
133+
<button className="toggle-mount" onClick={onClickToggleMount}>
134+
Toggle Mount
135+
</button>
136+
<button className="add-viewer" onClick={onClickAddViewer}>
137+
Add Viewer
138+
</button>
139+
<button className="swap" onClick={swapTables}>
140+
Swap underlying tables
141+
</button>
142+
</div>
143+
{state.mounted && (
144+
<PerspectiveWorkspace
145+
tables={state.swap ? state.swapTables : state.tables}
146+
config={state.config}
147+
onLayoutUpdate={onLayoutUpdate}
148+
/>
149+
)}
150+
</div>
151+
);
152+
};
153+
154+
createRoot(document.getElementById("root")!).render(<WorkspaceApp />);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"compilerOptions": {
3+
"moduleResolution": "bundler",
4+
"target": "ESNext",
5+
"module": "ESNext",
6+
"lib": [
7+
"ES2020",
8+
"dom"
9+
],
10+
"strict": true,
11+
"alwaysStrict": true,
12+
"noImplicitAny": true,
13+
"noImplicitReturns": true,
14+
"noImplicitThis": true,
15+
"noImplicitUseStrict": false,
16+
"noUnusedLocals": false,
17+
"strictNullChecks": true,
18+
"skipLibCheck": false,
19+
"removeComments": false,
20+
"jsx": "react-jsx",
21+
"allowSyntheticDefaultImports": true,
22+
"esModuleInterop": true,
23+
"forceConsistentCasingInFileNames": true,
24+
"importHelpers": false,
25+
"noEmitHelpers": true,
26+
"inlineSourceMap": false,
27+
"sourceMap": true,
28+
"emitDecoratorMetadata": false,
29+
"experimentalDecorators": true,
30+
"downlevelIteration": true,
31+
"pretty": true
32+
},
33+
"exclude": [
34+
"node_modules"
35+
]
36+
}

packages/perspective-react/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"author": "",
88
"license": "ISC",
99
"dependencies": {
10-
"@finos/perspective": "workspace:^"
10+
"@finos/perspective": "workspace:^",
11+
"async-mutex": "0.5.0"
1112
},
1213
"exports": {
1314
".": "./dist/esm/index.js"
@@ -24,6 +25,7 @@
2425
"peerDependencies": {
2526
"@finos/perspective": "workspace:^",
2627
"@finos/perspective-viewer": "workspace:^",
28+
"@finos/perspective-workspace": "workspace:^",
2729
"@types/react": "^18",
2830
"react": "^18",
2931
"react-dom": "^18"

0 commit comments

Comments
 (0)