chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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" });
|
||||
});
|
||||
});
|
||||
@@ -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} />;
|
||||
};
|
||||
Reference in New Issue
Block a user