diff --git a/web-admin/src/features/dashboards/share/ShareDashboardPopover.svelte b/web-admin/src/features/dashboards/share/ShareDashboardPopover.svelte index c0fc661ff5e..793b877fa31 100644 --- a/web-admin/src/features/dashboards/share/ShareDashboardPopover.svelte +++ b/web-admin/src/features/dashboards/share/ShareDashboardPopover.svelte @@ -17,10 +17,13 @@ import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte"; import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte"; import { featureFlags } from "@rilldata/web-common/features/feature-flags"; + import { getCanvasStoreUnguarded } from "@rilldata/web-common/features/canvas/state-managers/state-managers"; + import type { LayoutBlock } from "@rilldata/web-common/features/canvas/stores/tab-group"; import ExportDashboardForm from "@rilldata/web-common/features/exports/pdf/ExportDashboardForm.svelte"; import { exportCanvasPdf } from "@rilldata/web-common/features/exports/pdf/export-canvas-pdf"; import type { PdfExportRunOptions } from "@rilldata/web-common/features/exports/pdf/types"; import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; + import { readable } from "svelte/store"; export let createMagicAuthTokens: boolean; // Provide canvas identifiers to enable the "PDF" tab (canvas dashboards only). @@ -42,6 +45,18 @@ exportCanvasPdf({ canvasName: name, instanceId: id, ...o }); } + const emptyLayout = readable([]); + // Resolved when the popover opens (the canvas store may not yet be + // initialized when this header button first mounts). + $: layoutStore = + (isOpen && + canvasName && + instanceId && + getCanvasStoreUnguarded(canvasName, instanceId)?.canvasEntity.layout) || + emptyLayout; + // Gates the all-tabs/active-tab option in the PDF export form. + $: hasTabGroups = $layoutStore.some((block) => block.kind === "tab-group"); + function onCopy() { navigator.clipboard.writeText(window.location.href).catch(console.error); copied = true; @@ -106,6 +121,7 @@ (isOpen = false)} /> diff --git a/web-common/src/features/canvas/stores/canvas-entity.ts b/web-common/src/features/canvas/stores/canvas-entity.ts index 686517b3f2b..43db28f6548 100644 --- a/web-common/src/features/canvas/stores/canvas-entity.ts +++ b/web-common/src/features/canvas/stores/canvas-entity.ts @@ -126,6 +126,10 @@ export class CanvasEntity { // data query (see BaseCanvasComponent.dataEnabled) so all components fetch and // render regardless of the lazy-load latch, without mutating that latch. exportMode = writable(false); + // Whether the PDF export renders every tab of each tab group (stacked in strip + // order) or only each group's active tab. Set by exportCanvasPdf alongside + // exportMode; read by CanvasPdfExportView. + exportAllTabs = writable(true); // This is to skip processing the spec the first time the store updates with a value // We've already called it as part of the constructor diff --git a/web-common/src/features/exports/pdf/CanvasPdfExportTab.svelte b/web-common/src/features/exports/pdf/CanvasPdfExportTab.svelte new file mode 100644 index 00000000000..8f715e6c07d --- /dev/null +++ b/web-common/src/features/exports/pdf/CanvasPdfExportTab.svelte @@ -0,0 +1,56 @@ + + +
+

{tab.displayName}

+
+ +{#each $rows as row, rowIndex (rowIndex)} + +{/each} + + diff --git a/web-common/src/features/exports/pdf/CanvasPdfExportTabGroup.svelte b/web-common/src/features/exports/pdf/CanvasPdfExportTabGroup.svelte new file mode 100644 index 00000000000..7a1feaf929d --- /dev/null +++ b/web-common/src/features/exports/pdf/CanvasPdfExportTabGroup.svelte @@ -0,0 +1,32 @@ + + +{#each exportTabs as tab (tab.name)} + +{/each} diff --git a/web-common/src/features/exports/pdf/CanvasPdfExportView.svelte b/web-common/src/features/exports/pdf/CanvasPdfExportView.svelte index 4a11a783165..22cfb67cc5f 100644 --- a/web-common/src/features/exports/pdf/CanvasPdfExportView.svelte +++ b/web-common/src/features/exports/pdf/CanvasPdfExportView.svelte @@ -2,23 +2,28 @@ import { getCanvasStore } from "@rilldata/web-common/features/canvas/state-managers/state-managers"; import StaticCanvasRow from "@rilldata/web-common/features/canvas/StaticCanvasRow.svelte"; import CanvasPdfExportHeader from "./CanvasPdfExportHeader.svelte"; + import CanvasPdfExportTabGroup from "./CanvasPdfExportTabGroup.svelte"; // A dedicated, read-only render of the whole canvas used solely as the PDF - // capture target. It reuses the live dashboard's row/component rendering off + // capture target. It reuses the live dashboard's block/component rendering off // the same canvas store, but renders every component eagerly (lazy={false}) so // the capture is complete without touching the live dashboard's lazy-load - // state. See captureCanvasBlocks, which reads from #canvas-pdf-export-view. + // state. Tab groups are flattened for print: every exported tab (all tabs, or + // only each group's active one — see exportAllTabs) renders as a label band + // followed by its rows, stacked in strip order. + // See captureCanvasBlocks, which reads from #canvas-pdf-export-view. export let canvasName: string; export let instanceId: string; // Render width in px; matches the on-screen content width for fidelity. export let width: number; $: ({ - canvasEntity: { componentsStore, _rows }, + canvasEntity: { componentsStore, _rows, layout, exportAllTabs }, } = getCanvasStore(canvasName, instanceId)); $: components = $componentsStore; $: rows = $_rows; + $: blocks = $layout;
- {#each rows as row, rowIndex (rowIndex)} - + {#each blocks as block (block.kind === "tab-group" ? `g-${block.group.name}` : `r-${block.rowIndex}`)} + {#if block.kind === "tab-group"} + + {:else if rows[block.freeRowIndex]} + + {/if} {/each}
@@ -52,4 +66,12 @@ container-type: inline-size; container-name: canvas-container; } + + /* Scrollbars on overflowing components (tables, legends) would be rasterized + into the capture. Applied per element via `*` because html-to-image inlines + each element's computed styles into its clone: computed scrollbar-width + survives the cloning, whereas ::-webkit-scrollbar stylesheet rules do not. */ + #canvas-pdf-export-view :global(*) { + scrollbar-width: none; + } diff --git a/web-common/src/features/exports/pdf/ExportDashboardForm.svelte b/web-common/src/features/exports/pdf/ExportDashboardForm.svelte index d10ae3253d6..936c8b278cb 100644 --- a/web-common/src/features/exports/pdf/ExportDashboardForm.svelte +++ b/web-common/src/features/exports/pdf/ExportDashboardForm.svelte @@ -13,8 +13,12 @@ // in the future. export let runExport: (opts: PdfExportRunOptions) => Promise; export let onComplete: () => void = () => {}; + // Shown only when the dashboard has tab groups: whether to export every tab + // or only each group's active tab. + export let showTabOptions = false; let includeFilters = true; + let allTabs = true; let exporting = false; let progressLabel = m.export_pdf_button(); @@ -34,6 +38,7 @@ try { await runExport({ includeFilters, + allTabs, onProgress: ({ phase }) => { progressLabel = PROGRESS_COPY[phase]; }, @@ -66,6 +71,14 @@ label={m.export_pdf_include_filters()} /> + {#if showTabOptions} + + {/if} +