c61712d8c4
- Add shell terminal: new tmux window spawned at project cwd, opened from the Terminal icon in the ChatView header; ShellTerminalView lets you navigate back to chat or jump to the Claude tmux terminal mode - Fix system keyboard appearing on first terminal launch: ghostty-web calls focus() on a contenteditable div inside open(), triggering iOS keyboard before we can suppress it; patch HTMLElement.prototype.focus as a no-op for the synchronous duration of terminal.open() - Add keyboard font-size slider in Settings - Fix virtual keyboard button corner radius and depth scaling with key height - Fix keybar gap/padding proportionality via heightScale factor - Cap keybar font size so it does not grow when bar height is increased Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
2.1 KiB
TypeScript
76 lines
2.1 KiB
TypeScript
import { useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
|
|
import { STORAGE } from '../lib/storage-keys';
|
|
import { WebTerminal } from '../lib/terminal/WebTerminal';
|
|
import type { TerminalConfig } from '../lib/terminal/terminal-config';
|
|
|
|
interface TerminalViewProps {
|
|
sessionId: string;
|
|
adapter?: string;
|
|
style?: React.CSSProperties;
|
|
}
|
|
|
|
export interface TerminalViewHandle {
|
|
closeKeyboard(): void;
|
|
}
|
|
|
|
function buildTerminalWsUrl(sessionId: string, adapter?: string): string {
|
|
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
const token = localStorage.getItem(STORAGE.TOKEN) ?? '';
|
|
const adapterParam = adapter ? `&adapter=${encodeURIComponent(adapter)}` : '';
|
|
return `${proto}://${location.host}/terminal/${encodeURIComponent(sessionId)}?token=${encodeURIComponent(token)}${adapterParam}`;
|
|
}
|
|
|
|
const TERMINAL_CONFIG: TerminalConfig = {
|
|
fontSize: 14,
|
|
};
|
|
|
|
export const TerminalView = forwardRef<TerminalViewHandle, TerminalViewProps>(
|
|
function TerminalView({ sessionId, adapter, style }, ref) {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const termRef = useRef<WebTerminal | null>(null);
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
closeKeyboard() {
|
|
termRef.current?.closeKeyboard();
|
|
},
|
|
}));
|
|
|
|
useEffect(() => {
|
|
const el = containerRef.current;
|
|
if (!el) return;
|
|
|
|
const wsUrl = buildTerminalWsUrl(sessionId, adapter);
|
|
let cancelled = false;
|
|
|
|
WebTerminal.create(el, wsUrl, TERMINAL_CONFIG).then((t) => {
|
|
if (cancelled) {
|
|
t.dispose();
|
|
return;
|
|
}
|
|
termRef.current = t;
|
|
}).catch((err) => {
|
|
console.error('[TerminalView] Failed to create terminal:', err);
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
termRef.current?.dispose();
|
|
termRef.current = null;
|
|
};
|
|
}, [sessionId, adapter]);
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
style={{
|
|
width: '100%',
|
|
height: '100%',
|
|
backgroundColor: '#000',
|
|
overflow: 'hidden',
|
|
...style,
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
);
|