@xxmachina/components 19.51.0 → 19.81.0
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/fesm2022/xxmachina-components-extras-flow.mjs +73 -140
- package/fesm2022/xxmachina-components-extras-flow.mjs.map +1 -1
- package/fesm2022/xxmachina-components-features-query.mjs +3 -29
- package/fesm2022/xxmachina-components-features-query.mjs.map +1 -1
- package/fesm2022/xxmachina-components-groups-query-form.mjs +0 -54
- package/fesm2022/xxmachina-components-groups-query-form.mjs.map +1 -1
- package/fesm2022/xxmachina-components-molecules-inline-edit-field.mjs +0 -1
- package/fesm2022/xxmachina-components-molecules-inline-edit-field.mjs.map +1 -1
- package/fesm2022/xxmachina-components-molecules-user-avatar-menu.mjs +96 -0
- package/fesm2022/xxmachina-components-molecules-user-avatar-menu.mjs.map +1 -0
- package/fesm2022/xxmachina-components-molecules-weekly-header.mjs +2 -2
- package/fesm2022/xxmachina-components-molecules-workspace-selector.mjs +123 -0
- package/fesm2022/xxmachina-components-molecules-workspace-selector.mjs.map +1 -0
- package/fesm2022/xxmachina-components-organisms-calendar-section.mjs +2 -2
- package/fesm2022/xxmachina-components-organisms-calendar-section.mjs.map +1 -1
- package/fesm2022/xxmachina-components-organisms-event-input-section.mjs +0 -1
- package/fesm2022/xxmachina-components-organisms-event-input-section.mjs.map +1 -1
- package/fesm2022/xxmachina-components-organisms-terminal-input-section.mjs +2 -18
- package/fesm2022/xxmachina-components-organisms-terminal-input-section.mjs.map +1 -1
- package/fesm2022/xxmachina-components-organisms-video-trimmer-section.mjs +0 -3
- package/fesm2022/xxmachina-components-organisms-video-trimmer-section.mjs.map +1 -1
- package/fesm2022/xxmachina-components-organisms-xterm.mjs +283 -261
- package/fesm2022/xxmachina-components-organisms-xterm.mjs.map +1 -1
- package/fesm2022/xxmachina-components-pages-command.mjs +3 -9
- package/fesm2022/xxmachina-components-pages-command.mjs.map +1 -1
- package/fesm2022/xxmachina-components-pages-query-v2.mjs +0 -13
- package/fesm2022/xxmachina-components-pages-query-v2.mjs.map +1 -1
- package/fesm2022/xxmachina-components-pages-query.mjs +3 -67
- package/fesm2022/xxmachina-components-pages-query.mjs.map +1 -1
- package/fesm2022/xxmachina-components-pages-thread.mjs +2 -18
- package/fesm2022/xxmachina-components-pages-thread.mjs.map +1 -1
- package/fesm2022/{xxmachina-components-routes-commands-command.routes-CSwIdXZg.mjs → xxmachina-components-routes-commands-command.routes-Ca2yVrwL.mjs} +1 -2
- package/fesm2022/xxmachina-components-routes-commands-command.routes-Ca2yVrwL.mjs.map +1 -0
- package/fesm2022/{xxmachina-components-routes-commands-pages.routes-j2xVop4W.mjs → xxmachina-components-routes-commands-pages.routes-COpFeEsz.mjs} +2 -2
- package/fesm2022/{xxmachina-components-routes-commands-pages.routes-j2xVop4W.mjs.map → xxmachina-components-routes-commands-pages.routes-COpFeEsz.mjs.map} +1 -1
- package/fesm2022/xxmachina-components-routes-commands.mjs +1 -2
- package/fesm2022/xxmachina-components-routes-commands.mjs.map +1 -1
- package/fesm2022/xxmachina-components-routes-pages.mjs +0 -1
- package/fesm2022/xxmachina-components-routes-pages.mjs.map +1 -1
- package/fesm2022/xxmachina-components-routes-queries.mjs +0 -2
- package/fesm2022/xxmachina-components-routes-queries.mjs.map +1 -1
- package/fesm2022/{xxmachina-components-routes-query-pages.routes-CxTbOJDQ.mjs → xxmachina-components-routes-query-pages.routes-BNuONo3g.mjs} +1 -5
- package/fesm2022/xxmachina-components-routes-query-pages.routes-BNuONo3g.mjs.map +1 -0
- package/fesm2022/xxmachina-components-routes-query.mjs +1 -1
- package/fesm2022/{xxmachina-components-routes-resources-pages.routes-CRXxavHb.mjs → xxmachina-components-routes-resources-pages.routes-DEu2qpeu.mjs} +2 -2
- package/fesm2022/{xxmachina-components-routes-resources-pages.routes-CRXxavHb.mjs.map → xxmachina-components-routes-resources-pages.routes-DEu2qpeu.mjs.map} +1 -1
- package/fesm2022/{xxmachina-components-routes-resources-resource.routes-CkkmqB2_.mjs → xxmachina-components-routes-resources-resource.routes-CWxi2agj.mjs} +1 -2
- package/fesm2022/xxmachina-components-routes-resources-resource.routes-CWxi2agj.mjs.map +1 -0
- package/fesm2022/xxmachina-components-routes-resources.mjs +1 -1
- package/fesm2022/xxmachina-components-routes-transformer.mjs +0 -1
- package/fesm2022/xxmachina-components-routes-transformer.mjs.map +1 -1
- package/fesm2022/xxmachina-components-services-forms-query.mjs +1 -1
- package/fesm2022/xxmachina-components-services-forms-query.mjs.map +1 -1
- package/fesm2022/xxmachina-components-services-graphql.mjs +0 -4
- package/fesm2022/xxmachina-components-services-graphql.mjs.map +1 -1
- package/fesm2022/xxmachina-components-services-json-schema.mjs +6 -12
- package/fesm2022/xxmachina-components-services-json-schema.mjs.map +1 -1
- package/fesm2022/xxmachina-components-services-query.mjs +0 -7
- package/fesm2022/xxmachina-components-services-query.mjs.map +1 -1
- package/fesm2022/xxmachina-components-services-recording.mjs +0 -13
- package/fesm2022/xxmachina-components-services-recording.mjs.map +1 -1
- package/fesm2022/xxmachina-components-services-thread.mjs +0 -2
- package/fesm2022/xxmachina-components-services-thread.mjs.map +1 -1
- package/fesm2022/xxmachina-components-services-version.mjs +171 -0
- package/fesm2022/xxmachina-components-services-version.mjs.map +1 -0
- package/fesm2022/xxmachina-components-services-workspace-switcher.mjs +9 -0
- package/fesm2022/xxmachina-components-services-workspace-switcher.mjs.map +1 -0
- package/fesm2022/xxmachina-components-templates-agent.mjs +98 -146
- package/fesm2022/xxmachina-components-templates-agent.mjs.map +1 -1
- package/fesm2022/xxmachina-components-templates-background.mjs +10 -30
- package/fesm2022/xxmachina-components-templates-background.mjs.map +1 -1
- package/fesm2022/xxmachina-components-templates-env.mjs +295 -0
- package/fesm2022/xxmachina-components-templates-env.mjs.map +1 -0
- package/fesm2022/xxmachina-components-templates-flow-nodes-issue.mjs.map +1 -1
- package/fesm2022/xxmachina-components-templates-flow.mjs +1 -1
- package/fesm2022/xxmachina-components-templates-flow.mjs.map +1 -1
- package/fesm2022/xxmachina-components-templates-machina-icon-button-menu.mjs +78 -0
- package/fesm2022/xxmachina-components-templates-machina-icon-button-menu.mjs.map +1 -0
- package/fesm2022/xxmachina-components.mjs +9 -10
- package/fesm2022/xxmachina-components.mjs.map +1 -1
- package/package.json +25 -1
- package/types/xxmachina-components-extras-flow.d.ts +8 -43
- package/types/xxmachina-components-extras-flow.d.ts.map +1 -1
- package/types/xxmachina-components-features-query.d.ts +3 -24
- package/types/xxmachina-components-features-query.d.ts.map +1 -1
- package/types/xxmachina-components-groups-query-form.d.ts +2 -2
- package/types/xxmachina-components-molecules-user-avatar-menu.d.ts +19 -0
- package/types/xxmachina-components-molecules-user-avatar-menu.d.ts.map +1 -0
- package/types/xxmachina-components-molecules-workspace-selector.d.ts +25 -0
- package/types/xxmachina-components-molecules-workspace-selector.d.ts.map +1 -0
- package/types/xxmachina-components-organisms-xterm.d.ts +130 -111
- package/types/xxmachina-components-organisms-xterm.d.ts.map +1 -1
- package/types/xxmachina-components-pages-query-v2.d.ts +0 -3
- package/types/xxmachina-components-pages-query-v2.d.ts.map +1 -1
- package/types/xxmachina-components-pages-query.d.ts +0 -19
- package/types/xxmachina-components-pages-query.d.ts.map +1 -1
- package/types/xxmachina-components-services-forms-query.d.ts +1 -1
- package/types/xxmachina-components-services-graphql.d.ts +0 -3
- package/types/xxmachina-components-services-graphql.d.ts.map +1 -1
- package/types/xxmachina-components-services-query.d.ts +0 -7
- package/types/xxmachina-components-services-query.d.ts.map +1 -1
- package/types/xxmachina-components-services-version.d.ts +32 -0
- package/types/xxmachina-components-services-version.d.ts.map +1 -0
- package/types/xxmachina-components-services-workspace-switcher.d.ts +17 -0
- package/types/xxmachina-components-services-workspace-switcher.d.ts.map +1 -0
- package/types/xxmachina-components-templates-agent.d.ts +2 -1
- package/types/xxmachina-components-templates-agent.d.ts.map +1 -1
- package/types/xxmachina-components-templates-env.d.ts +43 -0
- package/types/xxmachina-components-templates-env.d.ts.map +1 -0
- package/types/xxmachina-components-templates-flow-nodes-issue.d.ts +0 -2
- package/types/xxmachina-components-templates-flow-nodes-issue.d.ts.map +1 -1
- package/types/xxmachina-components-templates-machina-icon-button-menu.d.ts +18 -0
- package/types/xxmachina-components-templates-machina-icon-button-menu.d.ts.map +1 -0
- package/types/xxmachina-components.d.ts +2 -0
- package/types/xxmachina-components.d.ts.map +1 -1
- package/fesm2022/xxmachina-components-routes-commands-command.routes-CSwIdXZg.mjs.map +0 -1
- package/fesm2022/xxmachina-components-routes-query-pages.routes-CxTbOJDQ.mjs.map +0 -1
- package/fesm2022/xxmachina-components-routes-resources-resource.routes-CkkmqB2_.mjs.map +0 -1
|
@@ -8,10 +8,6 @@ import { makeDI } from '@ng-atomic/common/services/ui';
|
|
|
8
8
|
const URL_REGEX = /https?:\/\/[^\s<>'")\[\]]+/g;
|
|
9
9
|
const LOCALHOST_REGEX = /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?(?:\/|$)/;
|
|
10
10
|
const MAX_SCAN_LINES = 30;
|
|
11
|
-
/**
|
|
12
|
-
* Link provider that detects URLs spanning multiple terminal lines.
|
|
13
|
-
* localhost/127.0.0.1 URLs are skipped (handled by ProxyLinksAddon).
|
|
14
|
-
*/
|
|
15
11
|
class WebLinkProvider {
|
|
16
12
|
_terminal;
|
|
17
13
|
_handler;
|
|
@@ -94,11 +90,6 @@ class WebLinkProvider {
|
|
|
94
90
|
}
|
|
95
91
|
}
|
|
96
92
|
|
|
97
|
-
/**
|
|
98
|
-
* Drop-in replacement for @xterm/addon-web-links that supports
|
|
99
|
-
* URLs spanning wrapped terminal lines.
|
|
100
|
-
* localhost/127.0.0.1 URLs are skipped (handled by ProxyLinksAddon).
|
|
101
|
-
*/
|
|
102
93
|
class WebLinksAddon {
|
|
103
94
|
_handler;
|
|
104
95
|
_linkProvider;
|
|
@@ -116,7 +107,6 @@ class WebLinksAddon {
|
|
|
116
107
|
class GitHubLinkProvider {
|
|
117
108
|
_terminal;
|
|
118
109
|
_handler;
|
|
119
|
-
// #番号 形式を検出(負の先読みで数字以外が続く場合にマッチ)
|
|
120
110
|
GITHUB_ISSUE_REGEX = /(?:^|[\s(])#(\d+)(?!\d)/g;
|
|
121
111
|
constructor(_terminal, _handler) {
|
|
122
112
|
this._terminal = _terminal;
|
|
@@ -137,7 +127,6 @@ class GitHubLinkProvider {
|
|
|
137
127
|
const issueNumber = parseInt(match[1], 10);
|
|
138
128
|
const fullMatch = match[0];
|
|
139
129
|
const issueRef = `#${match[1]}`;
|
|
140
|
-
// マッチ開始位置を計算(プレフィックス文字を除く)
|
|
141
130
|
const stringStartIndex = match.index + (fullMatch.length - issueRef.length);
|
|
142
131
|
const [, startX] = this._mapStrIdx(lineIndex, 0, stringStartIndex);
|
|
143
132
|
const [, endX] = this._mapStrIdx(lineIndex, startX, issueRef.length);
|
|
@@ -201,17 +190,12 @@ class GitHubLinksAddon {
|
|
|
201
190
|
class FileLinkProvider {
|
|
202
191
|
_terminal;
|
|
203
192
|
_handler;
|
|
204
|
-
// ファイルパスを検出する正規表現
|
|
205
|
-
// - 絶対パス: /path/to/file.ts, /path/to/file.ts:10:5
|
|
206
|
-
// - 相対パス: ./src/file.ts, ../lib/utils.ts:20
|
|
207
|
-
// - 拡張子を持つファイル名
|
|
208
193
|
FILE_PATH_REGEX = /(?:^|[\s'"`(])((\.\.?\/|\/)?[\w.@-]+(?:\/[\w.@-]+)*\.[a-zA-Z0-9]+(?::\d+(?::\d+)?)?)/g;
|
|
209
194
|
constructor(_terminal, _handler) {
|
|
210
195
|
this._terminal = _terminal;
|
|
211
196
|
this._handler = _handler;
|
|
212
197
|
}
|
|
213
198
|
provideLinks(bufferLineNumber, callback) {
|
|
214
|
-
// bufferLineNumber は 1-indexed、buffer.getLine() は 0-indexed を期待
|
|
215
199
|
const lineIndex = bufferLineNumber - 1;
|
|
216
200
|
const line = this._terminal.buffer.active.getLine(lineIndex);
|
|
217
201
|
if (!line) {
|
|
@@ -220,15 +204,12 @@ class FileLinkProvider {
|
|
|
220
204
|
}
|
|
221
205
|
const text = line.translateToString(true);
|
|
222
206
|
const links = [];
|
|
223
|
-
// Reset regex lastIndex
|
|
224
207
|
this.FILE_PATH_REGEX.lastIndex = 0;
|
|
225
208
|
let match;
|
|
226
209
|
while ((match = this.FILE_PATH_REGEX.exec(text)) !== null) {
|
|
227
210
|
const filePath = match[1];
|
|
228
211
|
if (this._isLikelyFilePath(filePath) && !this._isUrl(filePath)) {
|
|
229
|
-
// 文字列インデックスから表示位置(セル位置)を計算
|
|
230
212
|
const stringStartIndex = match.index + (match[0].length - match[1].length);
|
|
231
|
-
// WebLinksAddon と同じ方式で位置をマッピング(0-indexed lineIndexを渡す)
|
|
232
213
|
const [, startX] = this._mapStrIdx(lineIndex, 0, stringStartIndex);
|
|
233
214
|
const [, endX] = this._mapStrIdx(lineIndex, startX, filePath.length);
|
|
234
215
|
if (startX === -1 || endX === -1) {
|
|
@@ -237,7 +218,6 @@ class FileLinkProvider {
|
|
|
237
218
|
links.push({
|
|
238
219
|
text: filePath,
|
|
239
220
|
range: {
|
|
240
|
-
// IBufferCellPosition は 1-indexed
|
|
241
221
|
start: { x: startX + 1, y: bufferLineNumber },
|
|
242
222
|
end: { x: endX, y: bufferLineNumber }
|
|
243
223
|
},
|
|
@@ -250,12 +230,6 @@ class FileLinkProvider {
|
|
|
250
230
|
}
|
|
251
231
|
callback(links.length > 0 ? links : undefined);
|
|
252
232
|
}
|
|
253
|
-
/**
|
|
254
|
-
* Map a string index back to buffer positions.
|
|
255
|
-
* Returns buffer position as [lineIndex, columnIndex] 0-based,
|
|
256
|
-
* or [-1, -1] in case the lookup ran into a non-existing line.
|
|
257
|
-
* (Based on xterm.js WebLinkProvider implementation)
|
|
258
|
-
*/
|
|
259
233
|
_mapStrIdx(lineIndex, rowIndex, stringIndex) {
|
|
260
234
|
const buf = this._terminal.buffer.active;
|
|
261
235
|
const cell = buf.getNullCell();
|
|
@@ -282,11 +256,9 @@ class FileLinkProvider {
|
|
|
282
256
|
return [lineIndex, start];
|
|
283
257
|
}
|
|
284
258
|
_isLikelyFilePath(text) {
|
|
285
|
-
// 拡張子を持つか確認
|
|
286
259
|
return /\.[a-zA-Z0-9]+(?::\d+(?::\d+)?)?$/.test(text);
|
|
287
260
|
}
|
|
288
261
|
_isUrl(text) {
|
|
289
|
-
// URL形式を除外
|
|
290
262
|
return /^https?:\/\//.test(text) || /^file:\/\//.test(text);
|
|
291
263
|
}
|
|
292
264
|
_parseFilePath(text) {
|
|
@@ -301,7 +273,6 @@ class FileLinkProvider {
|
|
|
301
273
|
return { path: text };
|
|
302
274
|
}
|
|
303
275
|
}
|
|
304
|
-
// Export helper functions for testing
|
|
305
276
|
const isLikelyFilePath = (text) => {
|
|
306
277
|
return /\.[a-zA-Z0-9]+(?::\d+(?::\d+)?)?$/.test(text) && !/^https?:\/\//.test(text);
|
|
307
278
|
};
|
|
@@ -317,20 +288,6 @@ const parseFilePath = (text) => {
|
|
|
317
288
|
return { path: text };
|
|
318
289
|
};
|
|
319
290
|
|
|
320
|
-
/**
|
|
321
|
-
* xterm.js addon for detecting and handling file path links in terminal output.
|
|
322
|
-
* Similar to WebLinksAddon but for local file paths.
|
|
323
|
-
*
|
|
324
|
-
* Usage:
|
|
325
|
-
* ```typescript
|
|
326
|
-
* const fileLinksAddon = new FileLinksAddon((event, filePath, line, column) => {
|
|
327
|
-
* if (event.metaKey || event.ctrlKey) {
|
|
328
|
-
* // Open file in editor
|
|
329
|
-
* }
|
|
330
|
-
* });
|
|
331
|
-
* terminal.loadAddon(fileLinksAddon);
|
|
332
|
-
* ```
|
|
333
|
-
*/
|
|
334
291
|
class FileLinksAddon {
|
|
335
292
|
_handler;
|
|
336
293
|
_terminal;
|
|
@@ -347,34 +304,6 @@ class FileLinksAddon {
|
|
|
347
304
|
}
|
|
348
305
|
}
|
|
349
306
|
|
|
350
|
-
/**
|
|
351
|
-
* localhost/127.0.0.1 URLをmachina web proxy URLに変換するリンクプロバイダ。
|
|
352
|
-
*
|
|
353
|
-
* 変換ルール:
|
|
354
|
-
* http://localhost:4200/path → https://{serverUUID}--4200.proxy.machina.at/path
|
|
355
|
-
* http://127.0.0.1:3000/api → https://{serverUUID}--3000.proxy.machina.at/api
|
|
356
|
-
*
|
|
357
|
-
* deployVersion に応じて suffix を付与:
|
|
358
|
-
* - 'pr-2464' → https://{uuid}--{port}--pr-2464.proxy.machina.at/...
|
|
359
|
-
* - 'v19.10.0-dev.5' 等 → https://{uuid}--{port}--dev.proxy.machina.at/...
|
|
360
|
-
* - null / 'stable' → suffix なし
|
|
361
|
-
*
|
|
362
|
-
* proxy iframe の SW intercept は referrer-based に変更されたため、 top window URL に
|
|
363
|
-
* 余分な marker prefix を埋め込む必要はない。 iframe.src は location.pathname をそのまま継承する。
|
|
364
|
-
* @see projects/@machina.at/web-proxy/src/app/services/proxy-iframe-url.ts
|
|
365
|
-
*/
|
|
366
|
-
const LOCALHOST_URL_REGEX = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::(\d+))?(\/[^\s<>'")\]]*)?/g;
|
|
367
|
-
/**
|
|
368
|
-
* deployVersion を proxy.machina.at のサブドメイン suffix に変換。
|
|
369
|
-
*
|
|
370
|
-
* 変換ルール:
|
|
371
|
-
* - null / 'stable' → '' (suffix なし)
|
|
372
|
-
* - 'pr-{N}' → '--pr-{N}'
|
|
373
|
-
* - 'dev' / 'develop' → '--dev'
|
|
374
|
-
* - 'v{X.Y.Z}' / 'v{X.Y.Z-pre.N}' → '--v{X-Y-Z}[--pre-N]' (dot → dash)
|
|
375
|
-
* - 'X.Y.Z' (v prefix なし) → 同上
|
|
376
|
-
* - その他 → '' (fallback)
|
|
377
|
-
*/
|
|
378
307
|
function deployVersionToHostSuffix(version) {
|
|
379
308
|
if (!version || version === 'stable')
|
|
380
309
|
return '';
|
|
@@ -382,7 +311,6 @@ function deployVersionToHostSuffix(version) {
|
|
|
382
311
|
return `--${version}`;
|
|
383
312
|
if (version === 'dev' || version === 'develop')
|
|
384
313
|
return '--dev';
|
|
385
|
-
// semver format: (v)?{MAJOR}.{MINOR}.{PATCH}[-{pre}.{num}]
|
|
386
314
|
const match = version.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([a-z]+)\.(\d+))?$/);
|
|
387
315
|
if (match) {
|
|
388
316
|
const [, major, minor, patch, pre, num] = match;
|
|
@@ -393,6 +321,43 @@ function deployVersionToHostSuffix(version) {
|
|
|
393
321
|
}
|
|
394
322
|
return '';
|
|
395
323
|
}
|
|
324
|
+
|
|
325
|
+
function encodeUrlHandoff(token, endpoints, env) {
|
|
326
|
+
if (env) {
|
|
327
|
+
return btoa(JSON.stringify({ t: token, env }));
|
|
328
|
+
}
|
|
329
|
+
return btoa(JSON.stringify({ t: token, e: endpoints }));
|
|
330
|
+
}
|
|
331
|
+
function appendUrlHandoff(proxyUrl, token, endpoints, env) {
|
|
332
|
+
const u = new URL(proxyUrl);
|
|
333
|
+
u.searchParams.set('lt', encodeUrlHandoff(token, endpoints, env));
|
|
334
|
+
return u.toString();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function openWebProxyForPort(opts) {
|
|
338
|
+
const { serverUUID, port, path = '/', token, endpoints, env, deployVersion } = opts;
|
|
339
|
+
const handler = opts.handler ?? ((_, url) => { window.open(url, '_blank', 'noopener'); });
|
|
340
|
+
const hostSuffix = deployVersionToHostSuffix(deployVersion);
|
|
341
|
+
const baseProxyUrl = `https://${serverUUID}--${port}${hostSuffix}.proxy.machina.at${path}`;
|
|
342
|
+
if (!token || !endpoints) {
|
|
343
|
+
handler(undefined, baseProxyUrl);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const proxyUrlWithHandoff = appendUrlHandoff(baseProxyUrl, token, endpoints, env);
|
|
347
|
+
window.open(proxyUrlWithHandoff, '_blank', 'noopener');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function toProxyUrl(serverUUID, localhostUrl, deployVersion) {
|
|
351
|
+
const match = localhostUrl.match(/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::(\d+))?(\/.*)?$/);
|
|
352
|
+
if (!match)
|
|
353
|
+
return null;
|
|
354
|
+
const port = match[1] || '80';
|
|
355
|
+
const path = match[2] || '/';
|
|
356
|
+
const hostSuffix = deployVersionToHostSuffix(deployVersion);
|
|
357
|
+
return `https://${serverUUID}--${port}${hostSuffix}.proxy.machina.at${path}`;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const LOCALHOST_URL_REGEX = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::(\d+))?(\/[^\s<>'")\]]*)?/g;
|
|
396
361
|
class ProxyLinkProvider {
|
|
397
362
|
_terminal;
|
|
398
363
|
_getServerUUID;
|
|
@@ -400,15 +365,19 @@ class ProxyLinkProvider {
|
|
|
400
365
|
_handler;
|
|
401
366
|
_getDeployVersion;
|
|
402
367
|
_getEndpoints;
|
|
368
|
+
_onMissingToken;
|
|
369
|
+
_getEnv;
|
|
403
370
|
constructor(_terminal, _getServerUUID, _getToken, _handler = (_, url) => {
|
|
404
371
|
window.open(url, '_blank', 'noopener');
|
|
405
|
-
}, _getDeployVersion, _getEndpoints) {
|
|
372
|
+
}, _getDeployVersion, _getEndpoints, _onMissingToken, _getEnv) {
|
|
406
373
|
this._terminal = _terminal;
|
|
407
374
|
this._getServerUUID = _getServerUUID;
|
|
408
375
|
this._getToken = _getToken;
|
|
409
376
|
this._handler = _handler;
|
|
410
377
|
this._getDeployVersion = _getDeployVersion;
|
|
411
378
|
this._getEndpoints = _getEndpoints;
|
|
379
|
+
this._onMissingToken = _onMissingToken;
|
|
380
|
+
this._getEnv = _getEnv;
|
|
412
381
|
}
|
|
413
382
|
provideLinks(bufferLineNumber, callback) {
|
|
414
383
|
const lineIndex = bufferLineNumber - 1;
|
|
@@ -434,82 +403,60 @@ class ProxyLinkProvider {
|
|
|
434
403
|
start: { x: startX, y: bufferLineNumber },
|
|
435
404
|
end: { x: endX, y: bufferLineNumber },
|
|
436
405
|
},
|
|
437
|
-
// serverUUIDはクリック時にlazy評価(セッション接続後に値が入る)
|
|
438
406
|
activate: (event) => {
|
|
439
407
|
const uuid = this._getServerUUID();
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const targetUrl = proxyUrl || matchedUrl;
|
|
449
|
-
console.log('[proxy-link] activate:', { matchedUrl, uuid, proxyUrl, targetUrl, hasToken: !!token });
|
|
450
|
-
if (token && proxyUrl) {
|
|
451
|
-
// postMessage方式: openerとしてwindow参照を保持
|
|
452
|
-
const proxyOrigin = new URL(proxyUrl).origin;
|
|
453
|
-
const win = window.open(targetUrl, '_blank');
|
|
454
|
-
if (win) {
|
|
455
|
-
const listener = (e) => {
|
|
456
|
-
// クロスオリジン WindowProxy の === 比較は信頼できないため origin で判定
|
|
457
|
-
if (e.origin === proxyOrigin && e.data?.type === 'proxy-auth-ready') {
|
|
458
|
-
console.log('[proxy-link] auth-ready received, sending token');
|
|
459
|
-
const endpoints = this._getEndpoints?.();
|
|
460
|
-
win.postMessage({ type: 'proxy-auth-token', token, endpoints }, proxyOrigin);
|
|
461
|
-
window.removeEventListener('message', listener);
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
window.addEventListener('message', listener);
|
|
465
|
-
// 10秒後にリスナーをクリーンアップ
|
|
466
|
-
setTimeout(() => window.removeEventListener('message', listener), 10000);
|
|
467
|
-
}
|
|
408
|
+
if (!uuid) {
|
|
409
|
+
console.warn('[ProxyLinkProvider] serverUUID undefined, opening localhost URL as-is (no proxy):', {
|
|
410
|
+
matchedUrl,
|
|
411
|
+
hasGetToken: !!this._getToken,
|
|
412
|
+
hasGetEndpoints: !!this._getEndpoints,
|
|
413
|
+
});
|
|
414
|
+
this._handler(event, matchedUrl);
|
|
415
|
+
return;
|
|
468
416
|
}
|
|
469
|
-
|
|
470
|
-
|
|
417
|
+
const token = this._getToken?.();
|
|
418
|
+
const endpoints = this._getEndpoints?.();
|
|
419
|
+
if ((!token || !endpoints) && this._onMissingToken) {
|
|
420
|
+
this._onMissingToken();
|
|
421
|
+
return;
|
|
471
422
|
}
|
|
423
|
+
openWebProxyForPort({
|
|
424
|
+
serverUUID: uuid,
|
|
425
|
+
port,
|
|
426
|
+
path,
|
|
427
|
+
token,
|
|
428
|
+
endpoints,
|
|
429
|
+
env: this._getEnv?.(),
|
|
430
|
+
deployVersion: this._getDeployVersion?.(),
|
|
431
|
+
handler: (_e, url) => this._handler(event, url),
|
|
432
|
+
});
|
|
472
433
|
},
|
|
473
434
|
});
|
|
474
435
|
}
|
|
475
436
|
callback(links.length > 0 ? links : undefined);
|
|
476
437
|
}
|
|
477
438
|
}
|
|
478
|
-
function toProxyUrl(serverUUID, localhostUrl, deployVersion) {
|
|
479
|
-
const match = localhostUrl.match(/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::(\d+))?(\/.*)?$/);
|
|
480
|
-
if (!match)
|
|
481
|
-
return null;
|
|
482
|
-
const port = match[1] || '80';
|
|
483
|
-
const path = match[2] || '/';
|
|
484
|
-
const hostSuffix = deployVersionToHostSuffix(deployVersion);
|
|
485
|
-
return `https://${serverUUID}--${port}${hostSuffix}.proxy.machina.at${path}`;
|
|
486
|
-
}
|
|
487
439
|
|
|
488
|
-
/**
|
|
489
|
-
* xterm.js addon: localhost URLをmachina web proxyにリダイレクトするリンクを表示。
|
|
490
|
-
*
|
|
491
|
-
* Usage:
|
|
492
|
-
* ```typescript
|
|
493
|
-
* const addon = new ProxyLinksAddon(() => currentServerUUID);
|
|
494
|
-
* terminal.loadAddon(addon);
|
|
495
|
-
* ```
|
|
496
|
-
*/
|
|
497
440
|
class ProxyLinksAddon {
|
|
498
441
|
_getServerUUID;
|
|
499
442
|
_getToken;
|
|
500
443
|
_handler;
|
|
501
444
|
_getDeployVersion;
|
|
502
445
|
_getEndpoints;
|
|
446
|
+
_onMissingToken;
|
|
447
|
+
_getEnv;
|
|
503
448
|
_linkProvider;
|
|
504
|
-
constructor(_getServerUUID, _getToken, _handler, _getDeployVersion, _getEndpoints) {
|
|
449
|
+
constructor(_getServerUUID, _getToken, _handler, _getDeployVersion, _getEndpoints, _onMissingToken, _getEnv) {
|
|
505
450
|
this._getServerUUID = _getServerUUID;
|
|
506
451
|
this._getToken = _getToken;
|
|
507
452
|
this._handler = _handler;
|
|
508
453
|
this._getDeployVersion = _getDeployVersion;
|
|
509
454
|
this._getEndpoints = _getEndpoints;
|
|
455
|
+
this._onMissingToken = _onMissingToken;
|
|
456
|
+
this._getEnv = _getEnv;
|
|
510
457
|
}
|
|
511
458
|
activate(terminal) {
|
|
512
|
-
this._linkProvider = terminal.registerLinkProvider(new ProxyLinkProvider(terminal, this._getServerUUID, this._getToken, this._handler, this._getDeployVersion, this._getEndpoints));
|
|
459
|
+
this._linkProvider = terminal.registerLinkProvider(new ProxyLinkProvider(terminal, this._getServerUUID, this._getToken, this._handler, this._getDeployVersion, this._getEndpoints, this._onMissingToken, this._getEnv));
|
|
513
460
|
}
|
|
514
461
|
dispose() {
|
|
515
462
|
this._linkProvider?.dispose();
|
|
@@ -524,17 +471,7 @@ var XtermActionId;
|
|
|
524
471
|
XtermActionId["FILE_LINK_CLICK"] = "xterm:file-link-click";
|
|
525
472
|
XtermActionId["FOCUS_INPUT_REQUEST"] = "xterm:focus-input-request";
|
|
526
473
|
})(XtermActionId || (XtermActionId = {}));
|
|
527
|
-
|
|
528
|
-
const XTERM_SERVER_UUID = new InjectionToken('XTERM_SERVER_UUID');
|
|
529
|
-
/** DI token for providing CLI token getter to ProxyLinksAddon */
|
|
530
|
-
const XTERM_PROXY_TOKEN = new InjectionToken('XTERM_PROXY_TOKEN');
|
|
531
|
-
/** DI token for providing deploy version getter to ProxyLinksAddon */
|
|
532
|
-
const XTERM_DEPLOY_VERSION = new InjectionToken('XTERM_DEPLOY_VERSION');
|
|
533
|
-
/**
|
|
534
|
-
* DI token for providing API endpoints (apiNext / hasura) to ProxyLinksAddon.
|
|
535
|
-
* web-proxy が opener postMessage 経由でこの値を受信して使用する (環境追従)。
|
|
536
|
-
*/
|
|
537
|
-
const XTERM_PROXY_ENDPOINTS = new InjectionToken('XTERM_PROXY_ENDPOINTS');
|
|
474
|
+
const XTERM_SESSION_CONTEXT = new InjectionToken('XTERM_SESSION_CONTEXT');
|
|
538
475
|
class XtermOrganismStore extends InjectableComponent {
|
|
539
476
|
static DI = makeDI(XtermOrganismStore, () => () => ({
|
|
540
477
|
data: '',
|
|
@@ -548,14 +485,7 @@ class XtermOrganismStore extends InjectableComponent {
|
|
|
548
485
|
queryResult = input('', ...(ngDevMode ? [{ debugName: "queryResult" }] : []));
|
|
549
486
|
useInteractiveTheme = input(false, ...(ngDevMode ? [{ debugName: "useInteractiveTheme" }] : []));
|
|
550
487
|
totalWritten = input(0, ...(ngDevMode ? [{ debugName: "totalWritten" }] : []));
|
|
551
|
-
|
|
552
|
-
getServerUUID = inject(XTERM_SERVER_UUID, { optional: true });
|
|
553
|
-
/** Injected CLI token getter for proxy link auto-auth (optional) */
|
|
554
|
-
getProxyToken = inject(XTERM_PROXY_TOKEN, { optional: true });
|
|
555
|
-
/** Injected deploy version getter for proxy URL host suffix (optional) */
|
|
556
|
-
getDeployVersion = inject(XTERM_DEPLOY_VERSION, { optional: true });
|
|
557
|
-
/** Injected endpoints getter for opener-driven environment switching (optional) */
|
|
558
|
-
getProxyEndpoints = inject(XTERM_PROXY_ENDPOINTS, { optional: true });
|
|
488
|
+
sessionContext = inject(XTERM_SESSION_CONTEXT, { optional: true });
|
|
559
489
|
constructor() {
|
|
560
490
|
super();
|
|
561
491
|
XtermOrganismStore.DI.initialize(this);
|
|
@@ -568,20 +498,143 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
568
498
|
args: [{ standalone: true, selector: 'organisms-xterm' }]
|
|
569
499
|
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], queryResult: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryResult", required: false }] }], useInteractiveTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "useInteractiveTheme", required: false }] }], totalWritten: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalWritten", required: false }] }] } });
|
|
570
500
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
501
|
+
const MIN_VIEWPORT_COLS = 2;
|
|
502
|
+
const MIN_VIEWPORT_ROWS = 1;
|
|
503
|
+
const MAX_VIEWPORT_COLS = 500;
|
|
504
|
+
const MAX_VIEWPORT_ROWS = 200;
|
|
505
|
+
const CANONICAL_FONT_FAMILY = '"FiraCode Nerd Font", "Fira Code", "SF Mono", "Cascadia Code", "Consolas", "Courier New", monospace';
|
|
506
|
+
const CANONICAL_FONT_SIZE_PX = 14;
|
|
507
|
+
const CANONICAL_LINE_HEIGHT = 1.2;
|
|
508
|
+
const DEFAULT_CELL_WIDTH = 8.4;
|
|
509
|
+
const DEFAULT_CELL_HEIGHT = CANONICAL_FONT_SIZE_PX * CANONICAL_LINE_HEIGHT;
|
|
510
|
+
function measureCellMetric() {
|
|
511
|
+
try {
|
|
512
|
+
const doc = document;
|
|
513
|
+
if (!doc?.body)
|
|
514
|
+
return { cellWidth: DEFAULT_CELL_WIDTH, cellHeight: DEFAULT_CELL_HEIGHT };
|
|
515
|
+
const probe = doc.createElement('span');
|
|
516
|
+
probe.textContent = 'M';
|
|
517
|
+
probe.style.position = 'absolute';
|
|
518
|
+
probe.style.visibility = 'hidden';
|
|
519
|
+
probe.style.whiteSpace = 'pre';
|
|
520
|
+
probe.style.left = '-9999px';
|
|
521
|
+
probe.style.top = '-9999px';
|
|
522
|
+
probe.style.fontFamily = CANONICAL_FONT_FAMILY;
|
|
523
|
+
probe.style.fontSize = `${CANONICAL_FONT_SIZE_PX}px`;
|
|
524
|
+
probe.style.lineHeight = String(CANONICAL_LINE_HEIGHT);
|
|
525
|
+
doc.body.appendChild(probe);
|
|
526
|
+
const rect = probe.getBoundingClientRect();
|
|
527
|
+
doc.body.removeChild(probe);
|
|
528
|
+
const cellWidth = rect.width >= 3 ? rect.width : DEFAULT_CELL_WIDTH;
|
|
529
|
+
const cellHeight = rect.height >= 8 ? rect.height : DEFAULT_CELL_HEIGHT;
|
|
530
|
+
return { cellWidth, cellHeight };
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
return { cellWidth: DEFAULT_CELL_WIDTH, cellHeight: DEFAULT_CELL_HEIGHT };
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function computeFitToViewport(input) {
|
|
537
|
+
const { viewportElement, cellWidth, cellHeight } = input;
|
|
538
|
+
if (!cellWidth || !cellHeight || cellWidth <= 0 || cellHeight <= 0)
|
|
539
|
+
return null;
|
|
540
|
+
const w = viewportElement.offsetWidth;
|
|
541
|
+
const h = viewportElement.offsetHeight;
|
|
542
|
+
if (w <= 0 || h <= 0)
|
|
543
|
+
return null;
|
|
544
|
+
return {
|
|
545
|
+
cols: Math.min(MAX_VIEWPORT_COLS, Math.max(MIN_VIEWPORT_COLS, Math.floor(w / cellWidth))),
|
|
546
|
+
rows: Math.min(MAX_VIEWPORT_ROWS, Math.max(MIN_VIEWPORT_ROWS, Math.floor(h / cellHeight))),
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
let cached = null;
|
|
551
|
+
function clearCanvasCellMetricCache() {
|
|
552
|
+
cached = null;
|
|
553
|
+
}
|
|
554
|
+
function recordCanvasCellMetric(metric) {
|
|
555
|
+
if (cached)
|
|
556
|
+
return;
|
|
557
|
+
if (metric.cellWidth > 0 && metric.cellHeight > 0) {
|
|
558
|
+
cached = { cellWidth: metric.cellWidth, cellHeight: metric.cellHeight };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const CELL_WIDTH_RATIO = 0.615;
|
|
562
|
+
const CELL_HEIGHT_RATIO = 1.43;
|
|
563
|
+
function getCanvasCellMetric(fontSize = CANONICAL_FONT_SIZE_PX) {
|
|
564
|
+
if (cached)
|
|
565
|
+
return cached;
|
|
566
|
+
cached = {
|
|
567
|
+
cellWidth: fontSize * CELL_WIDTH_RATIO,
|
|
568
|
+
cellHeight: Math.round(fontSize * CELL_HEIGHT_RATIO),
|
|
569
|
+
};
|
|
570
|
+
return cached;
|
|
571
|
+
}
|
|
572
|
+
function _legacyMeasureViaTempTerminal(fontSize = CANONICAL_FONT_SIZE_PX) {
|
|
573
|
+
const fallback = {
|
|
574
|
+
cellWidth: fontSize * 0.6,
|
|
575
|
+
cellHeight: fontSize * CANONICAL_LINE_HEIGHT,
|
|
576
|
+
};
|
|
577
|
+
if (typeof document === 'undefined')
|
|
578
|
+
return fallback;
|
|
579
|
+
let container = null;
|
|
580
|
+
let terminal = null;
|
|
581
|
+
try {
|
|
582
|
+
container = document.createElement('div');
|
|
583
|
+
container.style.position = 'absolute';
|
|
584
|
+
container.style.visibility = 'hidden';
|
|
585
|
+
container.style.pointerEvents = 'none';
|
|
586
|
+
container.style.width = '500px';
|
|
587
|
+
container.style.height = '500px';
|
|
588
|
+
container.style.left = '-9999px';
|
|
589
|
+
container.style.top = '-9999px';
|
|
590
|
+
document.body.appendChild(container);
|
|
591
|
+
terminal = new Terminal({
|
|
592
|
+
fontSize,
|
|
593
|
+
fontFamily: CANONICAL_FONT_FAMILY,
|
|
594
|
+
lineHeight: CANONICAL_LINE_HEIGHT,
|
|
595
|
+
rows: 24,
|
|
596
|
+
cols: 80,
|
|
597
|
+
});
|
|
598
|
+
terminal.open(container);
|
|
599
|
+
const internal = terminal;
|
|
600
|
+
const cell = internal._core?._renderService?.dimensions?.css?.cell;
|
|
601
|
+
const cellWidth = cell?.width;
|
|
602
|
+
const cellHeight = cell?.height;
|
|
603
|
+
if (typeof cellWidth === 'number' && typeof cellHeight === 'number' && cellWidth > 0 && cellHeight > 0) {
|
|
604
|
+
cached = { cellWidth: Math.ceil(cellWidth), cellHeight: Math.ceil(cellHeight) };
|
|
605
|
+
return cached;
|
|
606
|
+
}
|
|
607
|
+
return fallback;
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
return fallback;
|
|
611
|
+
}
|
|
612
|
+
finally {
|
|
613
|
+
try {
|
|
614
|
+
terminal?.dispose();
|
|
615
|
+
}
|
|
616
|
+
catch {
|
|
617
|
+
void 0;
|
|
618
|
+
}
|
|
619
|
+
try {
|
|
620
|
+
if (container?.parentElement)
|
|
621
|
+
container.parentElement.removeChild(container);
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
void 0;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
575
629
|
const MIN_CONTAINER_SIZE = 10;
|
|
576
|
-
/** Default display-only theme (for catalog/storybook) */
|
|
577
630
|
const DISPLAY_THEME = {
|
|
578
631
|
background: '#0a0a0a',
|
|
579
632
|
foreground: '#66d9ef',
|
|
580
633
|
cursor: '#ff79c6',
|
|
581
|
-
cyan: '#
|
|
582
|
-
brightCyan: '#
|
|
583
|
-
green: '#
|
|
584
|
-
brightGreen: '#
|
|
634
|
+
cyan: '#5bc5ff',
|
|
635
|
+
brightCyan: '#8ad7ff',
|
|
636
|
+
green: '#33ff33',
|
|
637
|
+
brightGreen: '#66ff66',
|
|
585
638
|
magenta: '#ff79c6',
|
|
586
639
|
brightMagenta: '#ff79c6',
|
|
587
640
|
yellow: '#f1fa8c',
|
|
@@ -589,26 +642,25 @@ const DISPLAY_THEME = {
|
|
|
589
642
|
red: '#ff5555',
|
|
590
643
|
brightRed: '#ff5555'
|
|
591
644
|
};
|
|
592
|
-
/** Interactive session theme (matches original TerminalManager) */
|
|
593
645
|
const INTERACTIVE_THEME = {
|
|
594
646
|
background: '#0a0a0a',
|
|
595
647
|
foreground: '#e0e0e0',
|
|
596
648
|
cursor: '#e0e0e0',
|
|
597
649
|
black: '#000000',
|
|
598
650
|
red: '#cd3131',
|
|
599
|
-
green: '#
|
|
651
|
+
green: '#33ff33',
|
|
600
652
|
yellow: '#e5e510',
|
|
601
653
|
blue: '#2472c8',
|
|
602
654
|
magenta: '#bc3fbc',
|
|
603
|
-
cyan: '#
|
|
655
|
+
cyan: '#5bc5ff',
|
|
604
656
|
white: '#e5e5e5',
|
|
605
657
|
brightBlack: '#666666',
|
|
606
658
|
brightRed: '#f14c4c',
|
|
607
|
-
brightGreen: '#
|
|
659
|
+
brightGreen: '#66ff66',
|
|
608
660
|
brightYellow: '#f5f543',
|
|
609
661
|
brightBlue: '#3b8eea',
|
|
610
662
|
brightMagenta: '#d670d6',
|
|
611
|
-
brightCyan: '#
|
|
663
|
+
brightCyan: '#8ad7ff',
|
|
612
664
|
brightWhite: '#e5e5e5',
|
|
613
665
|
};
|
|
614
666
|
class XtermOrganism extends NgAtomicComponent {
|
|
@@ -624,20 +676,15 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
624
676
|
fileLinksAddon;
|
|
625
677
|
resizeObserver;
|
|
626
678
|
lastDataLength = 0;
|
|
627
|
-
/** Tracks how many bytes have been processed (for totalWritten-based diff detection) */
|
|
628
679
|
lastProcessedLength = 0;
|
|
629
680
|
terminalInitialized = signal(false, ...(ngDevMode ? [{ debugName: "terminalInitialized" }] : []));
|
|
630
|
-
/** Tracks if initialization was skipped due to container being too small */
|
|
631
681
|
initSkippedDueToSize = false;
|
|
632
|
-
/** IDisposable refs for explicit cleanup (prevents memory leaks on mobile) */
|
|
633
682
|
disposables = [];
|
|
634
|
-
/** Timer refs for cleanup in ngOnDestroy */
|
|
635
683
|
stableTimer = null;
|
|
636
684
|
resizeTimer = null;
|
|
637
685
|
fallbackTimer = null;
|
|
638
686
|
constructor() {
|
|
639
687
|
super();
|
|
640
|
-
// Watch for data input changes (with totalWritten support for buffer overflow handling)
|
|
641
688
|
effect(() => {
|
|
642
689
|
const isInitialized = this.terminalInitialized();
|
|
643
690
|
const rawData = this.store.data();
|
|
@@ -646,17 +693,13 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
646
693
|
const data = dataIsFunction ? rawData() : rawData;
|
|
647
694
|
if (!isInitialized)
|
|
648
695
|
return;
|
|
649
|
-
// Use totalWritten-based logic when available (handles buffer overflow correctly)
|
|
650
696
|
if (totalWritten > 0) {
|
|
651
697
|
this.writeDataWithTotalWritten(data ?? '', totalWritten);
|
|
652
698
|
}
|
|
653
699
|
else {
|
|
654
|
-
// Legacy fallback for backward compatibility
|
|
655
700
|
this.writeDataToTerminal(data);
|
|
656
701
|
}
|
|
657
702
|
});
|
|
658
|
-
// Watch for interactive mode changes and update host class
|
|
659
|
-
// Note: Using effect with classList because @HostBinding doesn't work with OnPush + signals
|
|
660
703
|
effect(() => {
|
|
661
704
|
const isInteractive = this.store.interactive();
|
|
662
705
|
if (isInteractive) {
|
|
@@ -666,24 +709,18 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
666
709
|
this.hostElement.nativeElement.classList.remove('interactive');
|
|
667
710
|
}
|
|
668
711
|
});
|
|
669
|
-
// Watch for interactive mode changes and update cursor blink
|
|
670
712
|
effect(() => {
|
|
671
713
|
const isInitialized = this.terminalInitialized();
|
|
672
714
|
const isInteractive = this.store.interactive();
|
|
673
715
|
if (!isInitialized || !this.terminal)
|
|
674
716
|
return;
|
|
675
|
-
// Update cursor blink setting
|
|
676
717
|
this.terminal.options.cursorBlink = isInteractive;
|
|
677
718
|
});
|
|
678
719
|
}
|
|
679
720
|
writeDataToTerminal(data) {
|
|
680
|
-
// Guard against undefined/null/empty data
|
|
681
|
-
// Empty string is treated as "no data yet" (from startWith('') in streams)
|
|
682
|
-
// Explicit reset should use the public clear() or reset() methods
|
|
683
721
|
if (data === undefined || data === null || data === '') {
|
|
684
722
|
return;
|
|
685
723
|
}
|
|
686
|
-
// O(1) length comparison instead of O(n) startsWith
|
|
687
724
|
if (data.length >= this.lastDataLength) {
|
|
688
725
|
const newPart = data.slice(this.lastDataLength);
|
|
689
726
|
if (newPart) {
|
|
@@ -692,55 +729,48 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
692
729
|
this.lastDataLength = data.length;
|
|
693
730
|
}
|
|
694
731
|
else {
|
|
695
|
-
// Data got shorter = reset occurred
|
|
696
732
|
this.terminal.reset();
|
|
697
733
|
this.terminal.write(data);
|
|
698
734
|
this.lastDataLength = data.length;
|
|
699
735
|
}
|
|
700
736
|
}
|
|
701
|
-
/**
|
|
702
|
-
* Write data using totalWritten for accurate diff detection.
|
|
703
|
-
* This method handles buffer overflow scenarios correctly.
|
|
704
|
-
* When newBytesCount >= data.length, performs a full redraw with terminal.reset()
|
|
705
|
-
* to prevent stale output from previous sessions remaining on screen.
|
|
706
|
-
* @param data The current buffer content
|
|
707
|
-
* @param totalWritten Total bytes written to the buffer (cumulative)
|
|
708
|
-
*/
|
|
709
737
|
writeDataWithTotalWritten(data, totalWritten) {
|
|
710
738
|
if (!data)
|
|
711
739
|
return;
|
|
712
740
|
const newBytesCount = totalWritten - this.lastProcessedLength;
|
|
713
741
|
if (newBytesCount <= 0) {
|
|
714
742
|
if (totalWritten < this.lastProcessedLength) {
|
|
715
|
-
// Reset detected (totalWritten decreased)
|
|
716
743
|
this.terminal.reset();
|
|
717
744
|
this.terminal.write(data);
|
|
718
745
|
this.lastProcessedLength = totalWritten;
|
|
719
746
|
}
|
|
720
747
|
return;
|
|
721
748
|
}
|
|
722
|
-
|
|
723
|
-
// (newBytesCount >= data.length means the diff covers the entire buffer or more)
|
|
749
|
+
let written = '';
|
|
724
750
|
if (newBytesCount >= data.length) {
|
|
725
|
-
|
|
751
|
+
if (hasVisibleContent(data)) {
|
|
752
|
+
this.terminal.reset();
|
|
753
|
+
}
|
|
726
754
|
this.terminal.write(data);
|
|
755
|
+
written = data;
|
|
727
756
|
}
|
|
728
757
|
else {
|
|
729
|
-
|
|
730
|
-
const newContent = data.slice(-
|
|
758
|
+
const sliceN = Math.min(newBytesCount, data.length);
|
|
759
|
+
const newContent = data.slice(-sliceN);
|
|
731
760
|
if (newContent) {
|
|
732
761
|
this.terminal.write(newContent);
|
|
762
|
+
written = newContent;
|
|
733
763
|
}
|
|
734
764
|
}
|
|
735
765
|
this.lastProcessedLength = totalWritten;
|
|
766
|
+
if (hasDisplayClearWithoutScrollbackClear(written)) {
|
|
767
|
+
this.terminal.clear();
|
|
768
|
+
}
|
|
736
769
|
}
|
|
737
770
|
ngAfterViewInit() {
|
|
738
771
|
const containerElement = this.container().nativeElement;
|
|
739
|
-
// Guard against initialization in very small containers (e.g., SR-only 1x1 pixel hidden elements)
|
|
740
|
-
// This prevents FitAddon issues and excessive processing in hidden query sections
|
|
741
772
|
if (containerElement.offsetWidth < MIN_CONTAINER_SIZE || containerElement.offsetHeight < MIN_CONTAINER_SIZE) {
|
|
742
773
|
this.initSkippedDueToSize = true;
|
|
743
|
-
// Retry initialization when container reaches minimum size (e.g., after CSS/layout settles)
|
|
744
774
|
this.resizeObserver = new ResizeObserver(() => {
|
|
745
775
|
if (containerElement.offsetWidth >= MIN_CONTAINER_SIZE && containerElement.offsetHeight >= MIN_CONTAINER_SIZE) {
|
|
746
776
|
this.resizeObserver?.disconnect();
|
|
@@ -773,8 +803,6 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
773
803
|
this.fitAddon = new FitAddon();
|
|
774
804
|
this.terminal.loadAddon(this.fitAddon);
|
|
775
805
|
this.terminal.loadAddon(new WebLinksAddon());
|
|
776
|
-
// ImageAddon for displaying inline images (SIXEL and iTerm2 IIP support)
|
|
777
|
-
// Loaded dynamically to avoid browser compatibility issues
|
|
778
806
|
this.loadImageAddon();
|
|
779
807
|
this.githubLinksAddon = new GitHubLinksAddon((event, issueNumber) => {
|
|
780
808
|
this.dispatch({ id: XtermActionId.GITHUB_LINK_CLICK, payload: issueNumber });
|
|
@@ -786,38 +814,50 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
786
814
|
}
|
|
787
815
|
});
|
|
788
816
|
this.terminal.loadAddon(this.fileLinksAddon);
|
|
789
|
-
|
|
790
|
-
this.terminal.loadAddon(new ProxyLinksAddon(
|
|
817
|
+
const ctx = this.store.sessionContext;
|
|
818
|
+
this.terminal.loadAddon(new ProxyLinksAddon(ctx?.getServerUUID ?? (() => undefined), ctx?.getProxyToken, undefined, ctx?.getDeployVersion, ctx?.getProxyEndpoints, ctx?.onMissingToken, ctx?.getProxyEnv));
|
|
791
819
|
this.terminal.open(containerElement);
|
|
792
|
-
|
|
820
|
+
const core = this.terminal._core;
|
|
821
|
+
const charSizeChange = core?._charSizeService?.onCharSizeChange;
|
|
822
|
+
if (typeof charSizeChange === 'function') {
|
|
823
|
+
const disp = charSizeChange.call(core._charSizeService, () => {
|
|
824
|
+
if (!this.terminal)
|
|
825
|
+
return;
|
|
826
|
+
this.fitToViewport();
|
|
827
|
+
if (isInteractive) {
|
|
828
|
+
this.dispatch({ id: XtermActionId.RESIZED, payload: { cols: this.cols, rows: this.rows } });
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
if (disp && typeof disp.dispose === 'function') {
|
|
832
|
+
this.disposables.push(disp);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
793
835
|
if (isInteractive) {
|
|
794
836
|
this.disposables.push(this.terminal.onData((data) => {
|
|
795
837
|
this.dispatch({ id: XtermActionId.DATA_INPUT, payload: data });
|
|
796
838
|
}), this.terminal.onResize(({ cols, rows }) => this.dispatch({ id: XtermActionId.RESIZED, payload: { cols, rows } })));
|
|
797
839
|
}
|
|
798
|
-
// Use ResizeObserver with debounce to wait for container size to stabilize
|
|
799
|
-
// (CSS transitions take 300-500ms, so fixed setTimeout(100ms) is too early)
|
|
800
840
|
this.resizeObserver = new ResizeObserver(() => {
|
|
801
841
|
try {
|
|
802
842
|
if (!this.terminalInitialized()) {
|
|
803
|
-
this.
|
|
804
|
-
// Debounce: wait for container to stop changing before initializing
|
|
843
|
+
this.fitToViewport();
|
|
805
844
|
if (this.stableTimer)
|
|
806
845
|
clearTimeout(this.stableTimer);
|
|
807
846
|
this.stableTimer = setTimeout(() => {
|
|
808
847
|
this.stableTimer = null;
|
|
809
|
-
this.
|
|
848
|
+
this.fitToViewport();
|
|
810
849
|
this.markInitialized(isInteractive);
|
|
811
850
|
}, 150);
|
|
812
851
|
}
|
|
813
|
-
else
|
|
814
|
-
// Debounce resize to wait for CSS transitions to complete (300ms)
|
|
852
|
+
else {
|
|
815
853
|
if (this.resizeTimer)
|
|
816
854
|
clearTimeout(this.resizeTimer);
|
|
817
855
|
this.resizeTimer = setTimeout(() => {
|
|
818
856
|
this.resizeTimer = null;
|
|
819
|
-
this.
|
|
820
|
-
|
|
857
|
+
this.fitToViewport();
|
|
858
|
+
if (isInteractive) {
|
|
859
|
+
this.dispatch({ id: XtermActionId.RESIZED, payload: { cols: this.cols, rows: this.rows } });
|
|
860
|
+
}
|
|
821
861
|
}, 300);
|
|
822
862
|
}
|
|
823
863
|
}
|
|
@@ -826,17 +866,32 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
826
866
|
}
|
|
827
867
|
});
|
|
828
868
|
this.resizeObserver.observe(containerElement);
|
|
829
|
-
// Fallback: initialize after 500ms if ResizeObserver hasn't triggered stable state
|
|
830
869
|
this.fallbackTimer = setTimeout(() => {
|
|
831
870
|
this.fallbackTimer = null;
|
|
832
871
|
if (!this.terminalInitialized()) {
|
|
833
|
-
this.
|
|
872
|
+
this.fitToViewport();
|
|
834
873
|
this.markInitialized(isInteractive);
|
|
835
874
|
}
|
|
836
875
|
}, 500);
|
|
837
876
|
}
|
|
877
|
+
fitToViewport() {
|
|
878
|
+
if (!this.terminal)
|
|
879
|
+
return;
|
|
880
|
+
const viewportElement = this.container().nativeElement;
|
|
881
|
+
const sharedCanvas = getCanvasCellMetric();
|
|
882
|
+
const probe = measureCellMetric();
|
|
883
|
+
const cellWidth = sharedCanvas.cellWidth > 0 ? sharedCanvas.cellWidth : probe.cellWidth;
|
|
884
|
+
const cellHeight = sharedCanvas.cellHeight > 0 ? sharedCanvas.cellHeight : probe.cellHeight;
|
|
885
|
+
const result = computeFitToViewport({ viewportElement, cellWidth, cellHeight });
|
|
886
|
+
if (!result) {
|
|
887
|
+
this.fitAddon.fit();
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (result.cols !== this.terminal.cols || result.rows !== this.terminal.rows) {
|
|
891
|
+
this.terminal.resize(result.cols, result.rows);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
838
894
|
ngOnDestroy() {
|
|
839
|
-
// Clear pending timers to prevent post-destroy callbacks
|
|
840
895
|
if (this.stableTimer)
|
|
841
896
|
clearTimeout(this.stableTimer);
|
|
842
897
|
if (this.resizeTimer)
|
|
@@ -844,11 +899,9 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
844
899
|
if (this.fallbackTimer)
|
|
845
900
|
clearTimeout(this.fallbackTimer);
|
|
846
901
|
this.resizeObserver?.disconnect();
|
|
847
|
-
// Dispose xterm event handlers (onData, onResize) to prevent memory leaks
|
|
848
902
|
for (const d of this.disposables)
|
|
849
903
|
d.dispose();
|
|
850
904
|
this.disposables.length = 0;
|
|
851
|
-
// Remove terminal instance from window for E2E testing cleanup
|
|
852
905
|
if (typeof window !== 'undefined' && window.__XTERM_INSTANCES__) {
|
|
853
906
|
const instances = window.__XTERM_INSTANCES__;
|
|
854
907
|
const index = instances.indexOf(this.terminal);
|
|
@@ -863,12 +916,10 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
863
916
|
return;
|
|
864
917
|
this.terminalInitialized.set(true);
|
|
865
918
|
this.terminalReady.emit(this.terminal);
|
|
866
|
-
// Expose terminal instance for E2E testing (only in development/test environments)
|
|
867
919
|
if (typeof window !== 'undefined') {
|
|
868
920
|
window.__XTERM_INSTANCES__ = window.__XTERM_INSTANCES__ || [];
|
|
869
921
|
window.__XTERM_INSTANCES__.push(this.terminal);
|
|
870
922
|
}
|
|
871
|
-
// Dispatch initial RESIZED for interactive mode
|
|
872
923
|
if (isInteractive) {
|
|
873
924
|
this.dispatch({ id: XtermActionId.RESIZED, payload: { cols: this.cols, rows: this.rows } });
|
|
874
925
|
}
|
|
@@ -886,11 +937,9 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
886
937
|
this.terminal.loadAddon(this.imageAddon);
|
|
887
938
|
}
|
|
888
939
|
catch (e) {
|
|
889
|
-
// ImageAddon not available in browser environment - this is expected
|
|
890
940
|
console.debug('ImageAddon not available:', e);
|
|
891
941
|
}
|
|
892
942
|
}
|
|
893
|
-
// Public API
|
|
894
943
|
write(text) {
|
|
895
944
|
this.terminal?.write(text);
|
|
896
945
|
}
|
|
@@ -909,10 +958,13 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
909
958
|
scrollLines(lines) {
|
|
910
959
|
this.terminal?.scrollLines(lines);
|
|
911
960
|
}
|
|
961
|
+
focus() {
|
|
962
|
+
this.terminal?.focus();
|
|
963
|
+
const helperTextarea = this.container().nativeElement.querySelector('.xterm-helper-textarea');
|
|
964
|
+
helperTextarea?.focus();
|
|
965
|
+
}
|
|
912
966
|
fit() {
|
|
913
|
-
this.
|
|
914
|
-
// Only dispatch RESIZED after initialization to prevent sending wrong cols/rows
|
|
915
|
-
// during CSS transitions (SessionTemplateV2.ngAfterViewInit calls fit() before layout stabilizes)
|
|
967
|
+
this.fitToViewport();
|
|
916
968
|
if (this.terminalInitialized() && this.terminal) {
|
|
917
969
|
this.dispatch({ id: XtermActionId.RESIZED, payload: { cols: this.cols, rows: this.rows } });
|
|
918
970
|
}
|
|
@@ -924,14 +976,8 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
924
976
|
return this.terminal?.rows ?? 24;
|
|
925
977
|
}
|
|
926
978
|
getTerminal() {
|
|
927
|
-
// Only expose terminal after fitAddon.fit() has run (terminalInitialized === true)
|
|
928
979
|
return this.terminalInitialized() ? this.terminal : undefined;
|
|
929
980
|
}
|
|
930
|
-
/**
|
|
931
|
-
* Display an inline image using iTerm2's Inline Image Protocol
|
|
932
|
-
* @param base64Data Base64 encoded image data (PNG, JPEG, etc.)
|
|
933
|
-
* @param options Image display options
|
|
934
|
-
*/
|
|
935
981
|
displayImage(base64Data, options) {
|
|
936
982
|
if (!this.terminal)
|
|
937
983
|
return;
|
|
@@ -948,34 +994,34 @@ class XtermOrganism extends NgAtomicComponent {
|
|
|
948
994
|
if (options?.name) {
|
|
949
995
|
args.push(`name=${btoa(options.name)}`);
|
|
950
996
|
}
|
|
951
|
-
// iTerm2 Inline Image Protocol escape sequence
|
|
952
997
|
const escapeSequence = `\x1b]1337;File=${args.join(';')}:${base64Data}\x07`;
|
|
953
998
|
this.terminal.write(escapeSequence);
|
|
954
999
|
}
|
|
955
|
-
/**
|
|
956
|
-
* Get the ImageAddon instance for advanced image operations
|
|
957
|
-
*/
|
|
958
1000
|
getImageAddon() {
|
|
959
1001
|
return this.imageAddon;
|
|
960
1002
|
}
|
|
961
1003
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: XtermOrganism, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
962
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.6", type: XtermOrganism, isStandalone: true, selector: "organisms-xterm", outputs: { terminalReady: "terminalReady", githubLinkClick: "githubLinkClick" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: XtermOrganismStore, inputs: ["data", "data", "interactive", "interactive", "useInteractiveTheme", "useInteractiveTheme", "totalWritten", "totalWritten"] }], ngImport: i0, template: `<div #container class="xterm-wrapper"></div>`, isInline: true, styles: ["", "@import\"https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css\";:host{display:block;width:100%;height:100%}:host .xterm-wrapper{width:100%;height:100%;background:#0a0a0a;position:relative;overflow:hidden}:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor-layer{display:none!important}:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor,:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor-outline{opacity:0!important}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1004
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.6", type: XtermOrganism, isStandalone: true, selector: "organisms-xterm", outputs: { terminalReady: "terminalReady", githubLinkClick: "githubLinkClick" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: XtermOrganismStore, inputs: ["data", "data", "interactive", "interactive", "useInteractiveTheme", "useInteractiveTheme", "totalWritten", "totalWritten"] }], ngImport: i0, template: `<div #container class="terminal-viewport xterm-wrapper"></div>`, isInline: true, styles: ["", "@import\"https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css\";:host{display:block;width:100%;height:100%}:host .terminal-viewport,:host .xterm-wrapper{width:100%;height:100%;background:#0a0a0a;position:relative;overflow:hidden}:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor-layer{display:none!important}:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor,:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor-outline{opacity:0!important}:host ::ng-deep .xterm-screen canvas{pointer-events:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
963
1005
|
}
|
|
964
1006
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: XtermOrganism, decorators: [{
|
|
965
1007
|
type: Component,
|
|
966
|
-
args: [{ selector: 'organisms-xterm', standalone: true, imports: [], template: `<div #container class="xterm-wrapper"></div>`, changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [{
|
|
1008
|
+
args: [{ selector: 'organisms-xterm', standalone: true, imports: [], template: `<div #container class="terminal-viewport xterm-wrapper"></div>`, changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [{
|
|
967
1009
|
directive: XtermOrganismStore,
|
|
968
1010
|
inputs: ['data', 'interactive', 'useInteractiveTheme', 'totalWritten'],
|
|
969
|
-
}], styles: ["@import\"https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css\";:host{display:block;width:100%;height:100%}:host .xterm-wrapper{width:100%;height:100%;background:#0a0a0a;position:relative;overflow:hidden}:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor-layer{display:none!important}:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor,:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor-outline{opacity:0!important}\n"] }]
|
|
1011
|
+
}], styles: ["@import\"https://unpkg.com/@xterm/xterm@5.5.0/css/xterm.css\";:host{display:block;width:100%;height:100%}:host .terminal-viewport,:host .xterm-wrapper{width:100%;height:100%;background:#0a0a0a;position:relative;overflow:hidden}:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor-layer{display:none!important}:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor,:host:not(.interactive) .xterm-wrapper ::ng-deep .xterm-cursor-outline{opacity:0!important}:host ::ng-deep .xterm-screen canvas{pointer-events:none}\n"] }]
|
|
970
1012
|
}], ctorParameters: () => [], propDecorators: { container: [{ type: i0.ViewChild, args: ['container', { isSignal: true }] }], terminalReady: [{ type: i0.Output, args: ["terminalReady"] }], githubLinkClick: [{ type: i0.Output, args: ["githubLinkClick"] }] } });
|
|
1013
|
+
function hasVisibleContent(s) {
|
|
1014
|
+
const stripped = s
|
|
1015
|
+
// eslint-disable-next-line no-control-regex -- ANSI CSI / OSC を strip するため意図的に \x1b / \x07 を match
|
|
1016
|
+
.replace(/\x1b\[[\d;?]*[a-zA-Z]/g, '')
|
|
1017
|
+
// eslint-disable-next-line no-control-regex -- ANSI OSC を strip するため意図的に \x1b / \x07 を match
|
|
1018
|
+
.replace(/\x1b\][^\x07\x1b]*[\x07\x1b]/g, '');
|
|
1019
|
+
return /\S/.test(stripped);
|
|
1020
|
+
}
|
|
1021
|
+
function hasDisplayClearWithoutScrollbackClear(s) {
|
|
1022
|
+
return s.includes('\x1b[2J') && !s.includes('\x1b[3J');
|
|
1023
|
+
}
|
|
971
1024
|
|
|
972
|
-
/**
|
|
973
|
-
* Utility functions for displaying images in xterm using SIXEL format
|
|
974
|
-
*/
|
|
975
|
-
/**
|
|
976
|
-
* Generate ASCII art text using figlet
|
|
977
|
-
* Note: figlet is loaded dynamically as it's not available in browser environments
|
|
978
|
-
*/
|
|
979
1025
|
async function generateAsciiArt(text, font = 'ANSI Shadow') {
|
|
980
1026
|
try {
|
|
981
1027
|
const figlet = await import('figlet');
|
|
@@ -990,27 +1036,20 @@ async function generateAsciiArt(text, font = 'ANSI Shadow') {
|
|
|
990
1036
|
});
|
|
991
1037
|
}
|
|
992
1038
|
catch (e) {
|
|
993
|
-
// figlet not available in browser - return simple text
|
|
994
1039
|
console.debug('figlet not available:', e);
|
|
995
1040
|
return text;
|
|
996
1041
|
}
|
|
997
1042
|
}
|
|
998
|
-
/**
|
|
999
|
-
* Convert ASCII art text to SVG with transparent background
|
|
1000
|
-
*/
|
|
1001
1043
|
function asciiArtToSvg(asciiArt, options = {}) {
|
|
1002
1044
|
const { textColor = '#00FFFF', fontSize = 14 } = options;
|
|
1003
1045
|
const lines = asciiArt.split('\n');
|
|
1004
1046
|
const maxLineLength = Math.max(...lines.map(l => l.length));
|
|
1005
|
-
// Calculate dimensions based on monospace font metrics (block chars need more width)
|
|
1006
1047
|
const charWidth = fontSize * 0.65;
|
|
1007
1048
|
const lineHeight = fontSize * 1.4;
|
|
1008
1049
|
const padding = 40;
|
|
1009
1050
|
const width = Math.ceil(maxLineLength * charWidth) + padding;
|
|
1010
1051
|
const height = Math.ceil(lines.length * lineHeight) + padding;
|
|
1011
|
-
// Build SVG with text elements for each line
|
|
1012
1052
|
const textElements = lines.map((line, i) => {
|
|
1013
|
-
// Escape special XML characters
|
|
1014
1053
|
const escapedLine = line
|
|
1015
1054
|
.replace(/&/g, '&')
|
|
1016
1055
|
.replace(/</g, '<')
|
|
@@ -1023,9 +1062,6 @@ function asciiArtToSvg(asciiArt, options = {}) {
|
|
|
1023
1062
|
${textElements}
|
|
1024
1063
|
</svg>`;
|
|
1025
1064
|
}
|
|
1026
|
-
/**
|
|
1027
|
-
* Convert SVG to SIXEL format for xterm display
|
|
1028
|
-
*/
|
|
1029
1065
|
async function svgToSixel(svgString, options = {}) {
|
|
1030
1066
|
const { width = 400, height = 200, backgroundColor } = options;
|
|
1031
1067
|
return new Promise((resolve, reject) => {
|
|
@@ -1037,7 +1073,6 @@ async function svgToSixel(svgString, options = {}) {
|
|
|
1037
1073
|
reject(new Error('Failed to get canvas context'));
|
|
1038
1074
|
return;
|
|
1039
1075
|
}
|
|
1040
|
-
// Fill background (transparent if not specified)
|
|
1041
1076
|
if (backgroundColor) {
|
|
1042
1077
|
ctx.fillStyle = backgroundColor;
|
|
1043
1078
|
ctx.fillRect(0, 0, width, height);
|
|
@@ -1059,20 +1094,14 @@ async function svgToSixel(svgString, options = {}) {
|
|
|
1059
1094
|
img.src = url;
|
|
1060
1095
|
});
|
|
1061
1096
|
}
|
|
1062
|
-
/**
|
|
1063
|
-
* Convert ImageData to SIXEL escape sequence
|
|
1064
|
-
*/
|
|
1065
1097
|
function imageDataToSixel(imageData) {
|
|
1066
1098
|
const { width, height, data } = imageData;
|
|
1067
|
-
// Build color palette (quantize to 16 colors)
|
|
1068
1099
|
const palette = buildColorPalette(data, 16);
|
|
1069
1100
|
const colorMap = new Map();
|
|
1070
1101
|
palette.forEach((color, index) => {
|
|
1071
1102
|
colorMap.set(color, index);
|
|
1072
1103
|
});
|
|
1073
|
-
// SIXEL header
|
|
1074
1104
|
let sixel = '\x1bPq';
|
|
1075
|
-
// Define colors in palette
|
|
1076
1105
|
palette.forEach((color, index) => {
|
|
1077
1106
|
const [r, g, b] = hexToRgb(color);
|
|
1078
1107
|
const rPct = Math.round((r / 255) * 100);
|
|
@@ -1080,7 +1109,6 @@ function imageDataToSixel(imageData) {
|
|
|
1080
1109
|
const bPct = Math.round((b / 255) * 100);
|
|
1081
1110
|
sixel += `#${index};2;${rPct};${gPct};${bPct}`;
|
|
1082
1111
|
});
|
|
1083
|
-
// Process image in bands of 6 rows
|
|
1084
1112
|
for (let bandY = 0; bandY < height; bandY += 6) {
|
|
1085
1113
|
for (let colorIdx = 0; colorIdx < palette.length; colorIdx++) {
|
|
1086
1114
|
const color = palette[colorIdx];
|
|
@@ -1164,19 +1192,13 @@ function findNearestColor(targetHex, palette) {
|
|
|
1164
1192
|
}
|
|
1165
1193
|
return nearest;
|
|
1166
1194
|
}
|
|
1167
|
-
/**
|
|
1168
|
-
* Generate MACHINA ASCII art banner as SIXEL
|
|
1169
|
-
*/
|
|
1170
1195
|
async function getMachinaAsciiArtSixel(options = {}) {
|
|
1171
|
-
// Generate ASCII art for "MACHINA"
|
|
1172
1196
|
const machinaArt = await generateAsciiArt('MACHINA', 'ANSI Shadow');
|
|
1173
|
-
// Convert to SVG with cyan color (terminal style)
|
|
1174
1197
|
const fontSize = 14;
|
|
1175
1198
|
const svg = asciiArtToSvg(machinaArt, {
|
|
1176
1199
|
textColor: options.textColor || '#00FFFF',
|
|
1177
1200
|
fontSize
|
|
1178
1201
|
});
|
|
1179
|
-
// Calculate appropriate dimensions matching asciiArtToSvg calculation
|
|
1180
1202
|
const lines = machinaArt.split('\n');
|
|
1181
1203
|
const maxLineLength = Math.max(...lines.map(l => l.length));
|
|
1182
1204
|
const charWidth = fontSize * 0.65;
|
|
@@ -1195,5 +1217,5 @@ async function getMachinaAsciiArtSixel(options = {}) {
|
|
|
1195
1217
|
* Generated bundle index. Do not edit.
|
|
1196
1218
|
*/
|
|
1197
1219
|
|
|
1198
|
-
export { GitHubLinkProvider, GitHubLinksAddon,
|
|
1220
|
+
export { CANONICAL_FONT_FAMILY, CANONICAL_FONT_SIZE_PX, CANONICAL_LINE_HEIGHT, DISPLAY_THEME, GitHubLinkProvider, GitHubLinksAddon, INTERACTIVE_THEME, MAX_VIEWPORT_COLS, MAX_VIEWPORT_ROWS, MIN_VIEWPORT_COLS, MIN_VIEWPORT_ROWS, ProxyLinkProvider, ProxyLinksAddon, WebLinksAddon, XTERM_SESSION_CONTEXT, XtermActionId, XtermOrganism, XtermOrganismStore, appendUrlHandoff, asciiArtToSvg, clearCanvasCellMetricCache, computeFitToViewport, deployVersionToHostSuffix, encodeUrlHandoff, generateAsciiArt, getCanvasCellMetric, getMachinaAsciiArtSixel, measureCellMetric, openWebProxyForPort, recordCanvasCellMetric, svgToSixel, toProxyUrl };
|
|
1199
1221
|
//# sourceMappingURL=xxmachina-components-organisms-xterm.mjs.map
|