Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/md/how_to/javascript/save_restore.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,23 @@ Chrome developer console:
// Copy to clipboard
copy(await document.querySelector("perspective-viewer").save());
```

## Multi-panel viewers

`save()` and `restore()` operate on a _single_ panel — the _active_ one by
default, or a specific panel via their optional `{ panel }` selector (e.g.
`await elem.save({ panel: "my-panel" })`). If `restore()`'s `panel` names no
existing panel, a new panel is created with that id.

A `<perspective-viewer>` may host multiple panels. To serialize or restore the
_whole element_ — every panel plus the layout and cross-filter state — use
`saveWorkspace()` and `restoreWorkspace()` instead:

```javascript
const workspace_token = await elem.saveWorkspace();
await elem.restoreWorkspace(workspace_token);
```

A `saveWorkspace()` token is a `WorkspaceConfig` (`{ version, layout, panels,
... }`), not a `ViewerConfig` — passing it to the single-panel `restore()` will
_not_ restore the layout (its `panels`/`layout` keys are ignored).
4 changes: 2 additions & 2 deletions examples/blocks/src/dataset/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
const make_del_click_callback = (state) => async () => {
if (state.table) {
for (const name of window.psp_workspace.getPanelNames()) {
const cfg = await window.psp_workspace.savePanel(name);
const cfg = await window.psp_workspace.save({ panel: name });
if (cfg.table === "superstore") {
window.psp_workspace.removePanel(name);
}
Expand Down Expand Up @@ -220,7 +220,7 @@
})();

await window.psp_workspace.load(client);
await window.psp_workspace.restore(await fetch("layout.json").then((x) => x.json()));
await window.psp_workspace.restoreWorkspace(await fetch("layout.json").then((x) => x.json()));
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion examples/blocks/src/evictions/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
const layout = await layout_json.json();

el.load(await evictions);
el.restore(layout);
el.restoreWorkspace(layout);
}

load();
Expand Down
2 changes: 1 addition & 1 deletion examples/blocks/src/movies/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
let req = await fetch("layout.json");
let layout = await req.json();
await window.workspace.load(worker);
await window.workspace.restore(layout);
await window.workspace.restoreWorkspace(layout);
})();
</script>
</body>
Expand Down
6 changes: 3 additions & 3 deletions examples/blocks/src/nypd/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ if (LAYOUTS == undefined) {

const layout_names = Object.keys(LAYOUTS);
let selected_layout = LAYOUTS[layout_names[0]];
await window.workspace.restore(selected_layout);
await window.workspace.restoreWorkspace(selected_layout);

function set_layout_options() {
const layout_names = Object.keys(LAYOUTS);
Expand All @@ -117,7 +117,7 @@ window.layouts.addEventListener("change", async () => {
return;
}

await window.workspace.restore(LAYOUTS[window.layouts.value]);
await window.workspace.restoreWorkspace(LAYOUTS[window.layouts.value]);
window.name_input.value = window.layouts.value;
});

Expand Down Expand Up @@ -150,7 +150,7 @@ window.reset.addEventListener("click", () => {
});

window.save.addEventListener("click", async () => {
const token = await window.workspace.save();
const token = await window.workspace.saveWorkspace();
const new_name = window.name_input.value;
LAYOUTS[new_name] = token;
set_layout_options();
Expand Down
2 changes: 1 addition & 1 deletion examples/blocks/src/olympics/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
const arrow = await response.arrayBuffer();
await worker.table(arrow, { name: "olympics" });
window.workspace.load(worker);
await window.workspace.restore({
await window.workspace.restoreWorkspace({
layout: {
type: "tab-layout",
tabs: ["PERSPECTIVE_GENERATED_ID_1", "PERSPECTIVE_GENERATED_ID_0"],
Expand Down
2 changes: 1 addition & 1 deletion examples/blocks/src/superstore/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
await worker.table(buffer, { name: "superstore" });
window.workspace.load(worker);
const layout = await get_layout();
await window.workspace.restore(layout);
await window.workspace.restoreWorkspace(layout);
</script>
</body>
</html>
3 changes: 2 additions & 1 deletion packages/react/src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ export function usePspListener<A>(
el: HTMLElement | undefined | null,
event: string,
cb?: (x: A) => void,
map: (e: CustomEvent) => A = (e) => e.detail,
) {
React.useEffect(() => {
if (!cb || !el) return;
const ctx = new AbortController();
const callback = (e: Event) => cb((e as CustomEvent).detail);
const callback = (e: Event) => cb(map(e as CustomEvent));
el?.addEventListener(event, callback, { signal: ctx.signal });
return () => ctx.abort();
}, [el, cb]);
Expand Down
42 changes: 31 additions & 11 deletions packages/react/src/viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,42 @@ function PerspectiveViewerImpl(props: PerspectiveViewerProps) {
}, [viewer]);

React.useEffect(() => {
if (props.client) {
viewer?.load(props.client);
} else {
viewer?.eject();
if (!viewer) {
return;
}
}, [viewer, props.client]);

React.useEffect(() => {
if (props.client && props.config) {
viewer?.restore(props.config);
}
let cancelled = false;
(async () => {
if (!props.client) {
await viewer.eject();
return;
}

await viewer.load(props.client);
if (cancelled || !props.config) {
return;
}

if ("panels" in props.config) {
await viewer.restoreWorkspace(props.config);
} else {
await viewer.restore(props.config);
}
})();

return () => {
cancelled = true;
};
}, [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);
usePspListener(
viewer,
"perspective-config-update",
props.onConfigUpdate,
(e) => e.detail.getConfig(),
);

return (
<perspective-viewer
Expand All @@ -62,7 +82,7 @@ function PerspectiveViewerImpl(props: PerspectiveViewerProps) {
*/
export interface PerspectiveViewerProps {
client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
config?: pspViewer.ViewerConfigUpdate;
config?: pspViewer.ViewerConfigUpdate | pspViewer.WorkspaceConfigUpdate;
onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
Expand Down
6 changes: 3 additions & 3 deletions packages/viewer-charts/src/ts/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,11 @@ export class HTMLPerspectiveViewerWebGLPluginElement
// getClient()/getTable() resolve element-wide in a multi-panel viewer),
// and to tag the dispatched interaction events with their source panel.
const panel = this.getAttribute("slot") ?? undefined;
const client = await viewer.getClientPanel(undefined, panel);
const client = await viewer.getClient({ panel });
const viewer_class = customElements.get("perspective-viewer");
const clientWasm = viewer_class.get_wasm_module();
const clientWorkerURL = viewer_class.get_worker_url();
const table = await viewer?.getTablePanel?.(undefined, panel);
const table = await viewer?.getTable?.({ panel });
const tableName: string | undefined = table
? await table.get_name()
: undefined;
Expand Down Expand Up @@ -586,7 +586,7 @@ export class HTMLPerspectiveViewerWebGLPluginElement
return;
}

const viewerConfig = await viewer.getViewConfigPanel(panel);
const viewerConfig = await viewer.getViewConfig({ panel });
await renderer.loadAndRender({
viewerConfig: {
group_by: viewerConfig?.group_by ?? [],
Expand Down
10 changes: 4 additions & 6 deletions packages/viewer-charts/test/ts/context-leak.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,10 @@ async function toggleSecondViewer(page: Page, cfg: object): Promise<void> {
await page.evaluate(
async ({ cfg, tableName }) => {
const worker = (window as any).__TEST_WORKER__;
const table = await worker.open_table(tableName);
const v = document.createElement("perspective-viewer");
document.body.appendChild(v);
await v.load(table);
await v.restore(cfg);
await v.load(worker);
await v.restore({ ...(cfg as any), table: tableName });
},
{ cfg, tableName: TABLE_NAME },
);
Expand All @@ -69,11 +68,10 @@ async function toggleSecondViewerRacingInit(
await page.evaluate(
async ({ cfg, tableName }) => {
const worker = (window as any).__TEST_WORKER__;
const table = await worker.open_table(tableName);
const v = document.createElement("perspective-viewer");
document.body.appendChild(v);
await v.load(table);
v.restore(cfg).catch(() => {});
await v.load(worker);
v.restore({ ...(cfg as any), table: tableName }).catch(() => {});
await new Promise<void>((resolve) =>
setTimeout(() => {
v.remove();
Expand Down
6 changes: 3 additions & 3 deletions packages/viewer-charts/test/ts/tab-stack-activation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async function setupStack(page: Page): Promise<void> {
await page.evaluate(
async ({ table }) => {
const viewer = document.querySelector("perspective-viewer") as any;
await viewer.restore({
await viewer.restoreWorkspace({
layout: {
type: "tab-layout",
tabs: ["one", "two"],
Expand Down Expand Up @@ -179,7 +179,7 @@ test.describe("Tab-stack chart activation", () => {
const active = await page.evaluate(async () => {
const viewer = document.querySelector("perspective-viewer") as any;
const active = viewer.getActivePanel();
await viewer.restorePanel({ plugin: "Y Line" }, active);
await viewer.restore({ plugin: "Y Line" }, { panel: active });
await viewer.flush();
return active;
});
Expand All @@ -190,7 +190,7 @@ test.describe("Tab-stack chart activation", () => {
await hookDispatchCounters(page);
await page.evaluate(async (active) => {
const viewer = document.querySelector("perspective-viewer") as any;
await viewer.restorePanel({ plugin: "Y Line" }, active);
await viewer.restore({ plugin: "Y Line" }, { panel: active });
await viewer.flush();
}, active);

Expand Down
20 changes: 18 additions & 2 deletions packages/viewer-datagrid/src/ts/custom_elements/datagrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import { PRIVATE_PLUGIN_SYMBOL } from "../model/index.js";
import {
PRIVATE_PLUGIN_SYMBOL,
readThemeStyle,
save_column_size_overrides,
restore_column_size_overrides,
} from "../model/index.js";
import { activate } from "../plugin/activate.js";
import { restore } from "../plugin/restore.js";
import { save } from "../plugin/save.js";
Expand Down Expand Up @@ -270,7 +275,18 @@ export class HTMLPerspectiveViewerDatagridPluginElement
}
}

restyle() {}
restyle(): void {
if (!this.model || !this.isConnected) {
return;
}

Object.assign(this.model, readThemeStyle(this.regular_table));
if (this._initialized) {
const old_sizes = save_column_size_overrides.call(this);
this.regular_table.resetAutoSize();
restore_column_size_overrides.call(this, old_sizes);
}
}

delete(): void {
this.disconnectedCallback();
Expand Down
1 change: 0 additions & 1 deletion packages/viewer-datagrid/src/ts/custom_elements/toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ export class HTMLPerspectiveViewerDatagridToolbarElement extends HTMLElement {
plugin._edit_button.addEventListener("click", () => {
toggle_edit_mode.call(plugin);
plugin.regular_table.draw();
viewer.dispatchEvent(new Event("perspective-config-update"));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,9 @@ export function createMousedownListener(
const meta = regularTable.getMeta(target);
const column_name =
meta?.column_header?.[model._config.split_by.length];
await viewer.toggleColumnSettingsPanel(
`${column_name}`,
model._panel,
);
await viewer.toggleColumnSettings(`${column_name}`, {
panel: model._panel,
});
} else if (target.classList.contains("psp-sort-enabled")) {
sortHandler(model, regularTable, viewer, mouseEvent, target);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ function set_psp_selection(
viewport.end_row = y1 + 1;
}

viewer.setSelectionPanel(viewport, datagrid.model!._panel);
viewer.setSelection(viewport, { panel: datagrid.model!._panel });
}

type CellPredicate = (meta: CellMetadataBody, area: SelectionArea) => boolean;
Expand Down Expand Up @@ -406,7 +406,7 @@ export const applyMouseAreaSelections = (
} else {
(
datagrid.parentElement as HTMLPerspectiveViewerElement
).setSelectionPanel(undefined, datagrid.model!._panel);
).setSelection(undefined, { panel: datagrid.model!._panel });
const tds = table.querySelectorAll("tbody td");
for (const td of tds) {
td.classList.remove(className);
Expand Down
2 changes: 1 addition & 1 deletion packages/viewer-datagrid/src/ts/event_handlers/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function sortHandler(

const abs = event.shiftKey;
const sort = sort_method(model, `${column_name}`, abs);
await viewer.restorePanel({ sort }, model._panel);
await viewer.restore({ sort }, { panel: model._panel });
}

export function append_sort(
Expand Down
Loading
Loading