chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:23:53 +08:00
commit bf6f0825b2
1681 changed files with 296950 additions and 0 deletions
+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} />;
};