Skip to content

Commit 484615f

Browse files
committed
feat(map-server): add fullscreen support, clean UI
- Add fullscreen toggle button (only shown if host supports it) - Use autoResize: false and manually send height of 400px - Hide Cesium UI controls (home, scene mode picker) - Remove location label (gets stale quickly) - Resize Cesium viewer when display mode changes
1 parent c1e3cd6 commit 484615f

2 files changed

Lines changed: 139 additions & 30 deletions

File tree

examples/map-server/mcp-app.html

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,34 @@
2727
html, body, #cesiumContainer {
2828
width: 100%;
2929
height: 100%;
30-
min-height: 400px;
3130
margin: 0;
3231
padding: 0;
3332
overflow: hidden;
3433
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
3534
}
36-
#label {
35+
#fullscreen-btn {
3736
position: absolute;
3837
top: 10px;
39-
left: 10px;
38+
right: 10px;
39+
width: 36px;
40+
height: 36px;
4041
background: rgba(0, 0, 0, 0.7);
41-
color: white;
42-
padding: 8px 16px;
42+
border: none;
4343
border-radius: 6px;
44-
font-size: 14px;
45-
font-weight: 500;
44+
cursor: pointer;
4645
z-index: 1000;
4746
display: none;
48-
max-width: 300px;
49-
word-wrap: break-word;
47+
align-items: center;
48+
justify-content: center;
49+
transition: background 0.2s;
50+
}
51+
#fullscreen-btn:hover {
52+
background: rgba(0, 0, 0, 0.85);
53+
}
54+
#fullscreen-btn svg {
55+
width: 20px;
56+
height: 20px;
57+
fill: white;
5058
}
5159
#loading {
5260
position: absolute;
@@ -64,7 +72,12 @@
6472
</head>
6573
<body>
6674
<div id="cesiumContainer"></div>
67-
<div id="label"></div>
75+
<button id="fullscreen-btn" title="Toggle fullscreen">
76+
<!-- Expand icon (shown when inline) -->
77+
<svg id="expand-icon" viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>
78+
<!-- Compress icon (shown when fullscreen) -->
79+
<svg id="compress-icon" style="display:none" viewBox="0 0 24 24"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/></svg>
80+
</button>
6881
<div id="loading">Loading globe...</div>
6982
<script type="module" src="/src/mcp-app.ts"></script>
7083
</body>

examples/map-server/src/mcp-app.ts

Lines changed: 116 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ async function initCesium(): Promise<any> {
9191
// Disable Ion-dependent features
9292
geocoder: false,
9393
baseLayerPicker: false,
94-
// Simplify UI
94+
// Simplify UI - hide all controls
9595
animation: false,
9696
timeline: false,
97-
homeButton: true,
98-
sceneModePicker: true,
97+
homeButton: false,
98+
sceneModePicker: false,
9999
navigationHelpButton: false,
100100
fullscreenButton: false,
101101
// Disable terrain (requires Ion)
@@ -245,21 +245,6 @@ function flyToBoundingBox(
245245
});
246246
}
247247

248-
/**
249-
* Update the label display
250-
*/
251-
function setLabel(text: string | undefined): void {
252-
const labelEl = document.getElementById("label");
253-
if (labelEl) {
254-
if (text) {
255-
labelEl.textContent = text;
256-
labelEl.style.display = "block";
257-
} else {
258-
labelEl.style.display = "none";
259-
}
260-
}
261-
}
262-
263248
/**
264249
* Hide the loading indicator
265250
*/
@@ -270,13 +255,86 @@ function hideLoading(): void {
270255
}
271256
}
272257

258+
// Preferred height for inline mode (px)
259+
const PREFERRED_INLINE_HEIGHT = 400;
260+
261+
// Current display mode
262+
let currentDisplayMode: "inline" | "fullscreen" | "pip" = "inline";
263+
273264
// Create App instance with tool capabilities
265+
// autoResize: false - we manually send size since map fills its container
274266
const app = new App(
275267
{ name: "CesiumJS Globe", version: "1.0.0" },
276268
{ tools: { listChanged: true } },
277-
{ autoResize: false }, // Cesium handles its own sizing
269+
{ autoResize: false },
278270
);
279271

272+
/**
273+
* Update fullscreen button visibility and icon based on current state
274+
*/
275+
function updateFullscreenButton(): void {
276+
const btn = document.getElementById("fullscreen-btn");
277+
const expandIcon = document.getElementById("expand-icon");
278+
const compressIcon = document.getElementById("compress-icon");
279+
if (!btn || !expandIcon || !compressIcon) return;
280+
281+
// Check if fullscreen is available from host
282+
const context = app.getHostContext();
283+
const availableModes = context?.availableDisplayModes ?? ["inline"];
284+
const canFullscreen = availableModes.includes("fullscreen");
285+
286+
// Show button only if fullscreen is available
287+
btn.style.display = canFullscreen ? "flex" : "none";
288+
289+
// Toggle icons based on current mode
290+
const isFullscreen = currentDisplayMode === "fullscreen";
291+
expandIcon.style.display = isFullscreen ? "none" : "block";
292+
compressIcon.style.display = isFullscreen ? "block" : "none";
293+
btn.title = isFullscreen ? "Exit fullscreen" : "Enter fullscreen";
294+
}
295+
296+
/**
297+
* Request display mode change from host
298+
*/
299+
async function toggleFullscreen(): Promise<void> {
300+
const targetMode =
301+
currentDisplayMode === "fullscreen" ? "inline" : "fullscreen";
302+
log.info("Requesting display mode:", targetMode);
303+
304+
try {
305+
const result = await app.requestDisplayMode({ mode: targetMode });
306+
log.info("Display mode result:", result.mode);
307+
// Note: actual mode change will come via onhostcontextchanged
308+
} catch (error) {
309+
log.error("Failed to change display mode:", error);
310+
}
311+
}
312+
313+
/**
314+
* Handle display mode changes - resize Cesium and update UI
315+
*/
316+
function handleDisplayModeChange(
317+
newMode: "inline" | "fullscreen" | "pip",
318+
): void {
319+
if (newMode === currentDisplayMode) return;
320+
321+
log.info("Display mode changed:", currentDisplayMode, "->", newMode);
322+
currentDisplayMode = newMode;
323+
324+
// Update button state
325+
updateFullscreenButton();
326+
327+
// Tell Cesium to resize to new container dimensions
328+
if (viewer) {
329+
// Small delay to let the host finish resizing
330+
setTimeout(() => {
331+
viewer.resize();
332+
viewer.scene.requestRender();
333+
log.info("Cesium resized for", newMode, "mode");
334+
}, 100);
335+
}
336+
}
337+
280338
// Register handlers BEFORE connecting
281339
app.onteardown = async () => {
282340
log.info("App is being torn down");
@@ -289,6 +347,22 @@ app.onteardown = async () => {
289347

290348
app.onerror = log.error;
291349

350+
// Listen for host context changes (display mode, theme, etc.)
351+
app.onhostcontextchanged = (params) => {
352+
log.info("Host context changed:", params);
353+
354+
if (params.displayMode) {
355+
handleDisplayModeChange(
356+
params.displayMode as "inline" | "fullscreen" | "pip",
357+
);
358+
}
359+
360+
// Update button if available modes changed
361+
if (params.availableDisplayModes) {
362+
updateFullscreenButton();
363+
}
364+
};
365+
292366
// Handle initial tool input (bounding box from show-map tool)
293367
app.ontoolinput = (params) => {
294368
log.info("Received tool input:", params);
@@ -339,7 +413,6 @@ app.ontoolinput = (params) => {
339413
Cesium.Math.toDegrees(viewer!.camera.pitch),
340414
);
341415
});
342-
setLabel(args?.label);
343416
}, 500);
344417
}
345418
}
@@ -411,6 +484,29 @@ async function init() {
411484
// Connect to host (auto-creates PostMessageTransport)
412485
await app.connect();
413486
log.info("Connected to host");
487+
488+
// Get initial display mode from host context
489+
const context = app.getHostContext();
490+
if (context?.displayMode) {
491+
currentDisplayMode = context.displayMode as
492+
| "inline"
493+
| "fullscreen"
494+
| "pip";
495+
}
496+
log.info("Initial display mode:", currentDisplayMode);
497+
498+
// Tell host our preferred size for inline mode
499+
if (currentDisplayMode === "inline") {
500+
app.sendSizeChanged({ height: PREFERRED_INLINE_HEIGHT });
501+
log.info("Sent initial size:", PREFERRED_INLINE_HEIGHT);
502+
}
503+
504+
// Set up fullscreen button
505+
updateFullscreenButton();
506+
const fullscreenBtn = document.getElementById("fullscreen-btn");
507+
if (fullscreenBtn) {
508+
fullscreenBtn.addEventListener("click", toggleFullscreen);
509+
}
414510
} catch (error) {
415511
log.error("Failed to initialize:", error);
416512
const loadingEl = document.getElementById("loading");

0 commit comments

Comments
 (0)