@marimo-team/islands 0.23.5-dev4 → 0.23.5-dev6
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-C0bQaEHr.js} +835 -777
- package/dist/main.js +1039 -1073
- package/dist/{reveal-component-DK-5_Ei4.js → reveal-component-B2cdMcuv.js} +575 -527
- package/dist/style.css +1 -1
- package/package.json +1 -1
- 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
package/package.json
CHANGED
|
@@ -48,7 +48,11 @@ import {
|
|
|
48
48
|
useCellRuntime,
|
|
49
49
|
} from "../../core/cells/cells";
|
|
50
50
|
import { type CellId, SETUP_CELL_ID } from "../../core/cells/ids";
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
cellNeedsRun,
|
|
53
|
+
cellStatusClasses,
|
|
54
|
+
isUninstantiated,
|
|
55
|
+
} from "../../core/cells/utils";
|
|
52
56
|
import type { UserConfig } from "../../core/config/config-schema";
|
|
53
57
|
import { isAppInteractionDisabled } from "../../core/websocket/connection-utils";
|
|
54
58
|
import { useCellRenderCount } from "../../hooks/useCellRenderCount";
|
|
@@ -390,9 +394,6 @@ const EditableCellComponent = ({
|
|
|
390
394
|
|
|
391
395
|
const [languageAdapter, setLanguageAdapter] = useState<LanguageAdapterType>();
|
|
392
396
|
|
|
393
|
-
const disabledOrAncestorDisabled =
|
|
394
|
-
cellData.config.disabled || cellRuntime.status === "disabled-transitively";
|
|
395
|
-
|
|
396
397
|
const uninstantiated = isUninstantiated({
|
|
397
398
|
executionTime: cellRuntime.runElapsedTimeMs ?? cellData.lastExecutionTime,
|
|
398
399
|
status: cellRuntime.status,
|
|
@@ -401,10 +402,13 @@ const EditableCellComponent = ({
|
|
|
401
402
|
stopped: cellRuntime.stopped,
|
|
402
403
|
});
|
|
403
404
|
|
|
404
|
-
const needsRun =
|
|
405
|
-
cellData.edited
|
|
406
|
-
cellRuntime.interrupted
|
|
407
|
-
|
|
405
|
+
const needsRun = cellNeedsRun({
|
|
406
|
+
edited: cellData.edited,
|
|
407
|
+
interrupted: cellRuntime.interrupted,
|
|
408
|
+
staleInputs: cellRuntime.staleInputs,
|
|
409
|
+
disabled: cellData.config.disabled,
|
|
410
|
+
status: cellRuntime.status,
|
|
411
|
+
});
|
|
408
412
|
|
|
409
413
|
const loading = outputIsLoading(cellRuntime.status);
|
|
410
414
|
|
|
@@ -532,11 +536,13 @@ const EditableCellComponent = ({
|
|
|
532
536
|
|
|
533
537
|
const className = clsx("marimo-cell", "hover-actions-parent z-10", {
|
|
534
538
|
interactive: true,
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
539
|
+
...cellStatusClasses({
|
|
540
|
+
needsRun,
|
|
541
|
+
errored: cellRuntime.errored,
|
|
542
|
+
stopped: cellRuntime.stopped,
|
|
543
|
+
disabled: cellData.config.disabled,
|
|
544
|
+
status: cellRuntime.status,
|
|
545
|
+
}),
|
|
540
546
|
borderless:
|
|
541
547
|
isMarkdownCodeHidden && hasOutput && !navigationProps["data-selected"],
|
|
542
548
|
});
|
|
@@ -979,9 +985,6 @@ const SetupCellComponent = ({
|
|
|
979
985
|
const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
|
|
980
986
|
const runCell = useRunCell(cellId);
|
|
981
987
|
|
|
982
|
-
const disabledOrAncestorDisabled =
|
|
983
|
-
cellData.config.disabled || cellRuntime.status === "disabled-transitively";
|
|
984
|
-
|
|
985
988
|
const uninstantiated = isUninstantiated({
|
|
986
989
|
executionTime: cellRuntime.runElapsedTimeMs ?? cellData.lastExecutionTime,
|
|
987
990
|
status: cellRuntime.status,
|
|
@@ -990,10 +993,13 @@ const SetupCellComponent = ({
|
|
|
990
993
|
stopped: cellRuntime.stopped,
|
|
991
994
|
});
|
|
992
995
|
|
|
993
|
-
const needsRun =
|
|
994
|
-
cellData.edited
|
|
995
|
-
cellRuntime.interrupted
|
|
996
|
-
|
|
996
|
+
const needsRun = cellNeedsRun({
|
|
997
|
+
edited: cellData.edited,
|
|
998
|
+
interrupted: cellRuntime.interrupted,
|
|
999
|
+
staleInputs: cellRuntime.staleInputs,
|
|
1000
|
+
disabled: cellData.config.disabled,
|
|
1001
|
+
status: cellRuntime.status,
|
|
1002
|
+
});
|
|
997
1003
|
const loading =
|
|
998
1004
|
cellRuntime.status === "running" || cellRuntime.status === "queued";
|
|
999
1005
|
|
|
@@ -1034,9 +1040,13 @@ const SetupCellComponent = ({
|
|
|
1034
1040
|
|
|
1035
1041
|
const className = clsx("marimo-cell", "hover-actions-parent z-10", {
|
|
1036
1042
|
interactive: true,
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1043
|
+
...cellStatusClasses({
|
|
1044
|
+
needsRun,
|
|
1045
|
+
errored: cellRuntime.errored,
|
|
1046
|
+
stopped: cellRuntime.stopped,
|
|
1047
|
+
disabled: cellData.config.disabled,
|
|
1048
|
+
status: cellRuntime.status,
|
|
1049
|
+
}),
|
|
1040
1050
|
});
|
|
1041
1051
|
|
|
1042
1052
|
const handleRefactorWithAI: OnRefactorWithAI = useEvent(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import React, { useMemo,
|
|
2
|
+
import React, { useMemo, useState } from "react";
|
|
3
3
|
import { useAtomValue } from "jotai";
|
|
4
4
|
import { numColumnsAtom } from "@/core/cells/cells";
|
|
5
5
|
import type { CellId } from "@/core/cells/ids";
|
|
@@ -8,7 +8,6 @@ import type { SlidesLayout } from "./types";
|
|
|
8
8
|
import { computeSlideCellsInfo } from "./compute-slide-cells";
|
|
9
9
|
import { SlidesMinimap } from "@/components/slides/minimap";
|
|
10
10
|
import useEvent from "react-use-event-hook";
|
|
11
|
-
import type { RevealApi } from "reveal.js";
|
|
12
11
|
|
|
13
12
|
type Props = ICellRendererProps<SlidesLayout>;
|
|
14
13
|
|
|
@@ -26,7 +25,6 @@ export const SlidesLayoutRenderer: React.FC<Props> = ({
|
|
|
26
25
|
const numColumns = useAtomValue(numColumnsAtom);
|
|
27
26
|
const isMultiColumn = numColumns > 1;
|
|
28
27
|
const [activeCellId, setActiveCellId] = useState<CellId | null>(null);
|
|
29
|
-
const deckRef = useRef<RevealApi | null>(null);
|
|
30
28
|
|
|
31
29
|
const { cellsWithOutput, skippedIds, slideTypes, startCellIndex } = useMemo(
|
|
32
30
|
() => computeSlideCellsInfo(cells, layout),
|
|
@@ -53,9 +51,9 @@ export const SlidesLayoutRenderer: React.FC<Props> = ({
|
|
|
53
51
|
setLayout={setLayout}
|
|
54
52
|
activeIndex={resolvedIndex}
|
|
55
53
|
onSlideChange={handleSlideChange}
|
|
56
|
-
|
|
57
|
-
configWidth={250}
|
|
54
|
+
configWidth={300}
|
|
58
55
|
mode={mode}
|
|
56
|
+
isEditable={mode !== "read"}
|
|
59
57
|
/>
|
|
60
58
|
);
|
|
61
59
|
|
|
@@ -34,6 +34,7 @@ import { useResolvedMarimoConfig } from "@/core/config/config";
|
|
|
34
34
|
import { CSSClasses, KnownQueryParams } from "@/core/constants";
|
|
35
35
|
import type { MarimoError, OutputMessage } from "@/core/kernel/messages";
|
|
36
36
|
import { kernelStateAtom } from "@/core/kernel/state";
|
|
37
|
+
import { useNotebookCodeAvailable } from "@/core/meta/code-visibility";
|
|
37
38
|
import { showCodeInRunModeAtom } from "@/core/meta/state";
|
|
38
39
|
import { isErrorMime } from "@/core/mime";
|
|
39
40
|
import { type AppMode, kioskModeAtom } from "@/core/mode";
|
|
@@ -83,23 +84,7 @@ const VerticalLayoutRenderer: React.FC<VerticalLayoutProps> = ({
|
|
|
83
84
|
: showCodeByQueryParam === "true";
|
|
84
85
|
});
|
|
85
86
|
|
|
86
|
-
const
|
|
87
|
-
const cellsHaveCode = cells.some((cell) => Boolean(cell.code));
|
|
88
|
-
|
|
89
|
-
if (kioskMode) {
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Only show code if in read mode and there is at least one cell with code
|
|
94
|
-
|
|
95
|
-
// If it is a static-notebook or wasm-read-only-notebook, code is always included,
|
|
96
|
-
// but it can be turned it off via a query parameter (include-code=false)
|
|
97
|
-
|
|
98
|
-
const includeCode = urlParams.get(KnownQueryParams.includeCode);
|
|
99
|
-
return mode === "read" && includeCode !== "false" && cellsHaveCode;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const canShowCode = evaluateCanShowCode();
|
|
87
|
+
const canShowCode = useNotebookCodeAvailable(cells);
|
|
103
88
|
|
|
104
89
|
const renderCell = (cell: CellRuntimeState & CellData) => {
|
|
105
90
|
return (
|
|
@@ -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
|
+
};
|