Skip to content

Commit 0a07944

Browse files
author
Max Kroner
committed
feat: mount editor inline when fullscreen unavailable (VS Code support)
When the host doesn't report fullscreen in availableDisplayModes, mount the Excalidraw editor directly as the primary view instead of SVG-only preview. One code path, no overlay or mode toggling. Changes: - Detect canFullscreen from host context availableDisplayModes - Size inline editor via containerDimensions.maxHeight (fallback 500px) - Deduplicate ontoolinput/ontoolresult replays (VS Code scroll behavior) - Show toolbar only when fullscreen is available - Rename .fullscreen-btn to .toolbar-btn, add flex layout to .toolbar
1 parent a617657 commit 0a07944

2 files changed

Lines changed: 39 additions & 14 deletions

File tree

src/global.css

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ body {
8585
height: 100%;
8686
}
8787

88+
/* Inline editor mode (no fullscreen available) */
89+
.main.inline-editor {
90+
overflow: hidden;
91+
}
92+
8893
/* Hide library button in fullscreen editor */
8994
.main.fullscreen .default-sidebar-trigger {
9095
display: none !important;
@@ -101,6 +106,8 @@ body {
101106
top: 8px;
102107
right: 8px;
103108
z-index: 100;
109+
display: flex;
110+
gap: 4px;
104111
opacity: 0;
105112
transition: opacity 0.2s ease;
106113
}
@@ -109,8 +116,8 @@ body {
109116
opacity: 1;
110117
}
111118

112-
/* Fullscreen button */
113-
.fullscreen-btn {
119+
/* Toolbar button */
120+
.toolbar-btn {
114121
display: flex;
115122
align-items: center;
116123
justify-content: center;
@@ -129,14 +136,14 @@ body {
129136
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
130137
}
131138

132-
.fullscreen-btn:hover {
139+
.toolbar-btn:hover {
133140
color: rgba(0, 0, 0, 0.7);
134141
background: rgba(255, 255, 255, 0.8);
135142
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
136143
}
137144

138-
/* Hide fullscreen button in fullscreen mode (host provides exit UI) */
139-
.main.fullscreen .fullscreen-btn {
145+
/* Hide toolbar button in fullscreen mode (host provides exit UI) */
146+
.main.fullscreen .toolbar-btn {
140147
display: none !important;
141148
}
142149

src/mcp-app.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -565,13 +565,16 @@ function ExcalidrawApp() {
565565
const [elements, setElements] = useState<any[]>([]);
566566
const [userEdits, setUserEdits] = useState<any[] | null>(null);
567567
const [containerHeight, setContainerHeight] = useState<number | null>(null);
568+
const [canFullscreen, setCanFullscreen] = useState(false);
569+
const [containerMaxHeight, setContainerMaxHeight] = useState<number | null>(null);
568570
const [editorReady, setEditorReady] = useState(false);
569571
const [excalidrawApi, setExcalidrawApi] = useState<any>(null);
570572
const [editorSettled, setEditorSettled] = useState(false);
571573
const appRef = useRef<App | null>(null);
572574
const svgViewportRef = useRef<ViewportRect | null>(null);
573575
const elementsRef = useRef<any[]>([]);
574576
const checkpointIdRef = useRef<string | null>(null);
577+
const lastInputRef = useRef<string | null>(null);
575578

576579
const toggleFullscreen = useCallback(async () => {
577580
if (!appRef.current) return;
@@ -627,9 +630,9 @@ function ExcalidrawApp() {
627630
}
628631
}, [displayMode, containerHeight]);
629632

630-
// Mount editor when entering fullscreen
633+
// Mount editor when entering fullscreen or when host doesn't support fullscreen
631634
useEffect(() => {
632-
if (displayMode !== "fullscreen") {
635+
if (displayMode !== "fullscreen" && canFullscreen) {
633636
setEditorReady(false);
634637
setExcalidrawApi(null);
635638
setEditorSettled(false);
@@ -639,10 +642,10 @@ function ExcalidrawApp() {
639642
await document.fonts.ready;
640643
setTimeout(() => setEditorReady(true), 200);
641644
})();
642-
}, [displayMode]);
645+
}, [displayMode, canFullscreen]);
643646

644647
// After editor mounts: refresh text dimensions, then reveal
645-
const mountEditor = displayMode === "fullscreen" && inputIsFinal && elements.length > 0 && editorReady;
648+
const mountEditor = (displayMode === "fullscreen" || !canFullscreen) && inputIsFinal && elements.length > 0 && editorReady;
646649
useEffect(() => {
647650
if (!mountEditor || !excalidrawApi) return;
648651
if (editorSettled) return; // already revealed, don't redo
@@ -681,14 +684,25 @@ function ExcalidrawApp() {
681684
appRef.current = app;
682685
_logFn = (msg) => { try { app.sendLog({ level: "info", logger: "FS", data: msg }); } catch {} };
683686

684-
// Capture initial container dimensions
685-
const initDims = app.getHostContext()?.containerDimensions as any;
687+
// Capture initial container dimensions and host capabilities
688+
const initCtx = app.getHostContext() as any;
689+
const initDims = initCtx?.containerDimensions;
686690
if (initDims?.height) setContainerHeight(initDims.height);
691+
if (initDims?.maxHeight) setContainerMaxHeight(initDims.maxHeight);
692+
if (initCtx?.availableDisplayModes) {
693+
setCanFullscreen(initCtx.availableDisplayModes.includes("fullscreen"));
694+
}
687695

688696
app.onhostcontextchanged = (ctx: any) => {
689697
if (ctx.containerDimensions?.height) {
690698
setContainerHeight(ctx.containerDimensions.height);
691699
}
700+
if (ctx.containerDimensions?.maxHeight) {
701+
setContainerMaxHeight(ctx.containerDimensions.maxHeight);
702+
}
703+
if (ctx.availableDisplayModes) {
704+
setCanFullscreen(ctx.availableDisplayModes.includes("fullscreen"));
705+
}
692706
if (ctx.displayMode) {
693707
fsLog(`hostContextChanged: displayMode=${ctx.displayMode}`);
694708
// Sync edited elements when host exits fullscreen
@@ -711,13 +725,17 @@ function ExcalidrawApp() {
711725

712726
app.ontoolinput = async (input) => {
713727
const args = (input as any)?.arguments || input;
728+
const sig = JSON.stringify(args);
729+
if (lastInputRef.current === sig) return;
730+
lastInputRef.current = sig;
714731
setInputIsFinal(true);
715732
setToolInput(args);
716733
};
717734

718735
app.ontoolresult = (result: any) => {
719736
const cpId = (result.structuredContent as { checkpointId?: string })?.checkpointId;
720737
if (cpId) {
738+
if (cpId === checkpointIdRef.current) return;
721739
checkpointIdRef.current = cpId;
722740
setCheckpointId(cpId);
723741
// Use checkpointId as localStorage key for persisting user edits
@@ -741,11 +759,11 @@ function ExcalidrawApp() {
741759
if (!app) return <div className="loading">Connecting...</div>;
742760

743761
return (
744-
<main className={`main${displayMode === "fullscreen" ? " fullscreen" : ""}`} style={displayMode === "fullscreen" && containerHeight ? { height: containerHeight } : undefined}>
745-
{displayMode === "inline" && (
762+
<main className={`main${displayMode === "fullscreen" ? " fullscreen" : ""}${!canFullscreen && mountEditor ? " inline-editor" : ""}`} style={displayMode === "fullscreen" && containerHeight ? { height: containerHeight } : !canFullscreen && mountEditor ? { height: containerMaxHeight ?? 500 } : undefined}>
763+
{displayMode === "inline" && canFullscreen && (
746764
<div className="toolbar">
747765
<button
748-
className="fullscreen-btn"
766+
className="toolbar-btn"
749767
onClick={toggleFullscreen}
750768
title="Enter fullscreen"
751769
>

0 commit comments

Comments
 (0)