/* Copyright 2026 Marimo. All rights reserved. */ import { usePrevious } from "@dnd-kit/utilities"; import { Tooltip } from "radix-ui"; const TooltipProvider = Tooltip.Provider; import { useAtomValue, useSetAtom } from "jotai"; import { useEffect } from "react"; import { NotStartedConnectionAlert } from "@/components/editor/alerts/connecting-alert"; import { Controls } from "@/components/editor/controls/Controls"; import { AppHeader } from "@/components/editor/header/app-header"; import { FilenameForm } from "@/components/editor/header/filename-form"; import { MultiCellActionToolbar } from "@/components/editor/navigation/multi-cell-action-toolbar"; import { cn } from "@/utils/cn"; import { Paths } from "@/utils/paths"; import { AppContainer } from "../components/editor/app-container"; import { useRunAllCells, useRunStaleCells, } from "../components/editor/cell/useRunCells"; import { CellArray } from "../components/editor/renderers/cell-array"; import { CellsRenderer } from "../components/editor/renderers/cells-renderer"; import { useHotkey } from "../hooks/useHotkey"; import { hasCellsAtom, notebookIsRunningAtom, numColumnsAtom, useCellActions, } from "./cells/cells"; import type { AppConfig, UserConfig } from "./config/config-schema"; import { RuntimeState } from "./kernel/RuntimeState"; import { getSessionId } from "./kernel/session"; import { useTogglePresenting } from "./layout/useTogglePresenting"; import { viewStateAtom } from "./mode"; import { useRequestClient } from "./network/requests"; import { useFilename } from "./saving/filename"; import { lastSavedNotebookAtom } from "./saving/state"; import { useMarimoKernelConnection } from "./websocket/useMarimoKernelConnection"; interface AppProps { /** * The user config. */ userConfig: UserConfig; /** * The app config. */ appConfig: AppConfig; /** * If true, the floating controls will be hidden. */ hideControls?: boolean; } export const EditApp: React.FC = ({ userConfig, appConfig, hideControls = false, }) => { const { setCells, mergeAllColumns, collapseAllCells, expandAllCells } = useCellActions(); const viewState = useAtomValue(viewStateAtom); const numColumns = useAtomValue(numColumnsAtom); const hasCells = useAtomValue(hasCellsAtom); const filename = useFilename(); const setLastSavedNotebook = useSetAtom(lastSavedNotebookAtom); const { sendComponentValues, sendInterrupt } = useRequestClient(); const isEditing = viewState.mode === "edit"; const isPresenting = viewState.mode === "present"; const isRunning = useAtomValue(notebookIsRunningAtom); // Initialize RuntimeState event-listeners useEffect(() => { RuntimeState.INSTANCE.start(sendComponentValues); return () => { RuntimeState.INSTANCE.stop(); }; }, []); const { connection } = useMarimoKernelConnection({ autoInstantiate: userConfig.runtime.auto_instantiate, setCells: (cells, layout) => { setCells(cells); const names = cells.map((cell) => cell.name); const codes = cells.map((cell) => cell.code); const configs = cells.map((cell) => cell.config); setLastSavedNotebook({ names, codes, configs, layout }); }, sessionId: getSessionId(), }); // Update document title whenever filename or app_title changes useEffect(() => { // Set document title: app_title takes precedence, then filename, then default document.title = appConfig.app_title || Paths.basename(filename ?? "") || "Untitled Notebook"; }, [appConfig.app_title, filename]); // Delete column breakpoints if app width changes from "columns" const previousWidth = usePrevious(appConfig.width); useEffect(() => { if (previousWidth === "columns" && appConfig.width !== "columns") { mergeAllColumns(); } }, [appConfig.width, previousWidth, mergeAllColumns, numColumns]); const runStaleCells = useRunStaleCells(); const runAllCells = useRunAllCells(); const togglePresenting = useTogglePresenting(); // HOTKEYS useHotkey("global.runStale", () => { runStaleCells(); }); useHotkey("global.interrupt", () => { sendInterrupt(); }); useHotkey("global.hideCode", () => { togglePresenting(); }); useHotkey("global.runAll", () => { runAllCells(); }); useHotkey("global.collapseAllSections", () => { collapseAllCells(); }); useHotkey("global.expandAllSections", () => { expandAllCells(); }); const editableCellsArray = ( ); return ( <> {isEditing && (
)}
{/* Don't render until we have a single cell */} {hasCells && ( {editableCellsArray} )} {!hasCells && }
{!hideControls && ( )} ); };