@marimo-team/islands 0.23.10-dev25 → 0.23.10-dev26
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/{code-visibility-CXkMXcdB.js → code-visibility-Rcdlclvw.js} +1198 -1021
- package/dist/main.js +1198 -1334
- package/dist/{reveal-component-dIolR_34.js → reveal-component-xsFYQVKT.js} +407 -330
- package/package.json +1 -1
- package/src/components/editor/cell/code/language-toggle.tsx +7 -1
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +20 -0
- package/src/components/editor/renderers/slides-layout/types.ts +1 -0
- package/src/components/slides/__tests__/reveal-component.test.ts +425 -0
- package/src/components/slides/reveal-component.tsx +283 -61
- package/src/components/slides/slide-cell-view.tsx +26 -2
- package/src/components/slides/slide-form.tsx +26 -4
|
@@ -27,6 +27,7 @@ import "./reveal-slides.css";
|
|
|
27
27
|
import type {
|
|
28
28
|
SlideConfig,
|
|
29
29
|
SlidesLayout,
|
|
30
|
+
SlideType,
|
|
30
31
|
} from "../editor/renderers/slides-layout/types";
|
|
31
32
|
import {
|
|
32
33
|
buildSlideIndices,
|
|
@@ -156,14 +157,148 @@ const NotesAside = ({ text }: { text: string }) => {
|
|
|
156
157
|
);
|
|
157
158
|
};
|
|
158
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Resolve whether a slide cell shows its source instead of its output.
|
|
162
|
+
*
|
|
163
|
+
* Code is shown when either the cell's persisted `showCode` config is set or
|
|
164
|
+
* the keyboard toggle `C` override is active for it (logical OR).
|
|
165
|
+
*/
|
|
166
|
+
export function shouldShowCode(options: {
|
|
167
|
+
cells: ReadonlyMap<CellId, SlideConfig>;
|
|
168
|
+
cellId: CellId | undefined;
|
|
169
|
+
showCodeOverrides: ReadonlySet<CellId>;
|
|
170
|
+
codeToggleEnabled: boolean;
|
|
171
|
+
}): boolean {
|
|
172
|
+
const { cells, cellId, showCodeOverrides, codeToggleEnabled } = options;
|
|
173
|
+
if (cellId == null || !codeToggleEnabled) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
const configured = cells.get(cellId)?.showCode ?? false;
|
|
177
|
+
return configured || showCodeOverrides.has(cellId);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The slide type a cell takes *in the composed deck*. Cells without output and
|
|
182
|
+
* the cell currently held in the parked edit overlay are dropped (`"skip"`) so
|
|
183
|
+
* they aren't mounted a second time in the deck — the overlay renders them
|
|
184
|
+
* instead. Everything else uses its configured type, defaulting to a slide.
|
|
185
|
+
*/
|
|
186
|
+
export function deckSlideType(options: {
|
|
187
|
+
cell: RuntimeCell;
|
|
188
|
+
noOutputIds: ReadonlySet<CellId>;
|
|
189
|
+
heldEditCellId: CellId | null;
|
|
190
|
+
slideConfigs: ReadonlyMap<CellId, SlideConfig>;
|
|
191
|
+
}): SlideType {
|
|
192
|
+
const { cell, noOutputIds, heldEditCellId, slideConfigs } = options;
|
|
193
|
+
if (noOutputIds.has(cell.id) || cell.id === heldEditCellId) {
|
|
194
|
+
return "skip";
|
|
195
|
+
}
|
|
196
|
+
return slideConfigs.get(cell.id)?.type ?? DEFAULT_SLIDE_TYPE;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Tracks the cell pinned in the parked overlay (rendered over the deck for
|
|
201
|
+
* skipped / output-less cells, and during in-progress edits).
|
|
202
|
+
*
|
|
203
|
+
* A brand-new (or output-less) cell is edited in the parked overlay, which
|
|
204
|
+
* lives outside reveal's slide DOM. The moment it first produces output it
|
|
205
|
+
* would normally jump to its composed slide — a different React subtree that
|
|
206
|
+
* reveal also re-syncs/transitions — tearing down the editor and dropping
|
|
207
|
+
* focus mid-edit (e.g. typing in a new markdown cell). To avoid that we *hold*
|
|
208
|
+
* the cell in the overlay even after it gains output, and only let it settle
|
|
209
|
+
* into the deck once the user navigates to a different cell.
|
|
210
|
+
*
|
|
211
|
+
* The hold is keyed off the active cell rather than DOM focus on purpose: the
|
|
212
|
+
* slide editor doesn't participate in the global cell-focus state, and the
|
|
213
|
+
* active cell only changes when the user moves in the minimap — exactly when
|
|
214
|
+
* we want to release the hold.
|
|
215
|
+
*
|
|
216
|
+
* Returns:
|
|
217
|
+
* - `parkedPreviewCell`: the cell to render in the overlay
|
|
218
|
+
* - `isHeldEdit`: whether the cell is held in the overlay
|
|
219
|
+
* - `isNoOutputPreview`: whether the cell is output-less
|
|
220
|
+
* - `heldEditCellId`: the id of the cell that is held in the overlay
|
|
221
|
+
*/
|
|
222
|
+
export function useParkedPreview(options: {
|
|
223
|
+
activeCell: RuntimeCell | undefined;
|
|
224
|
+
slideConfigs: ReadonlyMap<CellId, SlideConfig>;
|
|
225
|
+
noOutputIds: ReadonlySet<CellId>;
|
|
226
|
+
}): {
|
|
227
|
+
parkedPreviewCell: RuntimeCell | null;
|
|
228
|
+
isHeldEdit: boolean;
|
|
229
|
+
isNoOutputPreview: boolean;
|
|
230
|
+
heldEditCellId: CellId | null;
|
|
231
|
+
heldShowsCode: boolean;
|
|
232
|
+
toggleHeldShowsCode: () => void;
|
|
233
|
+
} {
|
|
234
|
+
const { activeCell, slideConfigs, noOutputIds } = options;
|
|
235
|
+
const activeCellId = activeCell?.id ?? null;
|
|
236
|
+
const isNoOutputPreview =
|
|
237
|
+
activeCell != null && noOutputIds.has(activeCell.id);
|
|
238
|
+
const isSkippedPreview =
|
|
239
|
+
activeCell != null && slideConfigs.get(activeCell.id)?.type === "skip";
|
|
240
|
+
// Genuinely parked: skipped in the deck, or no output to compose yet.
|
|
241
|
+
const baseParked = isSkippedPreview || isNoOutputPreview;
|
|
242
|
+
|
|
243
|
+
// The cell pinned in the overlay, tracked alongside the active cell it was
|
|
244
|
+
// armed against so we can release it exactly when the active cell changes.
|
|
245
|
+
const [held, setHeld] = useState<{
|
|
246
|
+
activeCellId: CellId | null;
|
|
247
|
+
cellId: CellId | null;
|
|
248
|
+
}>({ activeCellId, cellId: null });
|
|
249
|
+
|
|
250
|
+
let heldCellId = held.cellId;
|
|
251
|
+
if (held.activeCellId !== activeCellId) {
|
|
252
|
+
// Active cell changed: drop any prior hold, arming a fresh one only while
|
|
253
|
+
// the new cell has no output yet (skipped cells park via `baseParked`).
|
|
254
|
+
heldCellId = isNoOutputPreview ? activeCellId : null;
|
|
255
|
+
setHeld({ activeCellId, cellId: heldCellId });
|
|
256
|
+
} else if (isNoOutputPreview && heldCellId !== activeCellId) {
|
|
257
|
+
// Same active cell, still output-less: (re)arm the hold.
|
|
258
|
+
heldCellId = activeCellId;
|
|
259
|
+
setHeld({ activeCellId, cellId: heldCellId });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const isHeldEdit =
|
|
263
|
+
!baseParked && activeCellId != null && heldCellId === activeCellId;
|
|
264
|
+
// Keep the held cell out of the composed deck so its editor isn't mounted a
|
|
265
|
+
// second time (the overlay already renders it); it rejoins once released.
|
|
266
|
+
const heldEditCellId = isHeldEdit ? heldCellId : null;
|
|
267
|
+
|
|
268
|
+
// Code visibility for the held overlay. Defaults to showing the editor so it
|
|
269
|
+
// survives the no-output -> output transition mid-edit; the `C` toggle can
|
|
270
|
+
// hide it on demand.
|
|
271
|
+
const [heldShow, setHeldShow] = useState<{
|
|
272
|
+
cellId: CellId | null;
|
|
273
|
+
show: boolean;
|
|
274
|
+
}>({ cellId: heldEditCellId, show: true });
|
|
275
|
+
let heldShowsCode = heldShow.show;
|
|
276
|
+
if (heldShow.cellId !== heldEditCellId) {
|
|
277
|
+
heldShowsCode = true;
|
|
278
|
+
setHeldShow({ cellId: heldEditCellId, show: true });
|
|
279
|
+
}
|
|
280
|
+
const toggleHeldShowsCode = useEvent(() =>
|
|
281
|
+
setHeldShow((prev) => ({ ...prev, show: !prev.show })),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
parkedPreviewCell: baseParked || isHeldEdit ? (activeCell ?? null) : null,
|
|
286
|
+
isHeldEdit,
|
|
287
|
+
isNoOutputPreview,
|
|
288
|
+
heldEditCellId,
|
|
289
|
+
heldShowsCode,
|
|
290
|
+
toggleHeldShowsCode,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
159
294
|
const SubslideView = ({
|
|
160
295
|
subslide,
|
|
161
|
-
|
|
296
|
+
resolveShowCode,
|
|
162
297
|
isEditable,
|
|
163
298
|
slideConfigs,
|
|
164
299
|
}: {
|
|
165
300
|
subslide: ComposedSubslide<RuntimeCell>;
|
|
166
|
-
|
|
301
|
+
resolveShowCode: (cellId: CellId) => boolean;
|
|
167
302
|
isEditable: boolean;
|
|
168
303
|
slideConfigs: ReadonlyMap<CellId, SlideConfig>;
|
|
169
304
|
}) => {
|
|
@@ -172,12 +307,16 @@ const SubslideView = ({
|
|
|
172
307
|
slideConfigs,
|
|
173
308
|
);
|
|
174
309
|
|
|
310
|
+
const anyCodeShown = subslide.blocks.some((block) =>
|
|
311
|
+
block.cells.some((cell) => resolveShowCode(cell.id)),
|
|
312
|
+
);
|
|
313
|
+
|
|
175
314
|
return (
|
|
176
315
|
<Slide>
|
|
177
316
|
<div className="h-full w-full overflow-auto flex">
|
|
178
317
|
<div
|
|
179
318
|
className={
|
|
180
|
-
|
|
319
|
+
anyCodeShown
|
|
181
320
|
? "mo-slide-content flex flex-col gap-3"
|
|
182
321
|
: "mo-slide-content"
|
|
183
322
|
}
|
|
@@ -187,7 +326,7 @@ const SubslideView = ({
|
|
|
187
326
|
>
|
|
188
327
|
{subslide.blocks.map((block, i) => {
|
|
189
328
|
const rendered = block.cells.map((cell) => {
|
|
190
|
-
if (!
|
|
329
|
+
if (!resolveShowCode(cell.id)) {
|
|
191
330
|
return (
|
|
192
331
|
<CellOutputSlide
|
|
193
332
|
key={cell.id}
|
|
@@ -222,22 +361,36 @@ const SubslideView = ({
|
|
|
222
361
|
);
|
|
223
362
|
};
|
|
224
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Whether the parked overlay renders the cell's *source* instead of its output.
|
|
366
|
+
*/
|
|
367
|
+
export function parkedRendersSource(options: {
|
|
368
|
+
isNoOutputPreview: boolean;
|
|
369
|
+
isEditable: boolean;
|
|
370
|
+
showCode: boolean;
|
|
371
|
+
}): boolean {
|
|
372
|
+
const { isNoOutputPreview, isEditable, showCode } = options;
|
|
373
|
+
return isNoOutputPreview ? isEditable || showCode : showCode;
|
|
374
|
+
}
|
|
375
|
+
|
|
225
376
|
const ParkedPreviewContent = ({
|
|
226
377
|
cell,
|
|
227
378
|
isNoOutputPreview,
|
|
228
379
|
isEditable,
|
|
229
|
-
|
|
380
|
+
showCode,
|
|
230
381
|
}: {
|
|
231
382
|
cell: RuntimeCell;
|
|
232
383
|
isNoOutputPreview: boolean;
|
|
233
384
|
isEditable: boolean;
|
|
234
|
-
|
|
385
|
+
showCode: boolean;
|
|
235
386
|
}) => {
|
|
236
|
-
if (isNoOutputPreview
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
387
|
+
if (parkedRendersSource({ isNoOutputPreview, isEditable, showCode })) {
|
|
388
|
+
// Editable cells get the live editor; otherwise a read-only source view.
|
|
389
|
+
return isEditable ? (
|
|
390
|
+
<SlideCellView cell={cell} />
|
|
391
|
+
) : (
|
|
392
|
+
<SlideCellReadOnlyView cell={cell} />
|
|
393
|
+
);
|
|
241
394
|
}
|
|
242
395
|
return (
|
|
243
396
|
<CellOutputSlide
|
|
@@ -285,39 +438,68 @@ const RevealSlidesComponent = ({
|
|
|
285
438
|
[kioskMode],
|
|
286
439
|
);
|
|
287
440
|
|
|
288
|
-
|
|
441
|
+
// Store the state of the code toggle for each cell
|
|
442
|
+
// This acts like a 'peek' at the code.
|
|
443
|
+
const [showCodeOverrides, setShowCodeOverrides] = useState<
|
|
444
|
+
ReadonlySet<CellId>
|
|
445
|
+
>(() => new Set());
|
|
289
446
|
const codeAvailable = useNotebookCodeAvailable(slideCells);
|
|
290
447
|
const codeToggleEnabled = !isIslands() && codeAvailable;
|
|
291
|
-
const codeShown = codeToggleEnabled && showCode;
|
|
292
448
|
|
|
293
449
|
const activeCell = activeIndex != null ? slideCells[activeIndex] : undefined;
|
|
294
450
|
// Fall back to the first cell while the deck settles on an initial slide.
|
|
295
451
|
// Still `undefined` when the deck is empty (handled below).
|
|
296
452
|
const activeConfigCell = activeCell ?? slideCells.at(0);
|
|
297
453
|
|
|
454
|
+
const {
|
|
455
|
+
parkedPreviewCell,
|
|
456
|
+
isHeldEdit,
|
|
457
|
+
isNoOutputPreview,
|
|
458
|
+
heldEditCellId,
|
|
459
|
+
heldShowsCode,
|
|
460
|
+
toggleHeldShowsCode,
|
|
461
|
+
} = useParkedPreview({
|
|
462
|
+
activeCell,
|
|
463
|
+
slideConfigs: layout.cells,
|
|
464
|
+
noOutputIds,
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const resolveShowCode = (cellId: CellId | undefined): boolean =>
|
|
468
|
+
shouldShowCode({
|
|
469
|
+
cells: layout.cells,
|
|
470
|
+
cellId,
|
|
471
|
+
showCodeOverrides,
|
|
472
|
+
codeToggleEnabled,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// `C` and the toolbar button target the active slide's cell (the revealed
|
|
476
|
+
// fragment when stepping through a stack, otherwise the lead cell).
|
|
477
|
+
const cellIdToShowCode = activeCell?.id ?? activeConfigCell?.id;
|
|
478
|
+
const cellShowsCode = isHeldEdit
|
|
479
|
+
? heldShowsCode
|
|
480
|
+
: resolveShowCode(cellIdToShowCode);
|
|
481
|
+
|
|
482
|
+
// A slide persisted with `showCode: true` always renders code
|
|
483
|
+
const codeAlwaysShown =
|
|
484
|
+
codeToggleEnabled &&
|
|
485
|
+
cellIdToShowCode != null &&
|
|
486
|
+
(layout.cells.get(cellIdToShowCode)?.showCode ?? false);
|
|
487
|
+
|
|
298
488
|
const composition = useMemo(
|
|
299
489
|
() =>
|
|
300
490
|
composeSlides({
|
|
301
491
|
cells: slideCells,
|
|
302
492
|
getType: (cell) =>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
493
|
+
deckSlideType({
|
|
494
|
+
cell,
|
|
495
|
+
noOutputIds,
|
|
496
|
+
heldEditCellId,
|
|
497
|
+
slideConfigs: layout.cells,
|
|
498
|
+
}),
|
|
306
499
|
}),
|
|
307
|
-
[slideCells, noOutputIds, layout.cells],
|
|
500
|
+
[slideCells, noOutputIds, layout.cells, heldEditCellId],
|
|
308
501
|
);
|
|
309
502
|
|
|
310
|
-
// Skipped and output-less cells aren't part of the composed deck. When one is
|
|
311
|
-
// selected in the minimap we render a preview over the deck and park reveal on
|
|
312
|
-
// a neighboring real slide; keyboard nav while parked is handled below.
|
|
313
|
-
const activeCellSlideType = activeCell
|
|
314
|
-
? layout.cells.get(activeCell.id)?.type
|
|
315
|
-
: undefined;
|
|
316
|
-
const isNoOutputPreview =
|
|
317
|
-
activeCell != null && noOutputIds.has(activeCell.id);
|
|
318
|
-
const isParkedPreview = activeCellSlideType === "skip" || isNoOutputPreview;
|
|
319
|
-
const parkedPreviewCell = isParkedPreview ? activeCell : null;
|
|
320
|
-
|
|
321
503
|
const { cellToTarget, targetToCellIndex } = useMemo(
|
|
322
504
|
() =>
|
|
323
505
|
buildSlideIndices({
|
|
@@ -339,6 +521,7 @@ const RevealSlidesComponent = ({
|
|
|
339
521
|
url.searchParams.set("show-chrome", "false");
|
|
340
522
|
return url.toString();
|
|
341
523
|
}, []);
|
|
524
|
+
|
|
342
525
|
const revealConfig: RevealConfig = useMemo(
|
|
343
526
|
() => ({
|
|
344
527
|
embedded: true,
|
|
@@ -381,14 +564,35 @@ const RevealSlidesComponent = ({
|
|
|
381
564
|
// the state update so the button/keypress paints first and the heavier mount
|
|
382
565
|
// can be interrupted by higher-priority work.
|
|
383
566
|
const toggleShowCode = useEvent(() => {
|
|
384
|
-
|
|
567
|
+
if (cellIdToShowCode == null || codeAlwaysShown) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (isHeldEdit) {
|
|
571
|
+
toggleHeldShowsCode();
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
startTransition(() =>
|
|
575
|
+
setShowCodeOverrides((prev) => {
|
|
576
|
+
const next = new Set(prev);
|
|
577
|
+
if (next.has(cellIdToShowCode)) {
|
|
578
|
+
next.delete(cellIdToShowCode);
|
|
579
|
+
} else {
|
|
580
|
+
next.add(cellIdToShowCode);
|
|
581
|
+
}
|
|
582
|
+
return next;
|
|
583
|
+
}),
|
|
584
|
+
);
|
|
385
585
|
});
|
|
386
586
|
|
|
387
587
|
const handleDeckReady = useEvent((deck: RevealApi) => {
|
|
388
588
|
navigateDeckToActiveCell(deck);
|
|
389
589
|
if (codeToggleEnabled) {
|
|
390
590
|
deck.addKeyBinding(
|
|
391
|
-
{
|
|
591
|
+
{
|
|
592
|
+
keyCode: 67,
|
|
593
|
+
key: "C",
|
|
594
|
+
description: "Toggle code editor",
|
|
595
|
+
},
|
|
392
596
|
toggleShowCode,
|
|
393
597
|
);
|
|
394
598
|
}
|
|
@@ -404,17 +608,6 @@ const RevealSlidesComponent = ({
|
|
|
404
608
|
}
|
|
405
609
|
});
|
|
406
610
|
|
|
407
|
-
const activeSubslide = useMemo(() => {
|
|
408
|
-
if (!activeCell) {
|
|
409
|
-
return null;
|
|
410
|
-
}
|
|
411
|
-
const target = cellToTarget.get(activeCell.id);
|
|
412
|
-
if (!target) {
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
return { h: target.h, v: target.v };
|
|
416
|
-
}, [activeCell, cellToTarget]);
|
|
417
|
-
|
|
418
611
|
// Forward the deck's current cell to the parent, except while a parked
|
|
419
612
|
// preview is parked: every reveal.js event during that window is an echo
|
|
420
613
|
// of the programmatic park (possibly with transient indices), so ignoring
|
|
@@ -466,9 +659,22 @@ const RevealSlidesComponent = ({
|
|
|
466
659
|
|
|
467
660
|
useEventListener(document, "keydown", handleParkedNavKey, { capture: true });
|
|
468
661
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
662
|
+
// `isHeldEdit` means the cell already produces output and is only kept in the
|
|
663
|
+
// overlay so the editor survives the edit, so the parked banners don't apply.
|
|
664
|
+
const parkedPreviewLabel = isHeldEdit
|
|
665
|
+
? null
|
|
666
|
+
: isNoOutputPreview
|
|
667
|
+
? "Hidden as there is no output"
|
|
668
|
+
: "Skipped in presentation";
|
|
669
|
+
|
|
670
|
+
const parkedShowCode = isHeldEdit
|
|
671
|
+
? heldShowsCode
|
|
672
|
+
: resolveShowCode(parkedPreviewCell?.id);
|
|
673
|
+
const parkedShowsSource = parkedRendersSource({
|
|
674
|
+
isNoOutputPreview,
|
|
675
|
+
isEditable,
|
|
676
|
+
showCode: parkedShowCode,
|
|
677
|
+
});
|
|
472
678
|
|
|
473
679
|
const slideArea = (
|
|
474
680
|
<div
|
|
@@ -488,13 +694,11 @@ const RevealSlidesComponent = ({
|
|
|
488
694
|
>
|
|
489
695
|
{composition.stacks.map((stack, h) => {
|
|
490
696
|
if (stack.subslides.length === 1) {
|
|
491
|
-
const isActive =
|
|
492
|
-
activeSubslide?.h === h && activeSubslide?.v === 0;
|
|
493
697
|
return (
|
|
494
698
|
<SubslideView
|
|
495
699
|
key={h}
|
|
496
700
|
subslide={stack.subslides[0]}
|
|
497
|
-
|
|
701
|
+
resolveShowCode={resolveShowCode}
|
|
498
702
|
isEditable={isEditable}
|
|
499
703
|
slideConfigs={layout.cells}
|
|
500
704
|
/>
|
|
@@ -503,13 +707,11 @@ const RevealSlidesComponent = ({
|
|
|
503
707
|
return (
|
|
504
708
|
<Stack key={h}>
|
|
505
709
|
{stack.subslides.map((sub, v) => {
|
|
506
|
-
const isActive =
|
|
507
|
-
activeSubslide?.h === h && activeSubslide?.v === v;
|
|
508
710
|
return (
|
|
509
711
|
<SubslideView
|
|
510
712
|
key={v}
|
|
511
713
|
subslide={sub}
|
|
512
|
-
|
|
714
|
+
resolveShowCode={resolveShowCode}
|
|
513
715
|
isEditable={isEditable}
|
|
514
716
|
slideConfigs={layout.cells}
|
|
515
717
|
/>
|
|
@@ -523,16 +725,18 @@ const RevealSlidesComponent = ({
|
|
|
523
725
|
<div
|
|
524
726
|
key={parkedPreviewCell.id}
|
|
525
727
|
className="absolute inset-0 z-10 border rounded bg-background flex flex-col overflow-hidden"
|
|
526
|
-
aria-label={parkedPreviewLabel}
|
|
728
|
+
aria-label={parkedPreviewLabel ?? undefined}
|
|
527
729
|
>
|
|
528
|
-
|
|
529
|
-
<
|
|
530
|
-
|
|
531
|
-
|
|
730
|
+
{parkedPreviewLabel && (
|
|
731
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground border-b bg-muted/40">
|
|
732
|
+
<EyeOffIcon className="h-3.5 w-3.5" />
|
|
733
|
+
<span>{parkedPreviewLabel}</span>
|
|
734
|
+
</div>
|
|
735
|
+
)}
|
|
532
736
|
<div className="flex-1 overflow-auto flex">
|
|
533
737
|
<div
|
|
534
738
|
className={
|
|
535
|
-
|
|
739
|
+
parkedShowsSource
|
|
536
740
|
? "mo-slide-content flex flex-col gap-3"
|
|
537
741
|
: "mo-slide-content"
|
|
538
742
|
}
|
|
@@ -542,7 +746,7 @@ const RevealSlidesComponent = ({
|
|
|
542
746
|
cell={parkedPreviewCell}
|
|
543
747
|
isNoOutputPreview={isNoOutputPreview}
|
|
544
748
|
isEditable={isEditable}
|
|
545
|
-
|
|
749
|
+
showCode={parkedShowCode}
|
|
546
750
|
/>
|
|
547
751
|
</div>
|
|
548
752
|
</div>
|
|
@@ -550,17 +754,35 @@ const RevealSlidesComponent = ({
|
|
|
550
754
|
)}
|
|
551
755
|
<div className="absolute top-2 right-2 z-20 opacity-0 group-hover:opacity-70 text-muted-foreground transition-opacity">
|
|
552
756
|
{codeToggleEnabled && (
|
|
553
|
-
<Tooltip
|
|
757
|
+
<Tooltip
|
|
758
|
+
content={
|
|
759
|
+
codeAlwaysShown
|
|
760
|
+
? "Code is always shown for this slide"
|
|
761
|
+
: cellShowsCode
|
|
762
|
+
? "Hide code (C)"
|
|
763
|
+
: "Show code (C)"
|
|
764
|
+
}
|
|
765
|
+
>
|
|
554
766
|
<Button
|
|
555
767
|
data-testid="marimo-plugin-slides-toggle-code"
|
|
556
768
|
variant="ghost"
|
|
557
769
|
size="icon"
|
|
770
|
+
// Stay hoverable (no `disabled` attr) so the tooltip can
|
|
771
|
+
// explain why the toggle is inert when code is pinned on.
|
|
558
772
|
className={cn(
|
|
559
773
|
"text-muted-foreground h-7 w-7",
|
|
560
|
-
|
|
774
|
+
cellShowsCode && "text-foreground bg-muted",
|
|
775
|
+
codeAlwaysShown && "opacity-50 cursor-not-allowed",
|
|
561
776
|
)}
|
|
562
|
-
aria-pressed={
|
|
563
|
-
aria-
|
|
777
|
+
aria-pressed={cellShowsCode}
|
|
778
|
+
aria-disabled={codeAlwaysShown}
|
|
779
|
+
aria-label={
|
|
780
|
+
codeAlwaysShown
|
|
781
|
+
? "Code always shown"
|
|
782
|
+
: cellShowsCode
|
|
783
|
+
? "Hide code"
|
|
784
|
+
: "Show code"
|
|
785
|
+
}
|
|
564
786
|
onClick={toggleShowCode}
|
|
565
787
|
>
|
|
566
788
|
<CodeIcon className="h-4 w-4" />
|
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
import { useMemo, useRef, useState } from "react";
|
|
4
4
|
import type { EditorView } from "@codemirror/view";
|
|
5
5
|
import { useAtomValue } from "jotai";
|
|
6
|
+
import useEvent from "react-use-event-hook";
|
|
6
7
|
import { cellDomProps } from "@/components/editor/common";
|
|
7
8
|
import { CellEditor } from "@/components/editor/cell/code/cell-editor";
|
|
9
|
+
import { LanguageToggles } from "@/components/editor/cell/code/language-toggle";
|
|
8
10
|
import { CellStatusComponent } from "@/components/editor/cell/CellStatus";
|
|
9
11
|
import { RunButton } from "@/components/editor/cell/RunButton";
|
|
10
12
|
import { StopButton } from "@/components/editor/cell/StopButton";
|
|
11
13
|
import { useRunCell } from "@/components/editor/cell/useRunCells";
|
|
12
14
|
import { Slide as CellOutputSlide } from "@/components/slides/slide";
|
|
13
|
-
import {
|
|
15
|
+
import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
|
|
16
|
+
import { useCellActions } from "@/core/cells/cells";
|
|
17
|
+
import { autoInstantiateAtom, useUserConfig } from "@/core/config/config";
|
|
14
18
|
import {
|
|
15
19
|
cellNeedsRun,
|
|
16
20
|
cellStatusClasses,
|
|
@@ -37,12 +41,24 @@ export const SlideCellView = ({ cell }: { cell: RuntimeCell }) => {
|
|
|
37
41
|
const { theme } = useTheme();
|
|
38
42
|
const runCell = useRunCell(cell.id);
|
|
39
43
|
const connection = useAtomValue(connectionAtom);
|
|
44
|
+
const cellActions = useCellActions();
|
|
45
|
+
const autoInstantiate = useAtomValue(autoInstantiateAtom);
|
|
40
46
|
const editorViewRef = useRef<EditorView | null>(null);
|
|
41
47
|
const editorViewParentRef = useRef<HTMLDivElement | null>(null);
|
|
42
48
|
const [languageAdapter, setLanguageAdapter] = useState<
|
|
43
49
|
LanguageAdapterType | undefined
|
|
44
50
|
>();
|
|
45
51
|
|
|
52
|
+
const afterToggleLanguage = useEvent(() => {
|
|
53
|
+
maybeAddMarimoImport({
|
|
54
|
+
autoInstantiate,
|
|
55
|
+
createNewCell: cellActions.createNewCell,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Must be a stable identity: it feeds the editor's `extensions` memo
|
|
60
|
+
const showHiddenCode = useEvent(() => undefined);
|
|
61
|
+
|
|
46
62
|
const cellOutputPosition = userConfig.display.cell_output;
|
|
47
63
|
const hasOutput = cell.output != null;
|
|
48
64
|
|
|
@@ -97,6 +113,13 @@ export const SlideCellView = ({ cell }: { cell: RuntimeCell }) => {
|
|
|
97
113
|
lastRunStartTimestamp={cell.lastRunStartTimestamp}
|
|
98
114
|
uninstantiated={uninstantiated}
|
|
99
115
|
/>
|
|
116
|
+
<LanguageToggles
|
|
117
|
+
code={cell.code}
|
|
118
|
+
editorView={editorViewRef.current}
|
|
119
|
+
currentLanguageAdapter={languageAdapter}
|
|
120
|
+
onAfterToggle={afterToggleLanguage}
|
|
121
|
+
className="flex items-center gap-1"
|
|
122
|
+
/>
|
|
100
123
|
<div className="flex items-center shadow-none gap-1">
|
|
101
124
|
<RunButton
|
|
102
125
|
edited={cell.edited}
|
|
@@ -113,6 +136,7 @@ export const SlideCellView = ({ cell }: { cell: RuntimeCell }) => {
|
|
|
113
136
|
|
|
114
137
|
const editor = (
|
|
115
138
|
<div
|
|
139
|
+
tabIndex={-1}
|
|
116
140
|
className={editorWrapperClassName}
|
|
117
141
|
{...cellDomProps(cell.id, cell.name)}
|
|
118
142
|
>
|
|
@@ -134,7 +158,7 @@ export const SlideCellView = ({ cell }: { cell: RuntimeCell }) => {
|
|
|
134
158
|
hasOutput={hasOutput}
|
|
135
159
|
// hide_code is intentionally overridden in the slide view; the editor
|
|
136
160
|
// is unmounted entirely when the user toggles code off.
|
|
137
|
-
showHiddenCode={
|
|
161
|
+
showHiddenCode={showHiddenCode}
|
|
138
162
|
languageAdapter={languageAdapter}
|
|
139
163
|
setLanguageAdapter={setLanguageAdapter}
|
|
140
164
|
showLanguageToggles={false}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
PanelRightOpenIcon,
|
|
11
11
|
KeyboardIcon,
|
|
12
12
|
} from "lucide-react";
|
|
13
|
+
import { Switch } from "@/components/ui/switch";
|
|
13
14
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
14
15
|
import {
|
|
15
16
|
Select,
|
|
@@ -196,13 +197,22 @@ const SlideConfigForm = ({
|
|
|
196
197
|
setLayout: (layout: SlidesLayout) => void;
|
|
197
198
|
cellId: CellId;
|
|
198
199
|
}) => {
|
|
199
|
-
const
|
|
200
|
-
|
|
200
|
+
const currentConfig = layout.cells.get(cellId);
|
|
201
|
+
const currentSlideType: SlideType = currentConfig?.type ?? DEFAULT_SLIDE_TYPE;
|
|
202
|
+
const showCode = currentConfig?.showCode ?? false;
|
|
201
203
|
|
|
202
204
|
const handleSlideTypeChange = (value: SlideType) => {
|
|
203
|
-
const existingConfig = layout.cells.get(cellId);
|
|
204
205
|
const newCells = new Map(layout.cells);
|
|
205
|
-
newCells.set(cellId, { ...
|
|
206
|
+
newCells.set(cellId, { ...currentConfig, type: value });
|
|
207
|
+
setLayout({
|
|
208
|
+
...layout,
|
|
209
|
+
cells: newCells,
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const handleShowCodeChange = (checked: boolean) => {
|
|
214
|
+
const newCells = new Map(layout.cells);
|
|
215
|
+
newCells.set(cellId, { ...currentConfig, showCode: checked });
|
|
206
216
|
setLayout({
|
|
207
217
|
...layout,
|
|
208
218
|
cells: newCells,
|
|
@@ -261,6 +271,18 @@ const SlideConfigForm = ({
|
|
|
261
271
|
);
|
|
262
272
|
})}
|
|
263
273
|
</RadioGroup>
|
|
274
|
+
<div className="flex items-center gap-2">
|
|
275
|
+
<label htmlFor="slide-show-code" className="text-sm">
|
|
276
|
+
Show code
|
|
277
|
+
</label>
|
|
278
|
+
<Switch
|
|
279
|
+
id="slide-show-code"
|
|
280
|
+
aria-label="Show code"
|
|
281
|
+
checked={showCode}
|
|
282
|
+
onCheckedChange={handleShowCodeChange}
|
|
283
|
+
size="sm"
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
264
286
|
</div>
|
|
265
287
|
);
|
|
266
288
|
};
|