/* Copyright 2026 Marimo. All rights reserved. */ import { atom, useAtomValue } from "jotai"; import { isIslands } from "@/core/islands/utils"; import { assertExists } from "@/utils/assertExists"; import { invariant } from "@/utils/invariant"; import type { CellId } from "./cells/ids"; import { store } from "./state/jotai"; /** * This is the internal mode. * - `read`: A user is reading the notebook. Cannot switch to edit/present mode. * - `edit`: A user is editing the notebook. Can switch to present mode. * - `present`: A user is presenting the notebook, it looks like read mode but with some editing features. Cannot switch to present mode. * - `home`: A user is in the home page. * - `gallery`: A user is in the gallery page. */ export type AppMode = "read" | "edit" | "present" | "home" | "gallery"; export function getInitialAppMode(): Exclude { const initialMode = store.get(initialModeAtom); assertExists(initialMode, "internal-error: initial mode not found"); invariant( initialMode !== "present", "internal-error: initial mode cannot be 'present'", ); return initialMode; } export function toggleAppMode(mode: AppMode): AppMode { // Can't switch to present mode. if (mode === "read" || mode === "home" || mode === "gallery") { return mode; } return mode === "edit" ? "present" : "edit"; } /** * View state for the app. */ interface ViewState { /** * The mode of the app: read/edit/present */ mode: AppMode; /** * A cell ID to anchor scrolling to when toggling between presenting and * editing views */ cellAnchor: CellId | null; } export async function runDuringPresentMode( fn: () => void | Promise, ): Promise { const state = store.get(viewStateAtom); if (state.mode === "present") { await fn(); return; } store.set(viewStateAtom, { ...state, mode: "present" }); // Wait 100ms to allow the page to render await new Promise((resolve) => setTimeout(resolve, 100)); // Wait 2 frames await new Promise((resolve) => requestAnimationFrame(resolve)); await new Promise((resolve) => requestAnimationFrame(resolve)); await fn(); store.set(viewStateAtom, { ...state, mode: "edit" }); return undefined; } export const viewStateAtom = atom({ mode: isIslands() ? "read" : ("not-set" as AppMode), cellAnchor: null, }); export const initialModeAtom = atom(undefined); export const kioskModeAtom = atom(false); /** * Whether installing packages is allowed in the current view. False in read * mode, since end-users of a deployed notebook cannot mutate its environment. */ export function useInstallAllowed(): boolean { const { mode } = useAtomValue(viewStateAtom); return mode !== "read"; }