Improve demo by making full screen, add control bar

This commit is contained in:
Daniel Imms
2025-12-24 16:43:35 -08:00
parent c04806fc92
commit 40b2bbef34
4 changed files with 184 additions and 44 deletions
+11
View File
@@ -19,6 +19,7 @@ if ('WebAssembly' in window) {
import { Terminal, ITerminalOptions, type IDisposable, type ITheme } from '@xterm/xterm';
import { AttachAddon } from '@xterm/addon-attach';
import { initControlBar } from './components/controlBar';
import { ClipboardAddon } from '@xterm/addon-clipboard';
import { FitAddon } from '@xterm/addon-fit';
import { LigaturesAddon } from '@xterm/addon-ligatures';
@@ -231,6 +232,7 @@ if (document.location.pathname === '/test') {
window.WebglAddon = WebglAddon;
} else {
createTerminal();
initControlBar();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
document.getElementById('create-new-window').addEventListener('click', createNewWindowButtonHandler);
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
@@ -337,6 +339,7 @@ function createTerminal(): void {
}
term.focus();
updateTerminalContainerBackground();
const resizeObserver = new ResizeObserver(entries => {
if (autoResize) {
@@ -611,10 +614,18 @@ function initOptions(term: Terminal): void {
}
}
term.options[o] = value;
if (o === 'theme') {
updateTerminalContainerBackground();
}
});
});
}
function updateTerminalContainerBackground(): void {
const bg = term.options.theme?.background ?? '#000000';
terminalContainer.style.backgroundColor = bg;
}
function initAddons(term: Terminal): void {
const fragment = document.createDocumentFragment();
+43
View File
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
export function initControlBar(): void {
// Sidebar resize handling
const sidebar = document.getElementById('sidebar');
const resizeHandleH = document.getElementById('sidebar-resize-handle-horizontal');
const resizeHandleV = document.getElementById('sidebar-resize-handle-vertical');
const resizeHandleCorner = document.getElementById('sidebar-resize-handle-corner');
let resizeMode: 'none' | 'horizontal' | 'vertical' | 'corner' = 'none';
function startResize(mode: 'horizontal' | 'vertical' | 'corner', e: MouseEvent): void {
resizeMode = mode;
document.body.style.cursor = mode === 'horizontal' ? 'ew-resize' : mode === 'vertical' ? 'ns-resize' : 'nesw-resize';
document.body.style.userSelect = 'none';
e.preventDefault();
}
resizeHandleH.addEventListener('mousedown', (e: MouseEvent) => startResize('horizontal', e));
resizeHandleV.addEventListener('mousedown', (e: MouseEvent) => startResize('vertical', e));
resizeHandleCorner.addEventListener('mousedown', (e: MouseEvent) => startResize('corner', e));
document.addEventListener('mousemove', (e: MouseEvent) => {
if (resizeMode === 'none') return;
if (resizeMode === 'horizontal' || resizeMode === 'corner') {
const newWidth = window.innerWidth - e.clientX - 10;
sidebar.style.width = `${Math.max(200, newWidth)}px`;
}
if (resizeMode === 'vertical' || resizeMode === 'corner') {
const rect = sidebar.getBoundingClientRect();
const newHeight = e.clientY - rect.top;
sidebar.style.height = `${Math.max(100, newHeight)}px`;
}
});
document.addEventListener('mouseup', () => {
resizeMode = 'none';
document.body.style.cursor = '';
document.body.style.userSelect = '';
});
}
+33 -15
View File
@@ -12,19 +12,25 @@
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<h1 style="color: #2D2E2C">xterm.js: A terminal for the <em style="color: #5DA5D5">web</em></h1>
<div id="banner">
<span class="banner-title">xterm.js</span>
<div class="banner-tabs">
<button id="optionsbutton" class="tabLinks" onclick="openSection(event, 'options')">Options</button>
<button id="addonsbutton" class="tabLinks" onclick="openSection(event, 'addons')">Addons</button>
<button id="stylebutton" class="tabLinks" onclick="openSection(event, 'style')">Style</button>
<button id="testbutton" class="tabLinks" onclick="openSection(event, 'test')">Test</button>
<button id="vtbutton" class="tabLinks" onclick="openSection(event, 'vt')">VT</button>
<button id="gpubutton" class="tabLinks" onclick="openSection(event, 'gpu')">GPU</button>
</div>
</div>
<div id="container">
<div class="grid">
<div id="terminal-container"></div>
</div>
<div class="grid">
<div class="tab">
<button id="optionsbutton" class="tabLinks" onclick="openSection(event, 'options')">Options</button>
<button id="addonsbutton" class="tabLinks" onclick="openSection(event, 'addons')">Addons</button>
<button id="stylebutton" class="tabLinks" onclick="openSection(event, 'style')">Style</button>
<button id="testbutton" class="tabLinks" onclick="openSection(event, 'test')">Test</button>
<button id="vtbutton" class="tabLinks" onclick="openSection(event, 'vt')">VT</button>
</div>
<div id="sidebar" class="grid">
<div id="sidebar-resize-handle-horizontal"></div>
<div id="sidebar-resize-handle-vertical"></div>
<div id="sidebar-resize-handle-corner"></div>
<div id="options" class="tabContent">
<h3>Options</h3>
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.options</code> property.</p>
@@ -169,12 +175,13 @@
<h3>VT</h3>
<div id="vt-container"></div>
</div>
<div id="gpu" class="tabContent">
<h3>GPU</h3>
<input type="checkbox" id="texture-atlas-zoom"/>
<label for="texture-atlas-zoom">Zoom texture atlas</label>
<div id="texture-atlas"></div>
</div>
</div>
</div>
<div id="texture-atlas-container">
<input type="checkbox" id="texture-atlas-zoom"/>
<label for="texture-atlas-zoom">Zoom texture atlas</label>
<div id="texture-atlas"></div>
</div>
<script src="dist/client-bundle.js" defer ></script>
<script>
@@ -194,12 +201,23 @@
document.getElementById(""+tab+"button").classList.add("active");
}
function openSection(event, section) {
const sidebar = document.getElementById("sidebar");
const tabContent = document.getElementsByClassName("tabContent");
const tabLinks = document.getElementsByClassName("tabLinks");
let itr;
// If clicking active tab, toggle sidebar visibility
if (event.currentTarget.classList.contains("active")) {
sidebar.classList.toggle("sidebar-hidden");
return;
}
// Show sidebar if hidden
sidebar.classList.remove("sidebar-hidden");
for (itr = 0; itr < tabContent.length; itr+=1) {
tabContent[itr].style.display = "none";
}
const tabLinks = document.getElementsByClassName("tabLinks");
for (itr = 0; itr < tabLinks.length; itr+=1) {
tabLinks[itr].className = tabLinks[itr].className.replace(" active", "");
}
+97 -29
View File
@@ -2,16 +2,50 @@ body {
font-family: helvetica, sans-serif, arial;
font-size: 1em;
color: #111;
margin: 0;
padding: 0;
height: 100vh;
display: grid;
grid-template-rows: 24px 1fr;
}
h1 {
text-align: center;
/* Top banner */
#banner {
background: #2D2E2C;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
border-bottom: 1px solid #0005;
}
.banner-title {
color: #5DA5D5;
font-weight: bold;
font-size: 14px;
}
.banner-tabs {
display: flex;
gap: 2px;
}
.banner-tabs button {
background: transparent;
border: none;
color: #ccc;
padding: 4px 10px;
cursor: pointer;
font-size: 12px;
}
.banner-tabs button:hover {
background: rgba(255,255,255,0.1);
}
.banner-tabs button.active {
background: rgba(255,255,255,0.2);
color: #fff;
}
#terminal-container {
height: 60%;
margin: 0 auto;
padding: 2px;
}
p {
@@ -44,45 +78,79 @@ pre {
#container {
display: flex;
position: relative;
min-height: 0;
}
.grid {
flex: 1;
/* max-height: 80vh;
overflow-y: auto; */
width: 100%;
min-width: 100px;
}
div:first-of-type.grid {
flex: 2;
height: 60vh;
height: 100%;
}
.tab {
overflow: hidden;
/* Sidebar */
#sidebar {
position: fixed;
top: 24px;
right: 0;
width: 400px;
height: 500px;
background: #fff;
border: 1px solid #ccc;
background-color: #f1f1f1;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 1000;
overflow: auto;
flex: none;
opacity: 0.5;
transition: opacity 0.1s;
}
#sidebar:hover {
opacity: 1;
}
#sidebar.sidebar-hidden {
display: none;
}
/* Style the buttons inside the tab */
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
font-size: 17px;
/* Resize handles */
#sidebar-resize-handle-horizontal {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 6px;
cursor: ew-resize;
background: transparent;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
#sidebar-resize-handle-horizontal:hover {
background: rgba(0, 120, 212, 0.3);
}
#sidebar-resize-handle-vertical {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 6px;
cursor: ns-resize;
background: transparent;
}
#sidebar-resize-handle-vertical:hover {
background: rgba(0, 120, 212, 0.3);
}
#sidebar-resize-handle-corner {
position: absolute;
left: 0;
bottom: 0;
width: 12px;
height: 12px;
cursor: nesw-resize;
background: transparent;
}
#sidebar-resize-handle-corner:hover {
background: rgba(0, 120, 212, 0.5);
}
/* Create an active/current tablink class */
.tab button.active {
background-color: #ccc;
}
/* Style the tab content */
.tabContent {
@@ -92,7 +160,7 @@ div:first-of-type.grid {
border-top: none;
}
#texture-atlas-zoom:checked + label + #texture-atlas canvas {
#texture-atlas-zoom:checked ~ #texture-atlas canvas {
/* Zoom atlas to the width of the container*/
width: 100% !important;
height: auto !important;