@marimo-team/islands 0.19.7-dev35 → 0.19.7-dev37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{glide-data-editor-DHsjQhtP.js → glide-data-editor-C3T7HsLi.js} +1 -1
- package/dist/main.js +32 -21
- package/dist/style.css +1 -1
- package/dist/{types-DBsIRhMX.js → types-CzEZ3EWT.js} +1 -1
- package/package.json +1 -1
- package/src/components/data-table/TableActions.tsx +5 -3
- package/src/components/data-table/download-actions.tsx +7 -2
- package/src/components/data-table/pagination.tsx +4 -4
- package/src/components/debug/indicator.tsx +1 -1
- package/src/components/editor/actions/useNotebookActions.tsx +4 -2
- package/src/components/editor/chrome/panels/context-aware-panel/context-aware-panel.tsx +1 -1
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +6 -4
- package/src/components/editor/chrome/wrapper/footer-items/lsp-status.tsx +178 -0
- package/src/components/editor/chrome/wrapper/footer.tsx +1 -1
- package/src/components/editor/chrome/wrapper/sidebar.tsx +1 -1
- package/src/components/editor/controls/Controls.tsx +2 -2
- package/src/components/editor/controls/notebook-menu-dropdown.tsx +1 -1
- package/src/components/editor/file-tree/file-explorer.tsx +1 -1
- package/src/components/editor/header/status.tsx +1 -1
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +13 -4
- package/src/components/home/components.tsx +1 -1
- package/src/components/static-html/static-banner.tsx +1 -1
- package/src/components/ui/dropdown-menu.tsx +1 -1
- package/src/components/ui/table.tsx +1 -1
- package/src/core/export/__tests__/hooks.test.ts +60 -58
- package/src/core/export/hooks.ts +71 -31
- package/src/core/network/types.ts +4 -0
- package/src/css/app/print.css +0 -14
- package/src/utils/__tests__/async-capture-tracker.test.ts +353 -0
- package/src/utils/__tests__/download.test.tsx +5 -114
- package/src/utils/async-capture-tracker.ts +168 -0
- package/src/utils/download.ts +17 -57
- package/src/utils/html-to-image.ts +9 -12
|
@@ -5183,7 +5183,7 @@ var DropdownMenuContent = import_react.forwardRef((e4, t) => {
|
|
|
5183
5183
|
let n = (0, import_compiler_runtime$3.c)(17), r, i, a, o;
|
|
5184
5184
|
n[0] === e4 ? (r = n[1], i = n[2], a = n[3], o = n[4]) : ({ className: r, scrollable: a, sideOffset: o, ...i } = e4, n[0] = e4, n[1] = r, n[2] = i, n[3] = a, n[4] = o);
|
|
5185
5185
|
let s = a === void 0 ? true : a, c = o === void 0 ? 4 : o, l;
|
|
5186
|
-
n[5] !== r || n[6] !== s ? (l = cn(menuContentCommon(), "animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", s && "overflow-auto", r), n[5] = r, n[6] = s, n[7] = l) : l = n[7];
|
|
5186
|
+
n[5] !== r || n[6] !== s ? (l = cn(menuContentCommon(), "animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 print:hidden", s && "overflow-auto", r), n[5] = r, n[6] = s, n[7] = l) : l = n[7];
|
|
5187
5187
|
let u = s ? `calc(var(--radix-dropdown-menu-content-available-height) - ${MAX_HEIGHT_OFFSET}px)` : void 0, d;
|
|
5188
5188
|
n[8] !== i.style || n[9] !== u ? (d = {
|
|
5189
5189
|
...i.style,
|
package/package.json
CHANGED
|
@@ -107,13 +107,13 @@ export const TableActions = <TData,>({
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
return (
|
|
110
|
-
<div className="flex items-center shrink-0 pt-1
|
|
110
|
+
<div className="flex items-center shrink-0 pt-1">
|
|
111
111
|
{onSearchQueryChange && enableSearch && (
|
|
112
112
|
<Tooltip content="Search">
|
|
113
113
|
<Button
|
|
114
114
|
variant="text"
|
|
115
115
|
size="xs"
|
|
116
|
-
className="mb-0"
|
|
116
|
+
className="mb-0 print:hidden"
|
|
117
117
|
onClick={() => setIsSearchEnabled(!isSearchEnabled)}
|
|
118
118
|
>
|
|
119
119
|
<SearchIcon className="w-4 h-4 text-muted-foreground" />
|
|
@@ -125,7 +125,7 @@ export const TableActions = <TData,>({
|
|
|
125
125
|
<Button
|
|
126
126
|
variant="text"
|
|
127
127
|
size="xs"
|
|
128
|
-
className="mb-0"
|
|
128
|
+
className="mb-0 print:hidden"
|
|
129
129
|
onClick={toggleDisplayHeader}
|
|
130
130
|
>
|
|
131
131
|
<ChartSplineIcon className="w-4 h-4 text-muted-foreground" />
|
|
@@ -140,6 +140,7 @@ export const TableActions = <TData,>({
|
|
|
140
140
|
variant="text"
|
|
141
141
|
size="xs"
|
|
142
142
|
onClick={() => togglePanel("row-viewer")}
|
|
143
|
+
className="print:hidden"
|
|
143
144
|
>
|
|
144
145
|
<PanelRightIcon
|
|
145
146
|
className={cn(
|
|
@@ -156,6 +157,7 @@ export const TableActions = <TData,>({
|
|
|
156
157
|
variant="text"
|
|
157
158
|
size="xs"
|
|
158
159
|
onClick={() => togglePanel("column-explorer")}
|
|
160
|
+
className="print:hidden"
|
|
159
161
|
>
|
|
160
162
|
<ChartColumnStacked
|
|
161
163
|
className={cn(
|
|
@@ -87,7 +87,12 @@ export const DownloadAs: React.FC<DownloadActionProps> = (props) => {
|
|
|
87
87
|
const { locale } = useLocale();
|
|
88
88
|
|
|
89
89
|
const button = (
|
|
90
|
-
<Button
|
|
90
|
+
<Button
|
|
91
|
+
data-testid="download-as-button"
|
|
92
|
+
size="xs"
|
|
93
|
+
variant="link"
|
|
94
|
+
className="print:hidden"
|
|
95
|
+
>
|
|
91
96
|
Download <ChevronDownIcon className="w-3 h-3 ml-1" />
|
|
92
97
|
</Button>
|
|
93
98
|
);
|
|
@@ -146,7 +151,7 @@ export const DownloadAs: React.FC<DownloadActionProps> = (props) => {
|
|
|
146
151
|
return (
|
|
147
152
|
<DropdownMenu modal={false}>
|
|
148
153
|
<DropdownMenuTrigger asChild={true}>{button}</DropdownMenuTrigger>
|
|
149
|
-
<DropdownMenuContent side="bottom" className="
|
|
154
|
+
<DropdownMenuContent side="bottom" className="print:hidden">
|
|
150
155
|
{options.map((option) => (
|
|
151
156
|
<DropdownMenuItem
|
|
152
157
|
key={option.label}
|
|
@@ -67,7 +67,7 @@ export const DataTablePagination = <TData,>({
|
|
|
67
67
|
size="xs"
|
|
68
68
|
data-testid="select-all-button"
|
|
69
69
|
variant="link"
|
|
70
|
-
className="h-4"
|
|
70
|
+
className="h-4 print:hidden"
|
|
71
71
|
onMouseDown={Events.preventFocus}
|
|
72
72
|
onClick={() => {
|
|
73
73
|
if (onSelectAllRowsChange) {
|
|
@@ -91,7 +91,7 @@ export const DataTablePagination = <TData,>({
|
|
|
91
91
|
size="xs"
|
|
92
92
|
data-testid="clear-selection-button"
|
|
93
93
|
variant="link"
|
|
94
|
-
className="h-4"
|
|
94
|
+
className="h-4 print:hidden"
|
|
95
95
|
onMouseDown={Events.preventFocus}
|
|
96
96
|
onClick={() => {
|
|
97
97
|
if (!isCellSelection) {
|
|
@@ -139,7 +139,7 @@ export const DataTablePagination = <TData,>({
|
|
|
139
139
|
|
|
140
140
|
const renderPageSizeSelector = () => {
|
|
141
141
|
return (
|
|
142
|
-
<div className="flex items-center gap-1 text-xs whitespace-nowrap mr-1">
|
|
142
|
+
<div className="flex items-center gap-1 text-xs whitespace-nowrap mr-1 print:hidden">
|
|
143
143
|
<Select
|
|
144
144
|
value={pageSize.toString()}
|
|
145
145
|
onValueChange={(value) => table.setPageSize(Number(value))}
|
|
@@ -173,7 +173,7 @@ export const DataTablePagination = <TData,>({
|
|
|
173
173
|
{showPageSizeSelector && renderPageSizeSelector()}
|
|
174
174
|
</div>
|
|
175
175
|
|
|
176
|
-
<div className="flex items-end space-x-2">
|
|
176
|
+
<div className="flex items-end space-x-2 print:hidden">
|
|
177
177
|
<Button
|
|
178
178
|
size="xs"
|
|
179
179
|
variant="outline"
|
|
@@ -5,7 +5,7 @@ export const TailwindIndicator = () => {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
return (
|
|
8
|
-
<div className="fixed bottom-10 right-0 z-50 flex items-center justify-center bg-gray-800 py-[2px] px-1 font-mono text-[10px] text-white font-semibold">
|
|
8
|
+
<div className="fixed bottom-10 right-0 z-50 flex items-center justify-center bg-gray-800 py-[2px] px-1 font-mono text-[10px] text-white font-semibold print:hidden">
|
|
9
9
|
<div className="block sm:hidden">xs</div>
|
|
10
10
|
<div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">
|
|
11
11
|
sm
|
|
@@ -70,6 +70,7 @@ import { createShareableLink } from "@/core/wasm/share";
|
|
|
70
70
|
import { isWasm } from "@/core/wasm/utils";
|
|
71
71
|
import { copyToClipboard } from "@/utils/copy";
|
|
72
72
|
import {
|
|
73
|
+
ADD_PRINTING_CLASS,
|
|
73
74
|
downloadAsPDF,
|
|
74
75
|
downloadBlob,
|
|
75
76
|
downloadHTMLAsImage,
|
|
@@ -219,6 +220,8 @@ export function useNotebookActions() {
|
|
|
219
220
|
await downloadHTMLAsImage({
|
|
220
221
|
element: app,
|
|
221
222
|
filename: document.title,
|
|
223
|
+
// Add body.printing ONLY when converting the whole notebook to a screenshot
|
|
224
|
+
prepare: ADD_PRINTING_CLASS,
|
|
222
225
|
});
|
|
223
226
|
},
|
|
224
227
|
},
|
|
@@ -241,8 +244,7 @@ export function useNotebookActions() {
|
|
|
241
244
|
|
|
242
245
|
const downloadPDF = async (progress: ProgressState) => {
|
|
243
246
|
await updateCellOutputsWithScreenshots({
|
|
244
|
-
takeScreenshots: () =>
|
|
245
|
-
takeScreenshots({ progress, snappy: false }),
|
|
247
|
+
takeScreenshots: () => takeScreenshots({ progress }),
|
|
246
248
|
updateCellOutputs,
|
|
247
249
|
});
|
|
248
250
|
await downloadAsPDF({
|
|
@@ -123,7 +123,7 @@ export const ContextAwarePanel: React.FC = () => {
|
|
|
123
123
|
<>
|
|
124
124
|
<PanelResizeHandle
|
|
125
125
|
onDragging={handleDragging}
|
|
126
|
-
className="resize-handle border-border z-20
|
|
126
|
+
className="resize-handle border-border z-20 print:hidden border-l"
|
|
127
127
|
/>
|
|
128
128
|
<Panel defaultSize={20} minSize={15} maxSize={80}>
|
|
129
129
|
{renderBody()}
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
type PanelType,
|
|
38
38
|
} from "../types";
|
|
39
39
|
import { BackendConnectionStatus } from "./footer-items/backend-status";
|
|
40
|
+
import { LspStatus } from "./footer-items/lsp-status";
|
|
40
41
|
import { PanelsWrapper } from "./panels";
|
|
41
42
|
import { PendingAICells } from "./pending-ai-cells";
|
|
42
43
|
import { useAiPanelTab } from "./useAiPanel";
|
|
@@ -227,7 +228,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
227
228
|
<PanelResizeHandle
|
|
228
229
|
onDragging={handleDragging}
|
|
229
230
|
className={cn(
|
|
230
|
-
"border-border
|
|
231
|
+
"border-border print:hidden z-10",
|
|
231
232
|
isSidebarOpen ? "resize-handle" : "resize-handle-collapsed",
|
|
232
233
|
"vertical",
|
|
233
234
|
)}
|
|
@@ -238,7 +239,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
238
239
|
<PanelResizeHandle
|
|
239
240
|
onDragging={handleDragging}
|
|
240
241
|
className={cn(
|
|
241
|
-
"border-border
|
|
242
|
+
"border-border print:hidden z-20",
|
|
242
243
|
isDeveloperPanelOpen ? "resize-handle" : "resize-handle-collapsed",
|
|
243
244
|
"horizontal",
|
|
244
245
|
)}
|
|
@@ -382,7 +383,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
382
383
|
collapsedSize={0}
|
|
383
384
|
collapsible={true}
|
|
384
385
|
className={cn(
|
|
385
|
-
"dark:bg-(--slate-1)
|
|
386
|
+
"dark:bg-(--slate-1) print:hidden hide-on-fullscreen",
|
|
386
387
|
isSidebarOpen && "border-r border-l border-(--slate-7)",
|
|
387
388
|
)}
|
|
388
389
|
minSize={10}
|
|
@@ -427,7 +428,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
427
428
|
collapsedSize={0}
|
|
428
429
|
collapsible={true}
|
|
429
430
|
className={cn(
|
|
430
|
-
"dark:bg-(--slate-1)
|
|
431
|
+
"dark:bg-(--slate-1) print:hidden hide-on-fullscreen",
|
|
431
432
|
isDeveloperPanelOpen && "border-t",
|
|
432
433
|
)}
|
|
433
434
|
minSize={10}
|
|
@@ -490,6 +491,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
490
491
|
/>
|
|
491
492
|
<div className="border-l border-border h-4 mx-1" />
|
|
492
493
|
<BackendConnectionStatus />
|
|
494
|
+
<LspStatus />
|
|
493
495
|
<div className="flex-1" />
|
|
494
496
|
<Button
|
|
495
497
|
size="xs"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { atom, useAtomValue, useSetAtom } from "jotai";
|
|
4
|
+
import { AlertCircleIcon, CheckCircle2Icon } from "lucide-react";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { Spinner } from "@/components/icons/spinner";
|
|
7
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
8
|
+
import { toast } from "@/components/ui/use-toast";
|
|
9
|
+
import { API } from "@/core/network/api";
|
|
10
|
+
import { connectionAtom } from "@/core/network/connection";
|
|
11
|
+
import type {
|
|
12
|
+
LspHealthResponse,
|
|
13
|
+
LspRestartRequest,
|
|
14
|
+
LspRestartResponse,
|
|
15
|
+
} from "@/core/network/types";
|
|
16
|
+
import { isAppConnected } from "@/core/websocket/connection-utils";
|
|
17
|
+
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
18
|
+
import { useInterval } from "@/hooks/useInterval";
|
|
19
|
+
|
|
20
|
+
const CHECK_LSP_HEALTH_INTERVAL_MS = 60_000;
|
|
21
|
+
|
|
22
|
+
export const lspHealthAtom = atom<LspHealthResponse | null>(null);
|
|
23
|
+
|
|
24
|
+
export const LspStatus: React.FC = () => {
|
|
25
|
+
const connection = useAtomValue(connectionAtom).state;
|
|
26
|
+
const setLspHealth = useSetAtom(lspHealthAtom);
|
|
27
|
+
|
|
28
|
+
const { isFetching, data, refetch } = useAsyncData(async () => {
|
|
29
|
+
if (!isAppConnected(connection)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const health = await API.get<LspHealthResponse>("/lsp/health");
|
|
35
|
+
setLspHealth(health);
|
|
36
|
+
return health;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}, [connection]);
|
|
41
|
+
|
|
42
|
+
useInterval(refetch, {
|
|
43
|
+
delayMs: isAppConnected(connection) ? CHECK_LSP_HEALTH_INTERVAL_MS : null,
|
|
44
|
+
whenVisible: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const handleRestart = async () => {
|
|
48
|
+
try {
|
|
49
|
+
const result = await API.post<LspRestartRequest, LspRestartResponse>(
|
|
50
|
+
"/lsp/restart",
|
|
51
|
+
{},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (result.success) {
|
|
55
|
+
toast({
|
|
56
|
+
title: "LSP Servers Restarted",
|
|
57
|
+
description:
|
|
58
|
+
result.restarted.length > 0
|
|
59
|
+
? `Restarted: ${result.restarted.join(", ")}`
|
|
60
|
+
: "No servers needed restart",
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
toast({
|
|
64
|
+
variant: "danger",
|
|
65
|
+
title: "LSP Restart Failed",
|
|
66
|
+
description: Object.entries(result.errors ?? {})
|
|
67
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
68
|
+
.join("\n"),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Refresh health status
|
|
73
|
+
refetch();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
toast({
|
|
76
|
+
variant: "danger",
|
|
77
|
+
title: "LSP Restart Failed",
|
|
78
|
+
description: error instanceof Error ? error.message : "Unknown error",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Don't show if no LSP servers are configured
|
|
84
|
+
if (!data || data.servers.length === 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const getStatusIcon = () => {
|
|
89
|
+
if (isFetching) {
|
|
90
|
+
return <Spinner size="small" />;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!data) {
|
|
94
|
+
return <AlertCircleIcon className="w-4 h-4" />;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
switch (data.status) {
|
|
98
|
+
case "healthy":
|
|
99
|
+
return <CheckCircle2Icon className="w-4 h-4 text-(--green-9)" />;
|
|
100
|
+
case "degraded":
|
|
101
|
+
return <AlertCircleIcon className="w-4 h-4 text-(--yellow-11)" />;
|
|
102
|
+
case "unhealthy":
|
|
103
|
+
return <AlertCircleIcon className="w-4 h-4 text-(--yellow-11)" />;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const getServerStatusDisplay = (
|
|
108
|
+
status: "starting" | "running" | "stopped" | "crashed" | "unresponsive",
|
|
109
|
+
lastPingMs: number | null | undefined,
|
|
110
|
+
) => {
|
|
111
|
+
switch (status) {
|
|
112
|
+
case "running":
|
|
113
|
+
return `✓ OK${lastPingMs == null ? "" : ` (${lastPingMs.toFixed(0)}ms)`}`;
|
|
114
|
+
case "starting":
|
|
115
|
+
return "⋯ Starting";
|
|
116
|
+
case "stopped":
|
|
117
|
+
return "✗ Stopped";
|
|
118
|
+
case "crashed":
|
|
119
|
+
return "✗ Crashed";
|
|
120
|
+
case "unresponsive":
|
|
121
|
+
return "✗ Not responding";
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const getServerStatusColor = (
|
|
126
|
+
status: "starting" | "running" | "stopped" | "crashed" | "unresponsive",
|
|
127
|
+
) => {
|
|
128
|
+
switch (status) {
|
|
129
|
+
case "running":
|
|
130
|
+
return "text-(--green-9)";
|
|
131
|
+
case "starting":
|
|
132
|
+
return "text-(--yellow-11)";
|
|
133
|
+
default:
|
|
134
|
+
return "text-(--red-9)";
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const tooltipContent = (
|
|
139
|
+
<div className="text-sm">
|
|
140
|
+
<b>LSP Status</b>
|
|
141
|
+
<div className="mt-1 text-xs space-y-1">
|
|
142
|
+
{data?.servers.map((server) => (
|
|
143
|
+
<div key={server.serverId} className="flex justify-between gap-2">
|
|
144
|
+
<span>{server.serverId}</span>
|
|
145
|
+
<span className={getServerStatusColor(server.status)}>
|
|
146
|
+
{getServerStatusDisplay(server.status, server.lastPingMs)}
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
))}
|
|
150
|
+
</div>
|
|
151
|
+
{data?.status === "healthy" ? null : (
|
|
152
|
+
<div className="mt-2 text-xs text-muted-foreground">
|
|
153
|
+
Click to restart failed servers
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const handleClick = () => {
|
|
160
|
+
if (data?.status !== "healthy") {
|
|
161
|
+
void handleRestart();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<Tooltip content={tooltipContent} data-testid="footer-lsp-status">
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
onClick={handleClick}
|
|
170
|
+
className="p-1 hover:bg-accent rounded flex items-center gap-1.5 text-xs text-muted-foreground"
|
|
171
|
+
data-testid="lsp-status"
|
|
172
|
+
>
|
|
173
|
+
{getStatusIcon()}
|
|
174
|
+
<span>LSP</span>
|
|
175
|
+
</button>
|
|
176
|
+
</Tooltip>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
@@ -57,7 +57,7 @@ export const Footer: React.FC = () => {
|
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
return (
|
|
60
|
-
<footer className="h-10 py-1 gap-1 bg-background flex items-center text-muted-foreground text-md pl-2 pr-1 border-t border-border select-none
|
|
60
|
+
<footer className="h-10 py-1 gap-1 bg-background flex items-center text-muted-foreground text-md pl-2 pr-1 border-t border-border select-none print:hidden text-sm z-50 hide-on-fullscreen overflow-x-auto overflow-y-hidden scrollbar-thin">
|
|
61
61
|
<FooterItem
|
|
62
62
|
className="h-full"
|
|
63
63
|
tooltip={
|
|
@@ -115,7 +115,7 @@ export const Sidebar: React.FC = () => {
|
|
|
115
115
|
]);
|
|
116
116
|
|
|
117
117
|
return (
|
|
118
|
-
<div className="h-full pt-4 pb-1 px-1 flex flex-col items-start text-muted-foreground text-md select-none
|
|
118
|
+
<div className="h-full pt-4 pb-1 px-1 flex flex-col items-start text-muted-foreground text-md select-none text-sm z-50 dark:bg-background print:hidden hide-on-fullscreen">
|
|
119
119
|
<ReorderableList<PanelDescriptor>
|
|
120
120
|
value={sidebarItems}
|
|
121
121
|
setValue={handleSetSidebarItems}
|
|
@@ -212,7 +212,7 @@ const StopControlButton = ({
|
|
|
212
212
|
};
|
|
213
213
|
|
|
214
214
|
const topRightControls =
|
|
215
|
-
"absolute top-3 right-5 m-0 flex items-center gap-2 min-h-[28px]
|
|
215
|
+
"absolute top-3 right-5 m-0 flex items-center gap-2 min-h-[28px] print:hidden pointer-events-auto z-30";
|
|
216
216
|
|
|
217
217
|
const bottomRightControls =
|
|
218
|
-
"absolute bottom-5 right-5 flex flex-col gap-2 items-center
|
|
218
|
+
"absolute bottom-5 right-5 flex flex-col gap-2 items-center print:hidden pointer-events-auto z-30";
|
|
@@ -111,7 +111,7 @@ export const NotebookMenuDropdown: React.FC<Props> = ({
|
|
|
111
111
|
<DropdownMenuTrigger asChild={true} disabled={disabled}>
|
|
112
112
|
{button}
|
|
113
113
|
</DropdownMenuTrigger>
|
|
114
|
-
<DropdownMenuContent align="end" className="
|
|
114
|
+
<DropdownMenuContent align="end" className="print:hidden w-[240px]">
|
|
115
115
|
{actions.map((action) => {
|
|
116
116
|
if (action.hidden || action.redundant) {
|
|
117
117
|
return null;
|
|
@@ -510,7 +510,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
510
510
|
return (
|
|
511
511
|
<DropdownMenuContent
|
|
512
512
|
align="end"
|
|
513
|
-
className="
|
|
513
|
+
className="print:hidden w-[220px]"
|
|
514
514
|
onClick={(e) => e.stopPropagation()}
|
|
515
515
|
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
516
516
|
>
|
|
@@ -34,7 +34,7 @@ export const StatusOverlay: React.FC<{
|
|
|
34
34
|
);
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
const topLeftStatus = "
|
|
37
|
+
const topLeftStatus = "print:hidden pointer-events-auto hover:cursor-pointer";
|
|
38
38
|
|
|
39
39
|
const DisconnectedIcon = () => (
|
|
40
40
|
<Tooltip content="App disconnected">
|
|
@@ -41,7 +41,11 @@ import { downloadAsHTML } from "@/core/static/download-html";
|
|
|
41
41
|
import { isStaticNotebook } from "@/core/static/static-state";
|
|
42
42
|
import { isWasm } from "@/core/wasm/utils";
|
|
43
43
|
import { cn } from "@/utils/cn";
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
ADD_PRINTING_CLASS,
|
|
46
|
+
downloadBlob,
|
|
47
|
+
downloadHTMLAsImage,
|
|
48
|
+
} from "@/utils/download";
|
|
45
49
|
import { Filenames } from "@/utils/filenames";
|
|
46
50
|
import { FloatingOutline } from "../../chrome/panels/outline/floating-outline";
|
|
47
51
|
import { cellDomProps } from "../../common";
|
|
@@ -185,7 +189,12 @@ const ActionButtons: React.FC<{
|
|
|
185
189
|
if (!app) {
|
|
186
190
|
return;
|
|
187
191
|
}
|
|
188
|
-
await downloadHTMLAsImage({
|
|
192
|
+
await downloadHTMLAsImage({
|
|
193
|
+
element: app,
|
|
194
|
+
filename: document.title,
|
|
195
|
+
// Add body.printing ONLY when converting the whole notebook to a screenshot
|
|
196
|
+
prepare: ADD_PRINTING_CLASS,
|
|
197
|
+
});
|
|
189
198
|
};
|
|
190
199
|
|
|
191
200
|
const handleDownloadAsHTML = async () => {
|
|
@@ -271,7 +280,7 @@ const ActionButtons: React.FC<{
|
|
|
271
280
|
<div
|
|
272
281
|
data-testid="notebook-actions-dropdown"
|
|
273
282
|
className={cn(
|
|
274
|
-
"right-0 top-0 z-50 m-4
|
|
283
|
+
"right-0 top-0 z-50 m-4 print:hidden flex gap-2",
|
|
275
284
|
// If the notebook is static, we have a banner at the top, so
|
|
276
285
|
// we can't use fixed positioning. Ideally this is sticky, but the
|
|
277
286
|
// current dom structure makes that difficult.
|
|
@@ -284,7 +293,7 @@ const ActionButtons: React.FC<{
|
|
|
284
293
|
<MoreHorizontalIcon className="w-4 h-4" />
|
|
285
294
|
</Button>
|
|
286
295
|
</DropdownMenuTrigger>
|
|
287
|
-
<DropdownMenuContent align="end" className="
|
|
296
|
+
<DropdownMenuContent align="end" className="print:hidden w-[220px]">
|
|
288
297
|
{actions}
|
|
289
298
|
</DropdownMenuContent>
|
|
290
299
|
</DropdownMenu>
|
|
@@ -80,7 +80,7 @@ export const OpenTutorialDropDown: React.FC = () => {
|
|
|
80
80
|
<CaretDownIcon className="w-3 h-3 ml-1" />
|
|
81
81
|
</Button>
|
|
82
82
|
</DropdownMenuTrigger>
|
|
83
|
-
<DropdownMenuContent side="bottom" align="end" className="
|
|
83
|
+
<DropdownMenuContent side="bottom" align="end" className="print:hidden">
|
|
84
84
|
{Objects.entries(TUTORIALS).map(
|
|
85
85
|
([tutorialId, [label, Icon, description]]) => (
|
|
86
86
|
<DropdownMenuItem
|
|
@@ -36,7 +36,7 @@ export const StaticBanner: React.FC = () => {
|
|
|
36
36
|
|
|
37
37
|
return (
|
|
38
38
|
<div
|
|
39
|
-
className="px-4 py-2 bg-(--sky-2) border-b border-(--sky-7) text-(--sky-11) flex justify-between items-center gap-4
|
|
39
|
+
className="px-4 py-2 bg-(--sky-2) border-b border-(--sky-7) text-(--sky-11) flex justify-between items-center gap-4 print:hidden text-sm"
|
|
40
40
|
data-testid="static-notebook-banner"
|
|
41
41
|
>
|
|
42
42
|
<span>
|
|
@@ -83,7 +83,7 @@ const DropdownMenuContent = React.forwardRef<
|
|
|
83
83
|
sideOffset={sideOffset}
|
|
84
84
|
className={cn(
|
|
85
85
|
menuContentCommon(),
|
|
86
|
-
"animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
86
|
+
"animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 print:hidden",
|
|
87
87
|
scrollable && "overflow-auto",
|
|
88
88
|
className,
|
|
89
89
|
)}
|
|
@@ -7,7 +7,7 @@ const Table = React.forwardRef<
|
|
|
7
7
|
HTMLTableElement,
|
|
8
8
|
React.HTMLAttributes<HTMLTableElement>
|
|
9
9
|
>(({ className, ...props }, ref) => (
|
|
10
|
-
<div className="w-full overflow-auto scrollbar-thin flex-1">
|
|
10
|
+
<div className="w-full overflow-auto scrollbar-thin flex-1 print:overflow-hidden">
|
|
11
11
|
<table
|
|
12
12
|
ref={ref}
|
|
13
13
|
className={cn("w-full caption-bottom text-sm", className)}
|