@marimo-team/islands 0.23.4 → 0.23.5-dev10
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/{slide-form-CYU9AOO4.js → code-visibility-CXDNwlYU.js} +849 -784
- package/dist/main.js +1039 -1073
- package/dist/{reveal-component-DK-5_Ei4.js → reveal-component-CEDMcYw2.js} +575 -527
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/data-table/export-actions.tsx +15 -1
- package/src/components/editor/file-tree/file-explorer.tsx +38 -8
- package/src/components/editor/file-tree/file-operations.tsx +19 -2
- package/src/components/editor/notebook-cell.tsx +33 -23
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +3 -5
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +2 -17
- package/src/components/slides/reveal-component.tsx +159 -47
- package/src/components/slides/reveal-slides.css +8 -0
- package/src/components/slides/slide-cell-view.tsx +182 -0
- package/src/components/slides/slide-form.tsx +2 -0
- package/src/core/cells/utils.ts +45 -0
- package/src/core/islands/stubs/slide-cell-view.tsx +30 -0
- package/src/core/meta/__tests__/code-visibility.test.tsx +141 -0
- package/src/core/meta/code-visibility.ts +48 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
startTransition,
|
|
4
5
|
useEffect,
|
|
5
6
|
useMemo,
|
|
6
7
|
useRef,
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
Fragment as ReactFragment,
|
|
9
10
|
} from "react";
|
|
10
11
|
import useEvent from "react-use-event-hook";
|
|
11
|
-
import { ExpandIcon, EyeOffIcon } from "lucide-react";
|
|
12
|
+
import { CodeIcon, ExpandIcon, EyeOffIcon } from "lucide-react";
|
|
12
13
|
import { Deck, Fragment, Slide, Stack } from "@revealjs/react";
|
|
13
14
|
import { Slide as CellOutputSlide } from "@/components/slides/slide";
|
|
14
15
|
import { Button } from "@/components/ui/button";
|
|
@@ -34,6 +35,13 @@ import {
|
|
|
34
35
|
DEFAULT_SLIDE_TYPE,
|
|
35
36
|
SlideSidebar,
|
|
36
37
|
} from "./slide-form";
|
|
38
|
+
import {
|
|
39
|
+
SlideCellReadOnlyView,
|
|
40
|
+
SlideCellView,
|
|
41
|
+
} from "@/components/slides/slide-cell-view";
|
|
42
|
+
import { cn } from "@/utils/cn";
|
|
43
|
+
import { isIslands } from "@/core/islands/utils";
|
|
44
|
+
import { useNotebookCodeAvailable } from "@/core/meta/code-visibility";
|
|
37
45
|
import type { AppMode } from "@/core/mode";
|
|
38
46
|
|
|
39
47
|
const ASPECT_RATIO = 16 / 9;
|
|
@@ -118,21 +126,41 @@ function triggerResize(deck: RevealApi | null) {
|
|
|
118
126
|
|
|
119
127
|
const SubslideView = ({
|
|
120
128
|
subslide,
|
|
129
|
+
showCode,
|
|
130
|
+
isEditable,
|
|
121
131
|
}: {
|
|
122
132
|
subslide: ComposedSubslide<RuntimeCell>;
|
|
133
|
+
showCode: boolean;
|
|
134
|
+
isEditable: boolean;
|
|
123
135
|
}) => (
|
|
124
136
|
<Slide>
|
|
125
137
|
<div className="h-full w-full overflow-auto flex">
|
|
126
|
-
<div
|
|
138
|
+
<div
|
|
139
|
+
className={
|
|
140
|
+
showCode ? "mo-slide-content flex flex-col gap-3" : "mo-slide-content"
|
|
141
|
+
}
|
|
142
|
+
style={{
|
|
143
|
+
margin: "auto 20px",
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
127
146
|
{subslide.blocks.map((block, i) => {
|
|
128
|
-
const rendered = block.cells.map((cell) =>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
const rendered = block.cells.map((cell) => {
|
|
148
|
+
if (!showCode) {
|
|
149
|
+
return (
|
|
150
|
+
<CellOutputSlide
|
|
151
|
+
key={cell.id}
|
|
152
|
+
cellId={cell.id}
|
|
153
|
+
status={cell.status}
|
|
154
|
+
output={cell.output}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return isEditable ? (
|
|
159
|
+
<SlideCellView key={cell.id} cell={cell} />
|
|
160
|
+
) : (
|
|
161
|
+
<SlideCellReadOnlyView key={cell.id} cell={cell} />
|
|
162
|
+
);
|
|
163
|
+
});
|
|
136
164
|
if (block.isFragment) {
|
|
137
165
|
return (
|
|
138
166
|
<Fragment key={i} as="div">
|
|
@@ -147,27 +175,37 @@ const SubslideView = ({
|
|
|
147
175
|
</Slide>
|
|
148
176
|
);
|
|
149
177
|
|
|
178
|
+
// There is an upstream react bug in dev mode (https://github.com/facebook/react/issues/34840)
|
|
179
|
+
// Uncaught SecurityError: Failed to read a named property '$$typeof' from 'Window'
|
|
180
|
+
// Happens with cells containing iframes / external content
|
|
150
181
|
const RevealSlidesComponent = ({
|
|
151
182
|
cellsWithOutput,
|
|
152
183
|
layout,
|
|
153
184
|
setLayout,
|
|
154
185
|
activeIndex,
|
|
155
186
|
onSlideChange,
|
|
156
|
-
deckRef,
|
|
157
187
|
mode,
|
|
158
188
|
configWidth = 300, // px
|
|
189
|
+
isEditable = false,
|
|
159
190
|
}: {
|
|
160
191
|
cellsWithOutput: RuntimeCell[];
|
|
161
192
|
layout: SlidesLayout;
|
|
162
193
|
setLayout: (layout: SlidesLayout) => void;
|
|
163
194
|
activeIndex?: number;
|
|
164
195
|
onSlideChange?: (index: number) => void;
|
|
165
|
-
deckRef: React.RefObject<RevealApi | null>;
|
|
166
196
|
mode: AppMode;
|
|
167
197
|
configWidth?: number;
|
|
198
|
+
isEditable?: boolean;
|
|
168
199
|
}) => {
|
|
169
200
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
201
|
+
const deckRef = useRef<RevealApi | null>(null);
|
|
170
202
|
const { width, height } = useSlideDimensions(containerRef);
|
|
203
|
+
|
|
204
|
+
const [showCode, setShowCode] = useState(false);
|
|
205
|
+
const codeAvailable = useNotebookCodeAvailable(cellsWithOutput);
|
|
206
|
+
const codeToggleEnabled = !isIslands() && codeAvailable;
|
|
207
|
+
const codeShown = codeToggleEnabled && showCode;
|
|
208
|
+
|
|
171
209
|
const activeCell =
|
|
172
210
|
activeIndex != null ? cellsWithOutput[activeIndex] : undefined;
|
|
173
211
|
// Fall back to the first cell while the deck settles on an initial slide.
|
|
@@ -217,11 +255,7 @@ const RevealSlidesComponent = ({
|
|
|
217
255
|
[width, height, deckTransition],
|
|
218
256
|
);
|
|
219
257
|
|
|
220
|
-
|
|
221
|
-
const deck = deckRef.current;
|
|
222
|
-
if (deck == null) {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
258
|
+
const navigateDeckToActiveCell = useEvent((deck: RevealApi) => {
|
|
225
259
|
const target = resolveDeckNavigationTarget({
|
|
226
260
|
activeIndex,
|
|
227
261
|
cells: cellsWithOutput,
|
|
@@ -234,7 +268,43 @@ const RevealSlidesComponent = ({
|
|
|
234
268
|
}
|
|
235
269
|
deck.slide(next.h, next.v, next.f);
|
|
236
270
|
clearPreviousVerticalIndices(deck);
|
|
237
|
-
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
const deck = deckRef.current;
|
|
275
|
+
if (deck == null) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
navigateDeckToActiveCell(deck);
|
|
279
|
+
}, [activeIndex, cellToTarget, cellsWithOutput, navigateDeckToActiveCell]);
|
|
280
|
+
|
|
281
|
+
// Toggling code (re)mounts a CodeMirror editor on the active slide. Defer
|
|
282
|
+
// the state update so the button/keypress paints first and the heavier mount
|
|
283
|
+
// can be interrupted by higher-priority work.
|
|
284
|
+
const toggleShowCode = useEvent(() => {
|
|
285
|
+
startTransition(() => setShowCode((value) => !value));
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const handleDeckReady = useEvent((deck: RevealApi) => {
|
|
289
|
+
navigateDeckToActiveCell(deck);
|
|
290
|
+
if (codeToggleEnabled) {
|
|
291
|
+
deck.addKeyBinding(
|
|
292
|
+
{ keyCode: 67, key: "C", description: "Toggle code editor" },
|
|
293
|
+
toggleShowCode,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const activeSubslide = useMemo(() => {
|
|
299
|
+
if (!activeCell) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const target = cellToTarget.get(activeCell.id);
|
|
303
|
+
if (!target) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
return { h: target.h, v: target.v };
|
|
307
|
+
}, [activeCell, cellToTarget]);
|
|
238
308
|
|
|
239
309
|
// Forward the deck's current cell to the parent, except while a skipped
|
|
240
310
|
// preview is parked: every reveal.js event during that window is an echo
|
|
@@ -266,6 +336,7 @@ const RevealSlidesComponent = ({
|
|
|
266
336
|
if (Events.fromInput(event)) {
|
|
267
337
|
return;
|
|
268
338
|
}
|
|
339
|
+
|
|
269
340
|
const direction = classifyNavKey(event);
|
|
270
341
|
if (direction === 0) {
|
|
271
342
|
return;
|
|
@@ -279,6 +350,11 @@ const RevealSlidesComponent = ({
|
|
|
279
350
|
onSlideChange?.(nextIndex);
|
|
280
351
|
});
|
|
281
352
|
|
|
353
|
+
const handleSlideChange = useEvent(() => {
|
|
354
|
+
reportCurrentCell();
|
|
355
|
+
triggerResize(deckRef.current);
|
|
356
|
+
});
|
|
357
|
+
|
|
282
358
|
useEventListener(document, "keydown", handleParkedNavKey, { capture: true });
|
|
283
359
|
|
|
284
360
|
return (
|
|
@@ -292,23 +368,38 @@ const RevealSlidesComponent = ({
|
|
|
292
368
|
deckRef={deckRef}
|
|
293
369
|
className="aspect-video w-full overflow-hidden border rounded bg-background mo-slides-theme prose-slides"
|
|
294
370
|
config={revealConfig}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const deck = deckRef.current;
|
|
298
|
-
triggerResize(deck);
|
|
299
|
-
}}
|
|
371
|
+
onReady={handleDeckReady}
|
|
372
|
+
onSlideChange={handleSlideChange}
|
|
300
373
|
onFragmentShown={reportCurrentCell}
|
|
301
374
|
onFragmentHidden={reportCurrentCell}
|
|
302
375
|
>
|
|
303
|
-
{composition.stacks.map((stack,
|
|
376
|
+
{composition.stacks.map((stack, h) => {
|
|
304
377
|
if (stack.subslides.length === 1) {
|
|
305
|
-
|
|
378
|
+
const isActive =
|
|
379
|
+
activeSubslide?.h === h && activeSubslide?.v === 0;
|
|
380
|
+
return (
|
|
381
|
+
<SubslideView
|
|
382
|
+
key={h}
|
|
383
|
+
subslide={stack.subslides[0]}
|
|
384
|
+
showCode={codeShown && isActive}
|
|
385
|
+
isEditable={isEditable}
|
|
386
|
+
/>
|
|
387
|
+
);
|
|
306
388
|
}
|
|
307
389
|
return (
|
|
308
|
-
<Stack key={
|
|
309
|
-
{stack.subslides.map((sub,
|
|
310
|
-
|
|
311
|
-
|
|
390
|
+
<Stack key={h}>
|
|
391
|
+
{stack.subslides.map((sub, v) => {
|
|
392
|
+
const isActive =
|
|
393
|
+
activeSubslide?.h === h && activeSubslide?.v === v;
|
|
394
|
+
return (
|
|
395
|
+
<SubslideView
|
|
396
|
+
key={v}
|
|
397
|
+
subslide={sub}
|
|
398
|
+
showCode={codeShown && isActive}
|
|
399
|
+
isEditable={isEditable}
|
|
400
|
+
/>
|
|
401
|
+
);
|
|
402
|
+
})}
|
|
312
403
|
</Stack>
|
|
313
404
|
);
|
|
314
405
|
})}
|
|
@@ -336,24 +427,45 @@ const RevealSlidesComponent = ({
|
|
|
336
427
|
</div>
|
|
337
428
|
</div>
|
|
338
429
|
)}
|
|
339
|
-
<
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
430
|
+
<div className="absolute top-2 right-2 z-20 opacity-0 group-hover:opacity-70 text-muted-foreground transition-opacity">
|
|
431
|
+
{codeToggleEnabled && (
|
|
432
|
+
<Tooltip content={codeShown ? "Hide code (C)" : "Show code (C)"}>
|
|
433
|
+
<Button
|
|
434
|
+
data-testid="marimo-plugin-slides-toggle-code"
|
|
435
|
+
variant="ghost"
|
|
436
|
+
size="icon"
|
|
437
|
+
className={cn(
|
|
438
|
+
"text-muted-foreground h-7 w-7",
|
|
439
|
+
codeShown && "text-foreground bg-muted",
|
|
440
|
+
)}
|
|
441
|
+
aria-pressed={codeShown}
|
|
442
|
+
aria-label={codeShown ? "Hide code" : "Show code"}
|
|
443
|
+
onClick={toggleShowCode}
|
|
444
|
+
>
|
|
445
|
+
<CodeIcon className="h-4 w-4" />
|
|
446
|
+
</Button>
|
|
447
|
+
</Tooltip>
|
|
448
|
+
)}
|
|
449
|
+
<Tooltip content="Fullscreen (F)">
|
|
450
|
+
<Button
|
|
451
|
+
data-testid="marimo-plugin-slides-fullscreen"
|
|
452
|
+
variant="ghost"
|
|
453
|
+
size="icon"
|
|
454
|
+
className="text-muted-foreground h-7 w-7"
|
|
455
|
+
aria-label="Enter fullscreen"
|
|
456
|
+
onClick={() => {
|
|
457
|
+
deckRef.current
|
|
458
|
+
?.getViewportElement()
|
|
459
|
+
?.requestFullscreen()
|
|
460
|
+
.catch((error) => {
|
|
461
|
+
Logger.error("Failed to request fullscreen", error);
|
|
462
|
+
});
|
|
463
|
+
}}
|
|
464
|
+
>
|
|
465
|
+
<ExpandIcon className="h-4 w-4" />
|
|
466
|
+
</Button>
|
|
467
|
+
</Tooltip>
|
|
468
|
+
</div>
|
|
357
469
|
</div>
|
|
358
470
|
</div>
|
|
359
471
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useMemo, useRef, useState } from "react";
|
|
4
|
+
import type { EditorView } from "@codemirror/view";
|
|
5
|
+
import { useAtomValue } from "jotai";
|
|
6
|
+
import { CellEditor } from "@/components/editor/cell/code/cell-editor";
|
|
7
|
+
import { CellStatusComponent } from "@/components/editor/cell/CellStatus";
|
|
8
|
+
import { RunButton } from "@/components/editor/cell/RunButton";
|
|
9
|
+
import { StopButton } from "@/components/editor/cell/StopButton";
|
|
10
|
+
import { useRunCell } from "@/components/editor/cell/useRunCells";
|
|
11
|
+
import { Slide as CellOutputSlide } from "@/components/slides/slide";
|
|
12
|
+
import { useUserConfig } from "@/core/config/config";
|
|
13
|
+
import {
|
|
14
|
+
cellNeedsRun,
|
|
15
|
+
cellStatusClasses,
|
|
16
|
+
isUninstantiated,
|
|
17
|
+
} from "@/core/cells/utils";
|
|
18
|
+
import type { CellData, CellRuntimeState } from "@/core/cells/types";
|
|
19
|
+
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
20
|
+
import { connectionAtom } from "@/core/network/connection";
|
|
21
|
+
import { useTheme } from "@/theme/useTheme";
|
|
22
|
+
import { cn } from "@/utils/cn";
|
|
23
|
+
import { ReadonlyCode } from "../editor/code/readonly-python-code";
|
|
24
|
+
import { languageAdapterFromCode } from "@/core/codemirror/language/extension";
|
|
25
|
+
|
|
26
|
+
type RuntimeCell = CellRuntimeState & CellData;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Renders a single cell in the slides view as an editable CodeMirror editor
|
|
30
|
+
* stacked with its output, mirroring the notebook layout. Editing and
|
|
31
|
+
* Ctrl/Cmd+Enter run the cell against the live kernel so presenters can iterate
|
|
32
|
+
* without leaving the slide deck.
|
|
33
|
+
*/
|
|
34
|
+
export const SlideCellView = ({ cell }: { cell: RuntimeCell }) => {
|
|
35
|
+
const [userConfig] = useUserConfig();
|
|
36
|
+
const { theme } = useTheme();
|
|
37
|
+
const runCell = useRunCell(cell.id);
|
|
38
|
+
const connection = useAtomValue(connectionAtom);
|
|
39
|
+
const editorViewRef = useRef<EditorView | null>(null);
|
|
40
|
+
const editorViewParentRef = useRef<HTMLDivElement | null>(null);
|
|
41
|
+
const [languageAdapter, setLanguageAdapter] = useState<
|
|
42
|
+
LanguageAdapterType | undefined
|
|
43
|
+
>();
|
|
44
|
+
|
|
45
|
+
const cellOutputPosition = userConfig.display.cell_output;
|
|
46
|
+
const hasOutput = cell.output != null;
|
|
47
|
+
|
|
48
|
+
const uninstantiated = isUninstantiated({
|
|
49
|
+
executionTime: cell.runElapsedTimeMs ?? cell.lastExecutionTime,
|
|
50
|
+
status: cell.status,
|
|
51
|
+
errored: cell.errored,
|
|
52
|
+
interrupted: cell.interrupted,
|
|
53
|
+
stopped: cell.stopped,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const needsRun = cellNeedsRun({
|
|
57
|
+
edited: cell.edited,
|
|
58
|
+
interrupted: cell.interrupted,
|
|
59
|
+
staleInputs: cell.staleInputs,
|
|
60
|
+
disabled: cell.config.disabled,
|
|
61
|
+
status: cell.status,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const editorWrapperClassName = cn(
|
|
65
|
+
"marimo-cell",
|
|
66
|
+
"hover-actions-parent",
|
|
67
|
+
"interactive",
|
|
68
|
+
cellStatusClasses({
|
|
69
|
+
needsRun,
|
|
70
|
+
errored: cell.errored,
|
|
71
|
+
stopped: cell.stopped,
|
|
72
|
+
disabled: cell.config.disabled,
|
|
73
|
+
status: cell.status,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const output = (
|
|
78
|
+
<CellOutputSlide
|
|
79
|
+
cellId={cell.id}
|
|
80
|
+
status={cell.status}
|
|
81
|
+
output={cell.output}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const toolbar = (
|
|
86
|
+
<div className="absolute top-1 right-2 z-10 flex items-center gap-1.5">
|
|
87
|
+
<CellStatusComponent
|
|
88
|
+
editing={true}
|
|
89
|
+
status={cell.status}
|
|
90
|
+
disabled={cell.config.disabled ?? false}
|
|
91
|
+
staleInputs={cell.staleInputs}
|
|
92
|
+
edited={cell.edited}
|
|
93
|
+
interrupted={cell.interrupted}
|
|
94
|
+
elapsedTime={cell.runElapsedTimeMs ?? cell.lastExecutionTime}
|
|
95
|
+
runStartTimestamp={cell.runStartTimestamp}
|
|
96
|
+
lastRunStartTimestamp={cell.lastRunStartTimestamp}
|
|
97
|
+
uninstantiated={uninstantiated}
|
|
98
|
+
/>
|
|
99
|
+
<div className="flex items-center shadow-none gap-1">
|
|
100
|
+
<RunButton
|
|
101
|
+
edited={cell.edited}
|
|
102
|
+
status={cell.status}
|
|
103
|
+
needsRun={needsRun}
|
|
104
|
+
connectionState={connection.state}
|
|
105
|
+
config={cell.config}
|
|
106
|
+
onClick={runCell}
|
|
107
|
+
/>
|
|
108
|
+
<StopButton status={cell.status} connectionState={connection.state} />
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const editor = (
|
|
114
|
+
<div className={editorWrapperClassName}>
|
|
115
|
+
<CellEditor
|
|
116
|
+
theme={theme}
|
|
117
|
+
showPlaceholder={false}
|
|
118
|
+
id={cell.id}
|
|
119
|
+
code={cell.code}
|
|
120
|
+
config={cell.config}
|
|
121
|
+
status={cell.status}
|
|
122
|
+
serializedEditorState={cell.serializedEditorState}
|
|
123
|
+
runCell={runCell}
|
|
124
|
+
setEditorView={(ev) => {
|
|
125
|
+
editorViewRef.current = ev;
|
|
126
|
+
}}
|
|
127
|
+
userConfig={userConfig}
|
|
128
|
+
editorViewRef={editorViewRef}
|
|
129
|
+
editorViewParentRef={editorViewParentRef}
|
|
130
|
+
hasOutput={hasOutput}
|
|
131
|
+
// hide_code is intentionally overridden in the slide view; the editor
|
|
132
|
+
// is unmounted entirely when the user toggles code off.
|
|
133
|
+
showHiddenCode={() => undefined}
|
|
134
|
+
languageAdapter={languageAdapter}
|
|
135
|
+
setLanguageAdapter={setLanguageAdapter}
|
|
136
|
+
showLanguageToggles={false}
|
|
137
|
+
outputArea={cellOutputPosition}
|
|
138
|
+
/>
|
|
139
|
+
{toolbar}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
{cellOutputPosition === "above" && output}
|
|
146
|
+
{editor}
|
|
147
|
+
{cellOutputPosition === "below" && output}
|
|
148
|
+
</>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const SlideCellReadOnlyView = ({ cell }: { cell: RuntimeCell }) => {
|
|
153
|
+
const [userConfig] = useUserConfig();
|
|
154
|
+
const cellOutputPosition = userConfig.display.cell_output;
|
|
155
|
+
|
|
156
|
+
const language = useMemo(() => {
|
|
157
|
+
const adapter = languageAdapterFromCode(cell.code.trim());
|
|
158
|
+
return adapter.type === "sql" ? "sql" : "python";
|
|
159
|
+
}, [cell.code]);
|
|
160
|
+
|
|
161
|
+
const output = (
|
|
162
|
+
<CellOutputSlide
|
|
163
|
+
cellId={cell.id}
|
|
164
|
+
status={cell.status}
|
|
165
|
+
output={cell.output}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const editor = (
|
|
170
|
+
<div className="marimo-cell">
|
|
171
|
+
<ReadonlyCode code={cell.code} language={language} showHideCode={false} />
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<>
|
|
177
|
+
{cellOutputPosition === "above" && output}
|
|
178
|
+
{editor}
|
|
179
|
+
{cellOutputPosition === "below" && output}
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
@@ -288,6 +288,8 @@ export const SlideSidebar = ({
|
|
|
288
288
|
width: isConfigOpen ? configWidth : COLLAPSED_CONFIG_WIDTH,
|
|
289
289
|
}}
|
|
290
290
|
aria-label="Slide configuration"
|
|
291
|
+
// Prevent keys from bubbling up to reveal.js's document-level keydown listener and moving the deck.
|
|
292
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
291
293
|
>
|
|
292
294
|
<header
|
|
293
295
|
className={cn(
|
package/src/core/cells/utils.ts
CHANGED
|
@@ -140,3 +140,48 @@ export function isUninstantiated({
|
|
|
140
140
|
!(errored || interrupted || stopped)
|
|
141
141
|
);
|
|
142
142
|
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Whether a cell needs to be run given its edited / interrupted / stale
|
|
146
|
+
* inputs, while accounting for ancestor-disabled cells (which should not be
|
|
147
|
+
* flagged as needing a run until re-enabled).
|
|
148
|
+
*/
|
|
149
|
+
export function cellNeedsRun({
|
|
150
|
+
edited,
|
|
151
|
+
interrupted,
|
|
152
|
+
staleInputs,
|
|
153
|
+
disabled,
|
|
154
|
+
status,
|
|
155
|
+
}: {
|
|
156
|
+
edited: boolean;
|
|
157
|
+
interrupted: boolean;
|
|
158
|
+
staleInputs: boolean;
|
|
159
|
+
disabled: boolean | undefined;
|
|
160
|
+
status: RuntimeState;
|
|
161
|
+
}): boolean {
|
|
162
|
+
const disabledOrAncestorDisabled =
|
|
163
|
+
disabled || status === "disabled-transitively";
|
|
164
|
+
return edited || interrupted || (staleInputs && !disabledOrAncestorDisabled);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function cellStatusClasses({
|
|
168
|
+
needsRun,
|
|
169
|
+
errored,
|
|
170
|
+
stopped,
|
|
171
|
+
disabled,
|
|
172
|
+
status,
|
|
173
|
+
}: {
|
|
174
|
+
needsRun: boolean;
|
|
175
|
+
errored: boolean;
|
|
176
|
+
stopped: boolean;
|
|
177
|
+
disabled: boolean | undefined;
|
|
178
|
+
status: RuntimeState;
|
|
179
|
+
}) {
|
|
180
|
+
return {
|
|
181
|
+
"needs-run": needsRun,
|
|
182
|
+
"has-error": errored,
|
|
183
|
+
stopped,
|
|
184
|
+
disabled: disabled ?? false,
|
|
185
|
+
stale: status === "disabled-transitively",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { RuntimeCell } from "@/core/cells/types";
|
|
4
|
+
import { Logger } from "@/utils/Logger";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Build-time stub for `@/components/slides/slide-cell-view`, wired up via
|
|
8
|
+
* `resolve.alias` in `frontend/islands/vite.config.mts`. Islands embeds gate
|
|
9
|
+
* off the slides "show code" toggle entirely (see `useNotebookCodeAvailable`
|
|
10
|
+
* + `isIslands()`), so neither view is reachable at runtime there.
|
|
11
|
+
*
|
|
12
|
+
* Replacing the module at build time keeps the entire CodeMirror /
|
|
13
|
+
* Codeium / `@bufbuild/protobuf` import subtree out of the islands bundle,
|
|
14
|
+
* which both shrinks the bundle and lets `islands/validate.sh` pass — the
|
|
15
|
+
* upstream protobuf code contains a `process.env.BUF_BIGINT_DISABLE`
|
|
16
|
+
* runtime check that the validator otherwise flags.
|
|
17
|
+
*/
|
|
18
|
+
export const SlideCellView = (_props: { cell: RuntimeCell }) => {
|
|
19
|
+
Logger.warn(
|
|
20
|
+
"SlideCellView islands stub rendered; this should never happen in a read-only embed.",
|
|
21
|
+
);
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const SlideCellReadOnlyView = (_props: { cell: RuntimeCell }) => {
|
|
26
|
+
Logger.warn(
|
|
27
|
+
"SlideCellReadOnlyView islands stub rendered; this should never happen in a read-only embed.",
|
|
28
|
+
);
|
|
29
|
+
return null;
|
|
30
|
+
};
|