Skip to content

Conversation

@sinistersnare
Copy link
Contributor

@sinistersnare sinistersnare commented Jul 29, 2025

Hello! This PR adds onto our perspective-react package an API for the <perspective-workspace> custom element in the form of a <PerspectiveWorkspace /> React component. You can use it as such:

import * as Workspace from "@finos/perspective-workspace";
const tables = {a: client.table("a,b,c\n1,2,3\n4,5,6", { name: "a" })};
const base = { sizes: [1], detail: { main: null }, viewers: {});
const id = Workspace.genId(base);
const layout = Workspace.addViewer(base, { table: "a", title: "View A!"}, id);
<PerspectiveWorkspace tables={tables} layout={layout} />

You can see that this PR also adds some free functions (namely addViewer and genId) to assist in the functional style of React.

There are also callbacks that can be passed for the on-layout-update and on-new-view events.

The PerspectiveWorkspaceConfig interface was updated to better reflect the reality of the current workspace structure.

During the course of this, I encountered a bug of an internal engine message not being swallowed by the engine. That bug is tracked in #3039.

@sinistersnare sinistersnare force-pushed the feature/react-workspace branch 3 times, most recently from 90b0462 to b60ab32 Compare July 29, 2025 18:18
widget: pspWorkspace.PerspectiveViewerWidget;
isGlobalFilter: boolean;
}) => void;
id?: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use React.HTMLAttributes

PerspectiveWorkspace,
} from "@finos/perspective-react";

import "@finos/perspective-viewer/dist/css/themes.css";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't import themes.css for viewers within a workspace - use the specific theme pro.css (in this case)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for the <PerspectiveViewer /> tests, <PerspectiveWorkspace /> tests used themes.css as well though so I removed that line.

await new Promise((r) => setTimeout(r, delay));
}
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If every one of Perspective's tests did this, the test suite would run ~2hrs. You have multiple options available:

  • Listen for perspective-config-update
  • Remove the button click from the tests and just await the workspace calls directly (through an imperative handle for testing only).
  • Await workspace.flush()

master: { sizes: [] },
detail: { sizes: [] },
master: undefined,
detail: { main: null },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not look right

};
}

export function genId(workspace: PerspectiveWorkspaceConfig) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this - IDs are an internal implementation detail, there is no reason to expose this in the public API.

const exists = document.querySelector("body > perspective-indicator");
if (exists) {
return exists as HTMLElement;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This smells like a previous <perspective-workspace> that was disconnected leaked this element, not the responsibility of this method to correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I set the constructed indicator to be a child of <perspective-workspace> so that the element is cleaned up when the workspace is unmounted.

}

export interface PerspectiveWorkspaceConfig<T> {
// TODO: I have made some changes to this interface because it seemed to not reflect reality.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lumino's DockPanel.saveLayout() does not return string identifiers for widgets field, ergo the types for e.g. workspace.save() is now not PerspectiveWorkspaceConfig (it's an anonymous struct type). Add the explicit returns types to save() will show this error.

if (!newTables[t]) {
await viewer.eject();
} else {
await viewer.load(newTables[t]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please batch these and await them all at the end using Promise.all(), so the user does not have to watch a slide show of these elements updating.

async restore(value: PerspectiveWorkspaceConfig<string>) {
async restore(value: PerspectiveWorkspaceConfig) {
// bail out early if there is nothing to restore.
const layout = await this.save();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? This operation is not free and both perspective-viewer and lumino DockPanel should do this internally already.


interface PerspectiveWorkspaceProps {
tables: Record<string, Promise<psp.Table>>;
layout: PerspectiveWorkspaceConfig;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called a config in the API docs, examples and the type name itself. Layout is the type of the layout field within this struct.

@sinistersnare sinistersnare force-pushed the feature/react-workspace branch from b60ab32 to 2401842 Compare August 5, 2025 20:02
if (viewer) {
widget = starting_widgets.find((x) => x.viewer === viewer);
if (widget) {
console.warn("DDD Restore load??? ");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stray debug print

this.getAllWidgets().forEach((widget) => {
const psp_widget = widget as PerspectiveViewerWidget;
if (psp_widget.viewer.getAttribute("table") === name) {
console.warn("DDD Loading...?", table);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stray debug print

const widget = new PerspectiveViewerWidget({ node, viewer });
widget.task = (async () => {
if (table) {
console.warn("DDD Loading B?", table);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stray debug print

@@ -0,0 +1,38 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
Copy link
Contributor

@tomjakubowski tomjakubowski Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this workspace-repro example is to repro a bug case, I imagine you want to remove this for the final PR

@sinistersnare sinistersnare force-pushed the feature/react-workspace branch from d9095a1 to e65f89c Compare August 27, 2025 17:46
@sinistersnare sinistersnare marked this pull request as draft August 27, 2025 17:47
@sinistersnare sinistersnare force-pushed the feature/react-workspace branch from e65f89c to 95403a5 Compare August 27, 2025 17:55
tomjakubowski and others added 2 commits August 28, 2025 11:44
The ObservableMap's delete listener used to be able to "reject"
deletions by returning false.  This is no longer the case; the listener
in Workspace always returns undefined now.  Account for this in
observable map's delete method.

Co-authored by: Davis Silverman <[email protected]>

Signed-off-by: Tom Jakubowski <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants