Skip to content

Commit 3ffa9b6

Browse files
committed
feat(map-server): add fullscreen support, clean UI, sharp rendering
- 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) - Use full device pixel ratio for sharp rendering on high-DPI displays - Resize Cesium viewer when display mode changes
1 parent c1e3cd6 commit 3ffa9b6

2 files changed

Lines changed: 141 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: 118 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)
@@ -107,6 +107,8 @@ async function initCesium(): Promise<any> {
107107
alpha: true,
108108
},
109109
},
110+
// Use full device pixel ratio for sharp rendering on high-DPI displays
111+
useBrowserRecommendedResolution: false,
110112
});
111113
log.info("Viewer created");
112114

@@ -245,21 +247,6 @@ function flyToBoundingBox(
245247
});
246248
}
247249

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-
263250
/**
264251
* Hide the loading indicator
265252
*/
@@ -270,13 +257,86 @@ function hideLoading(): void {
270257
}
271258
}
272259

260+
// Preferred height for inline mode (px)
261+
const PREFERRED_INLINE_HEIGHT = 400;
262+
263+
// Current display mode
264+
let currentDisplayMode: "inline" | "fullscreen" | "pip" = "inline";
265+
273266
// Create App instance with tool capabilities
267+
// autoResize: false - we manually send size since map fills its container
274268
const app = new App(
275269
{ name: "CesiumJS Globe", version: "1.0.0" },
276270
{ tools: { listChanged: true } },
277-
{ autoResize: false }, // Cesium handles its own sizing
271+
{ autoResize: false },
278272
);
279273

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

290350
app.onerror = log.error;
291351

352+
// Listen for host context changes (display mode, theme, etc.)
353+
app.onhostcontextchanged = (params) => {
354+
log.info("Host context changed:", params);
355+
356+
if (params.displayMode) {
357+
handleDisplayModeChange(
358+
params.displayMode as "inline" | "fullscreen" | "pip",
359+
);
360+
}
361+
362+
// Update button if available modes changed
363+
if (params.availableDisplayModes) {
364+
updateFullscreenButton();
365+
}
366+
};
367+
292368
// Handle initial tool input (bounding box from show-map tool)
293369
app.ontoolinput = (params) => {
294370
log.info("Received tool input:", params);
@@ -339,7 +415,6 @@ app.ontoolinput = (params) => {
339415
Cesium.Math.toDegrees(viewer!.camera.pitch),
340416
);
341417
});
342-
setLabel(args?.label);
343418
}, 500);
344419
}
345420
}
@@ -411,6 +486,29 @@ async function init() {
411486
// Connect to host (auto-creates PostMessageTransport)
412487
await app.connect();
413488
log.info("Connected to host");
489+
490+
// Get initial display mode from host context
491+
const context = app.getHostContext();
492+
if (context?.displayMode) {
493+
currentDisplayMode = context.displayMode as
494+
| "inline"
495+
| "fullscreen"
496+
| "pip";
497+
}
498+
log.info("Initial display mode:", currentDisplayMode);
499+
500+
// Tell host our preferred size for inline mode
501+
if (currentDisplayMode === "inline") {
502+
app.sendSizeChanged({ height: PREFERRED_INLINE_HEIGHT });
503+
log.info("Sent initial size:", PREFERRED_INLINE_HEIGHT);
504+
}
505+
506+
// Set up fullscreen button
507+
updateFullscreenButton();
508+
const fullscreenBtn = document.getElementById("fullscreen-btn");
509+
if (fullscreenBtn) {
510+
fullscreenBtn.addEventListener("click", toggleFullscreen);
511+
}
414512
} catch (error) {
415513
log.error("Failed to initialize:", error);
416514
const loadingEl = document.getElementById("loading");

0 commit comments

Comments
 (0)