diff --git a/src/OpenTheBox.Web/WebGameHost.cs b/src/OpenTheBox.Web/WebGameHost.cs index 3b7ca7b..db5a2e1 100644 --- a/src/OpenTheBox.Web/WebGameHost.cs +++ b/src/OpenTheBox.Web/WebGameHost.cs @@ -592,7 +592,7 @@ public sealed class WebGameHost ColorSystem = ColorSystemSupport.TrueColor }); bufferConsole.Profile.Width = WebTerminal.Width; - bufferConsole.Profile.Height = WebTerminal.Height; + bufferConsole.Profile.Height = _terminal.Height; bufferConsole.Write(InventoryPanel.Render(_state, _registry, _loc, scrollOffset, selectedIndex: selectedIndex)); diff --git a/src/OpenTheBox.Web/WebTerminal.cs b/src/OpenTheBox.Web/WebTerminal.cs index 83920ad..86c9757 100644 --- a/src/OpenTheBox.Web/WebTerminal.cs +++ b/src/OpenTheBox.Web/WebTerminal.cs @@ -28,7 +28,7 @@ public sealed class WebTerminal }); public const int Width = 120; - public const int Height = 30; + public int Height { get; private set; } = 30; public WebTerminal(IJSRuntime js) { @@ -37,13 +37,24 @@ public sealed class WebTerminal } /// - /// Initializes the xterm.js terminal in the browser. + /// Initializes the xterm.js terminal in the browser and reads actual dimensions. /// public async Task InitAsync() { await _js.InvokeVoidAsync("terminalInterop.init"); + + // Read actual terminal dimensions after fitAddon has sized the terminal + try + { + var dims = await _js.InvokeAsync("terminalInterop.getDimensions"); + if (dims.rows >= 30) + Height = dims.rows; + } + catch { /* fallback to 30 */ } } + private record TerminalDimensions(int cols, int rows); + // ── Output ─────────────────────────────────────────────────────────── /// @@ -83,7 +94,7 @@ public sealed class WebTerminal ColorSystem = ColorSystemSupport.TrueColor }); console.Profile.Width = Width; - console.Profile.Height = Height; + console.Profile.Height = _instance?.Height ?? 50; console.Write(renderable); return writer.ToString(); } @@ -227,6 +238,7 @@ public sealed class WebTerminal while (true) { + int scrollOffset = 0; if (selected >= pageSize) scrollOffset = selected - pageSize + 1; @@ -262,7 +274,6 @@ public sealed class WebTerminal console.MarkupLine("[dim] ▼ ...[/]"); string rendered = writer.ToString().Replace("\n", "\r\n"); - // Count actual line breaks — Split('\n').Length overcounts by 1 due to trailing newline int lineCount = rendered.Count(c => c == '\n'); await WriteAsync(rendered); @@ -278,7 +289,6 @@ public sealed class WebTerminal selected = (selected + 1) % options.Count; break; case ConsoleKey.Enter: - // Clear the rendered selection before returning await WriteAsync($"\x1b[{lineCount}A\x1b[J"); return selected; case ConsoleKey.D1 or ConsoleKey.NumPad1: if (options.Count >= 1) { await WriteAsync($"\x1b[{lineCount}A\x1b[J"); return 0; } break; @@ -292,7 +302,7 @@ public sealed class WebTerminal case ConsoleKey.D9 or ConsoleKey.NumPad9: if (options.Count >= 9) { await WriteAsync($"\x1b[{lineCount}A\x1b[J"); return 8; } break; } - // Clear and re-render + // Clear menu before re-rendering await WriteAsync($"\x1b[{lineCount}A\x1b[J"); } } diff --git a/src/OpenTheBox.Web/wwwroot/css/terminal.css b/src/OpenTheBox.Web/wwwroot/css/terminal.css index 927c4c4..98a179d 100644 --- a/src/OpenTheBox.Web/wwwroot/css/terminal.css +++ b/src/OpenTheBox.Web/wwwroot/css/terminal.css @@ -31,8 +31,7 @@ body { #terminal { width: 100%; height: 100%; - max-width: 960px; - max-height: 600px; + max-width: 1200px; } /* Loading screen */ diff --git a/src/OpenTheBox.Web/wwwroot/js/terminal-interop.js b/src/OpenTheBox.Web/wwwroot/js/terminal-interop.js index 214f866..ea17324 100644 --- a/src/OpenTheBox.Web/wwwroot/js/terminal-interop.js +++ b/src/OpenTheBox.Web/wwwroot/js/terminal-interop.js @@ -24,12 +24,17 @@ window.terminalInterop = { const fitAddon = new FitAddon.FitAddon(); term.loadAddon(fitAddon); + // Show terminal container BEFORE opening so fitAddon can measure real dimensions + document.getElementById('loading').classList.add('hidden'); + document.getElementById('terminal-container').classList.add('active'); + term.open(document.getElementById('terminal')); - // Fit to container, but enforce minimum dimensions for game readability + // Fit to container, enforce minimum dimensions for game readability fitAddon.fit(); - if (term.cols < 120 || term.rows < 30) { - term.resize(Math.max(term.cols, 120), Math.max(term.rows, 30)); + const minCols = 120, minRows = 30; + if (term.cols < minCols || term.rows < minRows) { + term.resize(Math.max(term.cols, minCols), Math.max(term.rows, minRows)); } // Forward key input to C# @@ -40,18 +45,13 @@ window.terminalInterop = { // Handle resize window.addEventListener('resize', function () { fitAddon.fit(); - if (term.cols < 120 || term.rows < 30) { - term.resize(Math.max(term.cols, 120), Math.max(term.rows, 30)); + if (term.cols < minCols || term.rows < minRows) { + term.resize(Math.max(term.cols, minCols), Math.max(term.rows, minRows)); } }); this.term = term; this.fitAddon = fitAddon; - - // Show terminal, hide loading - document.getElementById('loading').classList.add('hidden'); - document.getElementById('terminal-container').classList.add('active'); - term.focus(); }, @@ -72,5 +72,12 @@ window.terminalInterop = { if (this.term) { this.term.focus(); } + }, + + getDimensions: function () { + if (this.term) { + return { cols: this.term.cols, rows: this.term.rows }; + } + return { cols: 120, rows: 30 }; } };