Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 74 additions & 8 deletions html/src/components/terminal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface Props extends XtermOptions {

interface State {
modal: boolean;
ctrlActive: boolean;
}

export class Terminal extends Component<Props, State> {
Expand All @@ -32,15 +33,37 @@ export class Terminal extends Component<Props, State> {
this.xterm.dispose();
}

render({ id }: Props, { modal }: State) {
render({ id }: Props, { modal, ctrlActive }: State) {
return (
<div id={id} ref={c => (this.container = c as HTMLElement)}>
<Modal show={modal}>
<label class="file-label">
<input onChange={this.sendFile} class="file-input" type="file" multiple />
<span class="file-cta">Choose files…</span>
</label>
</Modal>
<div id={id} class="terminal-wrapper">
<div class="toolbar">
<button class={`toolbar-btn ${ctrlActive ? 'active' : ''}`} onMouseDown={this.onCtrl}>
Ctrl
</button>
<button class="toolbar-btn" onMouseDown={this.onEsc}>
Esc
</button>
<button class="toolbar-btn" onMouseDown={this.onArrowUp}>
</button>
<button class="toolbar-btn" onMouseDown={this.onArrowDown}>
</button>
<button class="toolbar-btn" onMouseDown={this.onArrowLeft}>
</button>
<button class="toolbar-btn" onMouseDown={this.onArrowRight}>
</button>
</div>
<div class="terminal-main" ref={c => { this.container = c as HTMLElement; }}>
<Modal show={modal}>
<label class="file-label">
<input onChange={this.sendFile} class="file-input" type="file" multiple />
<span class="file-cta">Choose files…</span>
</label>
</Modal>
</div>
</div>
);
}
Expand All @@ -56,4 +79,47 @@ export class Terminal extends Component<Props, State> {
const files = (event.target as HTMLInputElement).files;
if (files) this.xterm.sendFile(files);
}

@bind
onEsc(event: Event) {
event.preventDefault();
this.xterm.sendEscape();
}

@bind
onCtrl(event: Event) {
event.preventDefault();
const { ctrlActive } = this.state;
if (ctrlActive) {
this.xterm.disableCtrlMode();
this.setState({ ctrlActive: false });
} else {
this.xterm.enableCtrlMode(() => this.setState({ ctrlActive: false }));
this.setState({ ctrlActive: true });
}
}

@bind
onArrowUp(event: Event) {
event.preventDefault();
this.xterm.sendArrowUp();
}

@bind
onArrowDown(event: Event) {
event.preventDefault();
this.xterm.sendArrowDown();
}

@bind
onArrowLeft(event: Event) {
event.preventDefault();
this.xterm.sendArrowLeft();
}

@bind
onArrowRight(event: Event) {
event.preventDefault();
this.xterm.sendArrowRight();
}
}
64 changes: 63 additions & 1 deletion html/src/components/terminal/xterm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class Xterm {
constructor(
private options: XtermOptions,
private sendCb: () => void
) {}
) { }

dispose() {
for (const d of this.disposables) {
Expand All @@ -127,6 +127,68 @@ export class Xterm {
this.zmodemAddon?.sendFile(files);
}

@bind
public sendEscape() {
this.sendData('\x1b');
this.terminal.focus();
}

@bind
public sendArrowUp() {
this.sendData('\x1b[A');
this.terminal.focus();
}

@bind
public sendArrowDown() {
this.sendData('\x1b[B');
this.terminal.focus();
}

@bind
public sendArrowRight() {
this.sendData('\x1b[C');
this.terminal.focus();
}

@bind
public sendArrowLeft() {
this.sendData('\x1b[D');
this.terminal.focus();
}

private ctrlModeListener?: (event: KeyboardEvent) => void;

@bind
public enableCtrlMode(callback: () => void) {
this.disableCtrlMode();
this.ctrlModeListener = (event: KeyboardEvent) => {
if (event.type !== 'keydown') return;
event.preventDefault();
event.stopPropagation();
this.terminal.textarea?.dispatchEvent(
new KeyboardEvent('keydown', {
key: event.key,
code: event.code,
ctrlKey: true,
bubbles: true,
cancelable: true,
})
);
this.disableCtrlMode();
callback();
};
document.addEventListener('keydown', this.ctrlModeListener, true);
}

@bind
public disableCtrlMode() {
if (this.ctrlModeListener) {
document.removeEventListener('keydown', this.ctrlModeListener, true);
this.ctrlModeListener = undefined;
}
}

@bind
public async refreshToken() {
try {
Expand Down
45 changes: 42 additions & 3 deletions html/src/style/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,47 @@ body {
height: 100%;
margin: 0 auto;
padding: 0;
.terminal {
padding: 5px;
height: calc(100% - 10px);
display: flex;
flex-direction: row;

.terminal-main {
flex: 1;
min-width: 0;
.terminal {
padding: 5px;
height: calc(100% - 10px);
}
}

.toolbar {
display: flex;
flex-direction: column;
gap: 4px;
padding: 4px;
background: #1e1e1e;
align-items: center;
}

.toolbar-btn {
width: 48px;
height: 48px;
border: 1px solid #555;
border-radius: 3px;
background: #2d2d2d;
color: #ccc;
font-size: 13px;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;

&:hover {
background: #3d3d3d;
}

&.active {
background: #0078d4;
border-color: #0078d4;
color: #fff;
}
}
}
Loading