import { isModifierPairShortcut, type DesktopSnapShotSetupAction, type DesktopSnapShotState, } from "@t3tools/contracts"; import { CircleCheckIcon } from "lucide-react"; import { useEffect, useId, useState, type ReactNode } from "react"; import { CaptureShortcutConfig } from "./CaptureShortcutConfig"; import { Button } from "../ui/button"; import { Dialog, DialogDescription } from "../ui/dialog"; import { WizardSteps, WizardPopup, WizardHeader, WizardPanel, WizardFooter } from "../ui/wizard"; import { captureSetupAccessReady, captureSetupBackend, captureSetupCheckMessage, captureSetupDesktopName, captureSetupInitialStep, captureSetupMacPermissionsReady, captureSetupShortcutReady, type CaptureSetupStep, } from "./SnapShotSetupDialog.logic"; const SETUP_STEPS = [ { id: "access", label: "Access" }, { id: "shortcut", label: "Shortcut" }, ] as const; const GNOME_ACCESS_COPY = { "not-installed": { title: "Install the extension", description: "The T3 Code GNOME extension lets you capture other windows and bring them into your draft. Sign out once after installing.", }, "restart-required": { title: "Extension installed", description: "Save your work, then sign out and back in. Your setup will be waiting here.", }, "update-required": { title: "Update the extension", description: "Install the update, then sign out and back in.", }, "extensions-disabled": { title: "Allow GNOME extensions", description: "Open GNOME Extensions and turn on extensions, then check again.", }, disabled: { title: "Enable the extension", description: "Enable T3 Code SnapShots to start capturing windows.", }, enabled: { title: "Capture is ready", description: "Next, choose your shortcut.", }, unsupported: { title: "Automatic capture isn't available", description: "Use Take snapshot from the command palette to choose a window.", }, error: { title: "Couldn't set up the extension", description: "Check T3 Code SnapShots in GNOME Extensions, then try again.", }, }; function ScreenRecordingIcon() { const gradientId = useId(); return ( ); } function AccessibilityPermissionIcon() { const gradientId = useId(); return ( ); } function MacPermissionRow({ icon, title, description, granted, busy, onAllow, }: { icon: ReactNode; title: string; description: string; granted: boolean; busy: boolean; onAllow: () => void; }) { return (
{icon}

{title}

{description}

{granted ? ( ) : ( )}
); } export function SnapShotSetupDialog({ state, initialStep, wasEnabled, includeAccessibility, busy: actionBusy, error, shortcutInput, shortcutStatus, shortcutChanged, canSaveShortcut, onSaveShortcut, onEnable, onAction, onRefresh, onClose, onLeaveStep, }: { state: DesktopSnapShotState; initialStep: CaptureSetupStep; wasEnabled: boolean; includeAccessibility: boolean; busy: boolean; error: string | null; shortcutInput: ReactNode; shortcutStatus: string | null | undefined; shortcutChanged: boolean; canSaveShortcut: boolean; onSaveShortcut: () => Promise; onEnable: () => Promise; onAction: (action: DesktopSnapShotSetupAction) => Promise; onRefresh: () => Promise; onClose: (completed: boolean) => Promise; onLeaveStep: () => void; }) { const [step, setStep] = useState(() => captureSetupInitialStep(state, initialStep)); const [checking, setChecking] = useState(false); const [checked, setChecked] = useState(false); const [configBusy, setConfigBusy] = useState(false); const busy = actionBusy || checking || configBusy; const backend = captureSetupBackend(state); const configShortcut = backend === "niri" || backend === "hyprland"; const desktop = captureSetupDesktopName(state); const extension = state.gnomeExtension; const helper = backend === "hyprland" ? state.hyprlandHelper : state.kdeHelper; const helperBackend = backend === "kde" || backend === "hyprland"; const installHelper = backend === "hyprland" ? "install-hyprland-helper" : "install-kde-helper"; const removeHelper = backend === "hyprland" ? "remove-hyprland-helper" : "remove-kde-helper"; const accessReady = captureSetupAccessReady(state); const macPermissions = state.macPermissions; const macPermissionsReady = captureSetupMacPermissionsReady(state, includeAccessibility); const shortcutReady = captureSetupShortcutReady(state, shortcutChanged); const install = extension?.status === "not-installed" || extension?.status === "update-required"; const enable = extension?.status === "disabled"; useEffect(() => { if (!macPermissions || step !== "access") return; const refresh = () => void onRefresh(); window.addEventListener("focus", refresh); return () => window.removeEventListener("focus", refresh); }, [macPermissions, onRefresh, step]); const changeStep = (next: CaptureSetupStep) => { onLeaveStep(); setChecked(false); setStep(next); }; const checkAgain = async () => { if (busy) return; setChecking(true); setChecked(false); try { setChecked((await onRefresh()) !== undefined); } finally { setChecking(false); } }; const accessCopy = state.message && !macPermissions ? { title: "Let's try that again", description: "Couldn't check snapshots. Try again to continue.", } : backend === "gnome" && extension ? extension.status === "enabled" && !accessReady ? { title: "Check capture access", description: "The extension isn't ready yet. Try again in a moment.", } : GNOME_ACCESS_COPY[extension.status] : helperBackend ? helper?.status === "ready" ? { title: "Capture is ready", description: "Next, choose your shortcut.", } : helper?.status === "error" ? { title: "Let's fix capture access", description: "Try reinstalling the capture helper, then check again.", } : { title: helper?.status === "update-required" ? "Update the capture helper" : "Allow snapshots", description: "T3 Code's capture helper lets you capture other apps and return to your draft. It's included with T3 Code.", } : backend === "niri" ? { title: "Capture is ready", description: "Next, choose your shortcut.", } : backend === "picker" ? { title: "Choose a window each time", description: "Your desktop doesn't support automatic capture. You'll choose the window to capture instead.", } : { title: "Allow snapshots", description: backend === "portal" ? "Your desktop may ask for permission when you first capture." : macPermissions ? macPermissionsReady ? "Test a snapshot of the current window. If macOS asks to bypass its window picker, choose Allow. The test image is discarded." : "Allow each permission, then continue." : "Allow access when prompted to start capturing windows.", }; const title = step === "access" ? accessCopy.title : "Choose your shortcut"; const description = step === "access" ? accessCopy.description : configShortcut ? "Click the shortcut, then press the keys you want." : state.mode === "portal" ? "Choose your keys, then approve the permission prompt if asked." : "Use both Shift keys, or record a different shortcut."; const stepIndex = SETUP_STEPS.findIndex(({ id }) => id === step); const details = [ ...new Set( [ error, ...(step === "access" ? [ state.message, backend === "gnome" && (extension?.status === "error" || extension?.status === "unsupported") ? extension.message : null, helperBackend && helper?.status === "error" ? helper.message : null, ] : []), ].filter((detail) => detail !== null), ), ]; return ( { if (!open && !busy) void onClose(false); }} > item.label)} currentStep={stepIndex} isStepDisabled={(index) => busy || index > stepIndex} onStepChange={(index) => { const next = SETUP_STEPS[index]; if (next && next.id !== step) changeStep(next.id); }} />

{title}

{description}
{step === "access" ? ( <>

{checked && !busy && !error ? captureSetupCheckMessage(state) : null}

{macPermissions ? (
} title="Screen Recording" description="Capture the window you're using." granted={macPermissions.screenRecording} busy={busy} onAllow={() => void onAction("allow-screen-recording")} /> } title="Accessibility" description={ includeAccessibility ? "Include text and controls from the captured app." : "Optional. Include text and controls from the captured app." } granted={macPermissions.accessibility} busy={busy} onAllow={() => void onAction("allow-accessibility")} />
) : null} {helperBackend && helper?.status === "error" ? ( ) : null} ) : configShortcut ? ( onClose(true)} /> ) : (
{shortcutInput} {shortcutStatus ? (

{shortcutStatus}

) : null} {!shortcutChanged && !state.shortcutRegistered && !state.shortcutPending && state.shortcutCanRetry !== false && !isModifierPairShortcut(state.shortcut) ? ( ) : null}
)} {step === "shortcut" && !accessReady ? (

Capture needs attention. Go back to check access.

) : null} {error ? (

Couldn't finish this step. Try again or check Advanced for help.

) : null} {details.length > 0 || (step === "access" && (backend === "gnome" || helperBackend)) ? (
Advanced
{details.map((detail) => (

{detail}

))} {step === "access" && (backend === "gnome" || helperBackend) ? (

Included with T3 Code. No download needed.

) : null} {step === "access" && backend === "gnome" && extension?.status === "enabled" ? ( ) : null} {step === "access" && helperBackend && helper?.status !== "not-installed" ? ( ) : null}
) : null}
{step !== "access" ? ( ) : null} {step === "access" ? ( helperBackend && !accessReady && helper?.status !== "ready" ? ( ) : backend === "gnome" && !accessReady && extension?.status !== "enabled" ? ( ) : ( ) ) : !configShortcut ? ( ) : null}
); }