lit-shell.js 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +304 -0
- package/dist/client/browser-bundle.js +314 -0
- package/dist/client/browser-bundle.js.map +7 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/terminal-client.d.ts +122 -0
- package/dist/client/terminal-client.d.ts.map +1 -0
- package/dist/client/terminal-client.js +328 -0
- package/dist/client/terminal-client.js.map +1 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +5 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/terminal-server.d.ts +109 -0
- package/dist/server/terminal-server.d.ts.map +1 -0
- package/dist/server/terminal-server.js +393 -0
- package/dist/server/terminal-server.js.map +1 -0
- package/dist/shared/types.d.ts +133 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +5 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/ui/browser-bundle.js +1678 -0
- package/dist/ui/browser-bundle.js.map +7 -0
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +6 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/lit-shell-terminal.d.ts +105 -0
- package/dist/ui/lit-shell-terminal.d.ts.map +1 -0
- package/dist/ui/lit-shell-terminal.js +570 -0
- package/dist/ui/lit-shell-terminal.js.map +1 -0
- package/dist/ui/styles.d.ts +16 -0
- package/dist/ui/styles.d.ts.map +1 -0
- package/dist/ui/styles.js +125 -0
- package/dist/ui/styles.js.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lit-shell-terminal web component
|
|
3
|
+
*
|
|
4
|
+
* A ready-to-use terminal component that wraps xterm.js and
|
|
5
|
+
* connects to a lit-shell server via WebSocket.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```html
|
|
9
|
+
* <lit-shell-terminal
|
|
10
|
+
* url="ws://localhost:3000/terminal"
|
|
11
|
+
* shell="/bin/bash"
|
|
12
|
+
* cwd="/home/user"
|
|
13
|
+
* theme="dark"
|
|
14
|
+
* ></lit-shell-terminal>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { LitElement } from 'lit';
|
|
18
|
+
import type { TerminalOptions } from '../shared/types.js';
|
|
19
|
+
export declare class LitShellTerminal extends LitElement {
|
|
20
|
+
static styles: import("lit").CSSResult[];
|
|
21
|
+
url: string;
|
|
22
|
+
shell: string;
|
|
23
|
+
cwd: string;
|
|
24
|
+
cols: number;
|
|
25
|
+
rows: number;
|
|
26
|
+
theme: 'dark' | 'light' | 'auto';
|
|
27
|
+
noHeader: boolean;
|
|
28
|
+
autoConnect: boolean;
|
|
29
|
+
autoSpawn: boolean;
|
|
30
|
+
fontSize: number;
|
|
31
|
+
fontFamily: string;
|
|
32
|
+
private client;
|
|
33
|
+
private terminal;
|
|
34
|
+
private fitAddon;
|
|
35
|
+
private connected;
|
|
36
|
+
private sessionActive;
|
|
37
|
+
private loading;
|
|
38
|
+
private error;
|
|
39
|
+
private sessionInfo;
|
|
40
|
+
private xtermModule;
|
|
41
|
+
private fitAddonModule;
|
|
42
|
+
private resizeObserver;
|
|
43
|
+
connectedCallback(): void;
|
|
44
|
+
disconnectedCallback(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Load xterm.js dynamically
|
|
47
|
+
*/
|
|
48
|
+
private loadXterm;
|
|
49
|
+
/**
|
|
50
|
+
* Load xterm.css into the shadow DOM
|
|
51
|
+
* This is necessary because CSS loaded in the main document doesn't apply inside shadow DOM
|
|
52
|
+
*/
|
|
53
|
+
private loadXtermStyles;
|
|
54
|
+
/**
|
|
55
|
+
* Connect to the terminal server
|
|
56
|
+
*/
|
|
57
|
+
connect(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Disconnect from the server
|
|
60
|
+
*/
|
|
61
|
+
disconnect(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Spawn a terminal session
|
|
64
|
+
*/
|
|
65
|
+
spawn(options?: TerminalOptions): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Initialize xterm.js UI
|
|
68
|
+
*/
|
|
69
|
+
private initTerminalUI;
|
|
70
|
+
/**
|
|
71
|
+
* Get terminal theme based on component theme
|
|
72
|
+
*/
|
|
73
|
+
private getTerminalTheme;
|
|
74
|
+
/**
|
|
75
|
+
* Kill the current session
|
|
76
|
+
*/
|
|
77
|
+
kill(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Clear the terminal
|
|
80
|
+
*/
|
|
81
|
+
clear(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Write data to the terminal (display only, not sent to server)
|
|
84
|
+
*/
|
|
85
|
+
write(data: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* Write line to the terminal (display only, not sent to server)
|
|
88
|
+
*/
|
|
89
|
+
writeln(data: string): void;
|
|
90
|
+
/**
|
|
91
|
+
* Focus the terminal
|
|
92
|
+
*/
|
|
93
|
+
focus(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Cleanup resources
|
|
96
|
+
*/
|
|
97
|
+
private cleanup;
|
|
98
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
99
|
+
}
|
|
100
|
+
declare global {
|
|
101
|
+
interface HTMLElementTagNameMap {
|
|
102
|
+
'lit-shell-terminal': LitShellTerminal;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=lit-shell-terminal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lit-shell-terminal.d.ts","sourceRoot":"","sources":["../../src/ui/lit-shell-terminal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAIrD,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,oBAAoB,CAAC;AAsBvE,qBACa,gBAAiB,SAAQ,UAAU;IAC9C,OAAgB,MAAM,4BAwGpB;IAG0B,GAAG,SAAM;IACT,KAAK,SAAM;IACX,GAAG,SAAM;IACT,IAAI,SAAM;IACV,IAAI,SAAM;IACK,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAU;IAChC,QAAQ,UAAS;IACd,WAAW,UAAS;IACtB,SAAS,UAAS;IAGpB,QAAQ,SAAM;IACZ,UAAU,SACpB;IAGnC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,WAAW,CAA4B;IAGxD,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,cAAc,CAA+B;IAE5C,iBAAiB;IAQjB,oBAAoB;IAK7B;;OAEG;YACW,SAAS;IAyBvB;;;OAGG;YACW,eAAe;IAyB7B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAuE9B;;OAEG;IACH,UAAU,IAAI,IAAI;IASlB;;OAEG;IACG,KAAK,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAoCrD;;OAEG;YACW,cAAc;IA4D5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;OAEG;IACH,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMzB;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM3B;;OAEG;IACM,KAAK,IAAI,IAAI;IAMtB;;OAEG;IACH,OAAO,CAAC,OAAO;IAmBN,MAAM;CA0ChB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,gBAAgB,CAAC;KACxC;CACF"}
|
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lit-shell-terminal web component
|
|
3
|
+
*
|
|
4
|
+
* A ready-to-use terminal component that wraps xterm.js and
|
|
5
|
+
* connects to a lit-shell server via WebSocket.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```html
|
|
9
|
+
* <lit-shell-terminal
|
|
10
|
+
* url="ws://localhost:3000/terminal"
|
|
11
|
+
* shell="/bin/bash"
|
|
12
|
+
* cwd="/home/user"
|
|
13
|
+
* theme="dark"
|
|
14
|
+
* ></lit-shell-terminal>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
18
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
19
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
20
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
21
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
22
|
+
};
|
|
23
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
24
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
25
|
+
import { sharedStyles, buttonStyles, themeStyles } from './styles.js';
|
|
26
|
+
import { TerminalClient } from '../client/terminal-client.js';
|
|
27
|
+
let LitShellTerminal = class LitShellTerminal extends LitElement {
|
|
28
|
+
constructor() {
|
|
29
|
+
super(...arguments);
|
|
30
|
+
// Connection properties
|
|
31
|
+
this.url = '';
|
|
32
|
+
this.shell = '';
|
|
33
|
+
this.cwd = '';
|
|
34
|
+
this.cols = 80;
|
|
35
|
+
this.rows = 24;
|
|
36
|
+
this.theme = 'dark';
|
|
37
|
+
this.noHeader = false;
|
|
38
|
+
this.autoConnect = false;
|
|
39
|
+
this.autoSpawn = false;
|
|
40
|
+
// Terminal appearance
|
|
41
|
+
this.fontSize = 14;
|
|
42
|
+
this.fontFamily = 'Menlo, Monaco, "Courier New", monospace';
|
|
43
|
+
// State
|
|
44
|
+
this.client = null;
|
|
45
|
+
this.terminal = null;
|
|
46
|
+
this.fitAddon = null;
|
|
47
|
+
this.connected = false;
|
|
48
|
+
this.sessionActive = false;
|
|
49
|
+
this.loading = false;
|
|
50
|
+
this.error = null;
|
|
51
|
+
this.sessionInfo = null;
|
|
52
|
+
// xterm.js module (loaded dynamically)
|
|
53
|
+
this.xtermModule = null;
|
|
54
|
+
this.fitAddonModule = null;
|
|
55
|
+
this.resizeObserver = null;
|
|
56
|
+
}
|
|
57
|
+
connectedCallback() {
|
|
58
|
+
super.connectedCallback();
|
|
59
|
+
if (this.autoConnect && this.url) {
|
|
60
|
+
this.connect();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
disconnectedCallback() {
|
|
64
|
+
super.disconnectedCallback();
|
|
65
|
+
this.cleanup();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Load xterm.js dynamically
|
|
69
|
+
*/
|
|
70
|
+
async loadXterm() {
|
|
71
|
+
if (this.xtermModule)
|
|
72
|
+
return;
|
|
73
|
+
try {
|
|
74
|
+
// Try to import from CDN
|
|
75
|
+
// @ts-ignore - Dynamic import from CDN
|
|
76
|
+
this.xtermModule = await import('https://cdn.jsdelivr.net/npm/xterm@5.3.0/+esm');
|
|
77
|
+
// @ts-ignore - Dynamic import from CDN
|
|
78
|
+
this.fitAddonModule = await import('https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/+esm');
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// Fallback to npm package if available
|
|
82
|
+
try {
|
|
83
|
+
// @ts-ignore - Optional peer dependency
|
|
84
|
+
this.xtermModule = await import('xterm');
|
|
85
|
+
// @ts-ignore - Optional peer dependency
|
|
86
|
+
this.fitAddonModule = await import('xterm-addon-fit');
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
throw new Error('Failed to load xterm.js. Make sure it is available.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Load xterm.css into shadow DOM (CSS doesn't penetrate shadow boundaries)
|
|
93
|
+
await this.loadXtermStyles();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Load xterm.css into the shadow DOM
|
|
97
|
+
* This is necessary because CSS loaded in the main document doesn't apply inside shadow DOM
|
|
98
|
+
*/
|
|
99
|
+
async loadXtermStyles() {
|
|
100
|
+
if (!this.shadowRoot)
|
|
101
|
+
return;
|
|
102
|
+
// Check if styles are already loaded
|
|
103
|
+
if (this.shadowRoot.querySelector('#xterm-styles'))
|
|
104
|
+
return;
|
|
105
|
+
try {
|
|
106
|
+
// Fetch xterm.css from CDN
|
|
107
|
+
const response = await fetch('https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css');
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`Failed to fetch xterm.css: ${response.status}`);
|
|
110
|
+
}
|
|
111
|
+
const cssText = await response.text();
|
|
112
|
+
// Create style element and inject into shadow DOM
|
|
113
|
+
const style = document.createElement('style');
|
|
114
|
+
style.id = 'xterm-styles';
|
|
115
|
+
style.textContent = cssText;
|
|
116
|
+
this.shadowRoot.appendChild(style);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.warn('[lit-shell] Failed to load xterm.css:', error);
|
|
120
|
+
// Terminal will still work but may have visual issues
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Connect to the terminal server
|
|
125
|
+
*/
|
|
126
|
+
async connect() {
|
|
127
|
+
if (!this.url) {
|
|
128
|
+
this.error = 'No URL specified';
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.loading = true;
|
|
132
|
+
this.error = null;
|
|
133
|
+
try {
|
|
134
|
+
// Load xterm.js
|
|
135
|
+
await this.loadXterm();
|
|
136
|
+
// Create client
|
|
137
|
+
this.client = new TerminalClient({ url: this.url });
|
|
138
|
+
this.client.onConnect(() => {
|
|
139
|
+
this.connected = true;
|
|
140
|
+
this.dispatchEvent(new CustomEvent('connect', { bubbles: true, composed: true }));
|
|
141
|
+
if (this.autoSpawn) {
|
|
142
|
+
this.spawn();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
this.client.onDisconnect(() => {
|
|
146
|
+
this.connected = false;
|
|
147
|
+
this.sessionActive = false;
|
|
148
|
+
this.dispatchEvent(new CustomEvent('disconnect', { bubbles: true, composed: true }));
|
|
149
|
+
});
|
|
150
|
+
this.client.onError((err) => {
|
|
151
|
+
this.error = err.message;
|
|
152
|
+
this.dispatchEvent(new CustomEvent('error', { detail: { error: err }, bubbles: true, composed: true }));
|
|
153
|
+
});
|
|
154
|
+
this.client.onData((data) => {
|
|
155
|
+
if (this.terminal) {
|
|
156
|
+
this.terminal.write(data);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
this.client.onExit((code) => {
|
|
160
|
+
this.sessionActive = false;
|
|
161
|
+
this.sessionInfo = null;
|
|
162
|
+
if (this.terminal) {
|
|
163
|
+
this.terminal.writeln('');
|
|
164
|
+
this.terminal.writeln(`\x1b[1;33m[Process exited with code: ${code}]\x1b[0m`);
|
|
165
|
+
}
|
|
166
|
+
this.dispatchEvent(new CustomEvent('exit', { detail: { exitCode: code }, bubbles: true, composed: true }));
|
|
167
|
+
});
|
|
168
|
+
this.client.onSpawned((info) => {
|
|
169
|
+
this.sessionInfo = info;
|
|
170
|
+
this.dispatchEvent(new CustomEvent('spawned', { detail: { session: info }, bubbles: true, composed: true }));
|
|
171
|
+
});
|
|
172
|
+
await this.client.connect();
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
this.error = err instanceof Error ? err.message : 'Connection failed';
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
this.loading = false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Disconnect from the server
|
|
183
|
+
*/
|
|
184
|
+
disconnect() {
|
|
185
|
+
if (this.client) {
|
|
186
|
+
this.client.disconnect();
|
|
187
|
+
this.client = null;
|
|
188
|
+
}
|
|
189
|
+
this.connected = false;
|
|
190
|
+
this.sessionActive = false;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Spawn a terminal session
|
|
194
|
+
*/
|
|
195
|
+
async spawn(options) {
|
|
196
|
+
if (!this.client || !this.connected) {
|
|
197
|
+
throw new Error('Not connected to server');
|
|
198
|
+
}
|
|
199
|
+
this.loading = true;
|
|
200
|
+
this.error = null;
|
|
201
|
+
try {
|
|
202
|
+
// Initialize terminal UI if needed
|
|
203
|
+
await this.initTerminalUI();
|
|
204
|
+
// Spawn session
|
|
205
|
+
const spawnOptions = {
|
|
206
|
+
shell: options?.shell || this.shell || undefined,
|
|
207
|
+
cwd: options?.cwd || this.cwd || undefined,
|
|
208
|
+
cols: this.terminal?.cols || this.cols,
|
|
209
|
+
rows: this.terminal?.rows || this.rows,
|
|
210
|
+
env: options?.env,
|
|
211
|
+
};
|
|
212
|
+
const info = await this.client.spawn(spawnOptions);
|
|
213
|
+
this.sessionActive = true;
|
|
214
|
+
this.sessionInfo = info;
|
|
215
|
+
// Focus terminal
|
|
216
|
+
if (this.terminal) {
|
|
217
|
+
this.terminal.focus();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
this.error = err instanceof Error ? err.message : 'Failed to spawn session';
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
this.loading = false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Initialize xterm.js UI
|
|
229
|
+
*/
|
|
230
|
+
async initTerminalUI() {
|
|
231
|
+
if (this.terminal)
|
|
232
|
+
return;
|
|
233
|
+
await this.loadXterm();
|
|
234
|
+
await this.updateComplete;
|
|
235
|
+
const container = this.shadowRoot?.querySelector('.terminal-container');
|
|
236
|
+
if (!container)
|
|
237
|
+
return;
|
|
238
|
+
// Get theme colors
|
|
239
|
+
const terminalTheme = this.getTerminalTheme();
|
|
240
|
+
// Create terminal
|
|
241
|
+
const Terminal = this.xtermModule.Terminal;
|
|
242
|
+
const term = new Terminal({
|
|
243
|
+
cursorBlink: true,
|
|
244
|
+
fontSize: this.fontSize,
|
|
245
|
+
fontFamily: this.fontFamily,
|
|
246
|
+
theme: terminalTheme,
|
|
247
|
+
cols: this.cols,
|
|
248
|
+
rows: this.rows,
|
|
249
|
+
});
|
|
250
|
+
// Create fit addon
|
|
251
|
+
const FitAddon = this.fitAddonModule.FitAddon;
|
|
252
|
+
const fit = new FitAddon();
|
|
253
|
+
// Store references
|
|
254
|
+
this.terminal = term;
|
|
255
|
+
this.fitAddon = fit;
|
|
256
|
+
term.loadAddon(fit);
|
|
257
|
+
// Open terminal
|
|
258
|
+
term.open(container);
|
|
259
|
+
fit.fit();
|
|
260
|
+
// Handle user input
|
|
261
|
+
term.onData((data) => {
|
|
262
|
+
if (this.client && this.sessionActive) {
|
|
263
|
+
this.client.write(data);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
// Handle resize
|
|
267
|
+
term.onResize(({ cols, rows }) => {
|
|
268
|
+
if (this.client && this.sessionActive) {
|
|
269
|
+
this.client.resize(cols, rows);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
// Setup resize observer
|
|
273
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
274
|
+
if (this.fitAddon) {
|
|
275
|
+
this.fitAddon.fit();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
this.resizeObserver.observe(container);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get terminal theme based on component theme
|
|
282
|
+
*/
|
|
283
|
+
getTerminalTheme() {
|
|
284
|
+
// These will be overridden by CSS variables in the actual implementation
|
|
285
|
+
// For now, provide sensible defaults based on theme attribute
|
|
286
|
+
if (this.theme === 'light') {
|
|
287
|
+
return {
|
|
288
|
+
background: '#ffffff',
|
|
289
|
+
foreground: '#1f2937',
|
|
290
|
+
cursor: '#1f2937',
|
|
291
|
+
selection: '#b4d5fe',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
background: '#1e1e1e',
|
|
296
|
+
foreground: '#cccccc',
|
|
297
|
+
cursor: '#ffffff',
|
|
298
|
+
selection: '#264f78',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Kill the current session
|
|
303
|
+
*/
|
|
304
|
+
kill() {
|
|
305
|
+
if (this.client) {
|
|
306
|
+
this.client.kill();
|
|
307
|
+
}
|
|
308
|
+
this.sessionActive = false;
|
|
309
|
+
this.sessionInfo = null;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Clear the terminal
|
|
313
|
+
*/
|
|
314
|
+
clear() {
|
|
315
|
+
if (this.terminal) {
|
|
316
|
+
this.terminal.clear();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Write data to the terminal (display only, not sent to server)
|
|
321
|
+
*/
|
|
322
|
+
write(data) {
|
|
323
|
+
if (this.terminal) {
|
|
324
|
+
this.terminal.write(data);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Write line to the terminal (display only, not sent to server)
|
|
329
|
+
*/
|
|
330
|
+
writeln(data) {
|
|
331
|
+
if (this.terminal) {
|
|
332
|
+
this.terminal.writeln(data);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Focus the terminal
|
|
337
|
+
*/
|
|
338
|
+
focus() {
|
|
339
|
+
if (this.terminal) {
|
|
340
|
+
this.terminal.focus();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Cleanup resources
|
|
345
|
+
*/
|
|
346
|
+
cleanup() {
|
|
347
|
+
if (this.resizeObserver) {
|
|
348
|
+
this.resizeObserver.disconnect();
|
|
349
|
+
this.resizeObserver = null;
|
|
350
|
+
}
|
|
351
|
+
if (this.terminal) {
|
|
352
|
+
this.terminal.dispose();
|
|
353
|
+
this.terminal = null;
|
|
354
|
+
}
|
|
355
|
+
if (this.client) {
|
|
356
|
+
this.client.disconnect();
|
|
357
|
+
this.client = null;
|
|
358
|
+
}
|
|
359
|
+
this.fitAddon = null;
|
|
360
|
+
}
|
|
361
|
+
render() {
|
|
362
|
+
return html `
|
|
363
|
+
${this.noHeader
|
|
364
|
+
? nothing
|
|
365
|
+
: html `
|
|
366
|
+
<div class="header">
|
|
367
|
+
<div class="header-title">
|
|
368
|
+
<span>Terminal</span>
|
|
369
|
+
${this.sessionInfo
|
|
370
|
+
? html `<span style="font-weight: normal; font-size: 12px; color: var(--ls-text-muted)">
|
|
371
|
+
${this.sessionInfo.shell}
|
|
372
|
+
</span>`
|
|
373
|
+
: nothing}
|
|
374
|
+
</div>
|
|
375
|
+
<div class="header-actions">
|
|
376
|
+
${!this.connected
|
|
377
|
+
? html `<button @click=${this.connect} ?disabled=${this.loading}>
|
|
378
|
+
${this.loading ? 'Connecting...' : 'Connect'}
|
|
379
|
+
</button>`
|
|
380
|
+
: !this.sessionActive
|
|
381
|
+
? html `<button @click=${() => this.spawn()} ?disabled=${this.loading}>
|
|
382
|
+
${this.loading ? 'Spawning...' : 'Start'}
|
|
383
|
+
</button>`
|
|
384
|
+
: html `<button @click=${this.kill}>Stop</button>`}
|
|
385
|
+
<button @click=${this.clear} ?disabled=${!this.sessionActive}>Clear</button>
|
|
386
|
+
<div class="status">
|
|
387
|
+
<span class="status-dot ${this.connected ? 'connected' : ''}"></span>
|
|
388
|
+
<span>${this.connected ? 'Connected' : 'Disconnected'}</span>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
`}
|
|
393
|
+
|
|
394
|
+
<div class="terminal-container">
|
|
395
|
+
${this.loading && !this.terminal
|
|
396
|
+
? html `<div class="loading"><span class="loading-spinner">⏳</span> Loading...</div>`
|
|
397
|
+
: this.error && !this.terminal
|
|
398
|
+
? html `<div class="error">❌ ${this.error}</div>`
|
|
399
|
+
: nothing}
|
|
400
|
+
</div>
|
|
401
|
+
`;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
LitShellTerminal.styles = [
|
|
405
|
+
sharedStyles,
|
|
406
|
+
themeStyles,
|
|
407
|
+
buttonStyles,
|
|
408
|
+
css `
|
|
409
|
+
:host {
|
|
410
|
+
display: flex;
|
|
411
|
+
flex-direction: column;
|
|
412
|
+
height: 100%;
|
|
413
|
+
min-height: 200px;
|
|
414
|
+
border: 1px solid var(--ls-border);
|
|
415
|
+
border-radius: 4px;
|
|
416
|
+
overflow: hidden;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.header {
|
|
420
|
+
display: flex;
|
|
421
|
+
align-items: center;
|
|
422
|
+
justify-content: space-between;
|
|
423
|
+
padding: 8px 12px;
|
|
424
|
+
background: var(--ls-bg-header);
|
|
425
|
+
border-bottom: 1px solid var(--ls-border);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.header-title {
|
|
429
|
+
display: flex;
|
|
430
|
+
align-items: center;
|
|
431
|
+
gap: 8px;
|
|
432
|
+
font-weight: 600;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.header-actions {
|
|
436
|
+
display: flex;
|
|
437
|
+
gap: 8px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.status {
|
|
441
|
+
display: flex;
|
|
442
|
+
align-items: center;
|
|
443
|
+
gap: 6px;
|
|
444
|
+
font-size: 12px;
|
|
445
|
+
color: var(--ls-text-muted);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.status-dot {
|
|
449
|
+
width: 8px;
|
|
450
|
+
height: 8px;
|
|
451
|
+
border-radius: 50%;
|
|
452
|
+
background: var(--ls-status-disconnected);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.status-dot.connected {
|
|
456
|
+
background: var(--ls-status-connected);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.terminal-container {
|
|
460
|
+
flex: 1;
|
|
461
|
+
padding: 4px;
|
|
462
|
+
background: var(--ls-terminal-bg);
|
|
463
|
+
overflow: hidden;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.terminal-container .xterm {
|
|
467
|
+
height: 100%;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.terminal-container .xterm-viewport {
|
|
471
|
+
overflow-y: auto;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.loading,
|
|
475
|
+
.error {
|
|
476
|
+
display: flex;
|
|
477
|
+
align-items: center;
|
|
478
|
+
justify-content: center;
|
|
479
|
+
height: 100%;
|
|
480
|
+
padding: 20px;
|
|
481
|
+
text-align: center;
|
|
482
|
+
color: var(--ls-text-muted);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.error {
|
|
486
|
+
color: #ef4444;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.loading-spinner {
|
|
490
|
+
animation: spin 1s linear infinite;
|
|
491
|
+
margin-right: 8px;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
@keyframes spin {
|
|
495
|
+
from {
|
|
496
|
+
transform: rotate(0deg);
|
|
497
|
+
}
|
|
498
|
+
to {
|
|
499
|
+
transform: rotate(360deg);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/* Hide header if requested */
|
|
504
|
+
:host([no-header]) .header {
|
|
505
|
+
display: none;
|
|
506
|
+
}
|
|
507
|
+
`,
|
|
508
|
+
];
|
|
509
|
+
__decorate([
|
|
510
|
+
property({ type: String })
|
|
511
|
+
], LitShellTerminal.prototype, "url", void 0);
|
|
512
|
+
__decorate([
|
|
513
|
+
property({ type: String })
|
|
514
|
+
], LitShellTerminal.prototype, "shell", void 0);
|
|
515
|
+
__decorate([
|
|
516
|
+
property({ type: String })
|
|
517
|
+
], LitShellTerminal.prototype, "cwd", void 0);
|
|
518
|
+
__decorate([
|
|
519
|
+
property({ type: Number })
|
|
520
|
+
], LitShellTerminal.prototype, "cols", void 0);
|
|
521
|
+
__decorate([
|
|
522
|
+
property({ type: Number })
|
|
523
|
+
], LitShellTerminal.prototype, "rows", void 0);
|
|
524
|
+
__decorate([
|
|
525
|
+
property({ type: String, reflect: true })
|
|
526
|
+
], LitShellTerminal.prototype, "theme", void 0);
|
|
527
|
+
__decorate([
|
|
528
|
+
property({ type: Boolean, attribute: 'no-header' })
|
|
529
|
+
], LitShellTerminal.prototype, "noHeader", void 0);
|
|
530
|
+
__decorate([
|
|
531
|
+
property({ type: Boolean, attribute: 'auto-connect' })
|
|
532
|
+
], LitShellTerminal.prototype, "autoConnect", void 0);
|
|
533
|
+
__decorate([
|
|
534
|
+
property({ type: Boolean, attribute: 'auto-spawn' })
|
|
535
|
+
], LitShellTerminal.prototype, "autoSpawn", void 0);
|
|
536
|
+
__decorate([
|
|
537
|
+
property({ type: Number, attribute: 'font-size' })
|
|
538
|
+
], LitShellTerminal.prototype, "fontSize", void 0);
|
|
539
|
+
__decorate([
|
|
540
|
+
property({ type: String, attribute: 'font-family' })
|
|
541
|
+
], LitShellTerminal.prototype, "fontFamily", void 0);
|
|
542
|
+
__decorate([
|
|
543
|
+
state()
|
|
544
|
+
], LitShellTerminal.prototype, "client", void 0);
|
|
545
|
+
__decorate([
|
|
546
|
+
state()
|
|
547
|
+
], LitShellTerminal.prototype, "terminal", void 0);
|
|
548
|
+
__decorate([
|
|
549
|
+
state()
|
|
550
|
+
], LitShellTerminal.prototype, "fitAddon", void 0);
|
|
551
|
+
__decorate([
|
|
552
|
+
state()
|
|
553
|
+
], LitShellTerminal.prototype, "connected", void 0);
|
|
554
|
+
__decorate([
|
|
555
|
+
state()
|
|
556
|
+
], LitShellTerminal.prototype, "sessionActive", void 0);
|
|
557
|
+
__decorate([
|
|
558
|
+
state()
|
|
559
|
+
], LitShellTerminal.prototype, "loading", void 0);
|
|
560
|
+
__decorate([
|
|
561
|
+
state()
|
|
562
|
+
], LitShellTerminal.prototype, "error", void 0);
|
|
563
|
+
__decorate([
|
|
564
|
+
state()
|
|
565
|
+
], LitShellTerminal.prototype, "sessionInfo", void 0);
|
|
566
|
+
LitShellTerminal = __decorate([
|
|
567
|
+
customElement('lit-shell-terminal')
|
|
568
|
+
], LitShellTerminal);
|
|
569
|
+
export { LitShellTerminal };
|
|
570
|
+
//# sourceMappingURL=lit-shell-terminal.js.map
|