html: upgrade to gts 4.0.0

This commit is contained in:
Shuanglei Tao
2022-11-01 11:36:32 +08:00
parent d4dc1150f0
commit 4cab29d470
16 changed files with 15189 additions and 14988 deletions

View File

@@ -6,5 +6,4 @@ updates:
interval: daily
open-pull-requests-limit: 10
ignore:
- dependency-name: gts
- dependency-name: webpack-dev-server

1
html/.eslintignore Normal file
View File

@@ -0,0 +1 @@
dist/

11
html/.eslintrc.json Normal file
View File

@@ -0,0 +1,11 @@
{
"extends": "./node_modules/gts/",
"overrides": [
{
"files": ["gulpfile.js", "webpack.config.js"],
"rules": {
"node/no-unpublished-require": "off"
}
}
]
}

6
html/.prettierrc.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
...require('gts/.prettierrc.json'),
"bracketSpacing": true,
"tabWidth": 4,
"printWidth": 120,
}

View File

@@ -1,31 +1,31 @@
const { src, dest, task, series } = require("gulp");
const { src, dest, task, series } = require('gulp');
const clean = require('gulp-clean');
const gzip = require('gulp-gzip');
const inlineSource = require('gulp-inline-source');
const rename = require("gulp-rename");
const rename = require('gulp-rename');
const through2 = require('through2');
const genHeader = (size, buf, len) => {
let idx = 0;
let data = "unsigned char index_html[] = {\n ";
let data = 'unsigned char index_html[] = {\n ';
for (const value of buf) {
idx++;
let current = value < 0 ? value + 256 : value;
const current = value < 0 ? value + 256 : value;
data += "0x";
data += '0x';
data += (current >>> 4).toString(16);
data += (current & 0xF).toString(16);
data += (current & 0xf).toString(16);
if (idx === len) {
data += "\n";
data += '\n';
} else {
data += idx % 12 === 0 ? ",\n " : ", ";
data += idx % 12 === 0 ? ',\n ' : ', ';
}
}
data += "};\n";
data += '};\n';
data += `unsigned int index_html_len = ${len};\n`;
data += `unsigned int index_html_size = ${size};\n`;
return data;
@@ -33,29 +33,32 @@ const genHeader = (size, buf, len) => {
let fileSize = 0;
task('clean', () => {
return src('dist', { read: false, allowEmpty: true })
.pipe(clean());
return src('dist', { read: false, allowEmpty: true }).pipe(clean());
});
task('inline', () => {
return src('dist/index.html')
.pipe(inlineSource())
.pipe(rename("inline.html"))
.pipe(dest('dist/'));
return src('dist/index.html').pipe(inlineSource()).pipe(rename('inline.html')).pipe(dest('dist/'));
});
task('default', series('inline', () => {
return src('dist/inline.html')
.pipe(through2.obj((file, enc, cb) => {
fileSize = file.contents.length;
return cb(null, file);
}))
.pipe(gzip())
.pipe(through2.obj((file, enc, cb) => {
const buf = file.contents;
file.contents = Buffer.from(genHeader(fileSize, buf, buf.length));
return cb(null, file);
}))
.pipe(rename("html.h"))
.pipe(dest('../src/'));
}));
task(
'default',
series('inline', () => {
return src('dist/inline.html')
.pipe(
through2.obj((file, enc, cb) => {
fileSize = file.contents.length;
return cb(null, file);
})
)
.pipe(gzip())
.pipe(
through2.obj((file, enc, cb) => {
const buf = file.contents;
file.contents = Buffer.from(genHeader(fileSize, buf, buf.length));
return cb(null, file);
})
)
.pipe(rename('html.h'))
.pipe(dest('../src/'));
})
);

View File

@@ -17,11 +17,15 @@
"check": "gts check",
"fix": "gts fix"
},
"engines": {
"node": ">=12"
},
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.2.2",
"gts": "^1.1.2",
"eslint-webpack-plugin": "^3.2.0",
"gts": "^4.0.0",
"gulp": "^4.0.2",
"gulp-clean": "^0.4.0",
"gulp-gzip": "^1.4.2",
@@ -36,8 +40,6 @@
"terser-webpack-plugin": "^5.3.6",
"through2": "^4.0.2",
"ts-loader": "^9.4.1",
"tslint": "^6.1.3",
"tslint-loader": "^3.5.4",
"typescript": "^4.8.4",
"util": "^0.12.5",
"webpack": "^5.74.0",

View File

@@ -1,6 +0,0 @@
module.exports = {
trailingComma: "es5",
tabWidth: 4,
printWidth: 120,
singleQuote: true,
};

View File

@@ -4,12 +4,11 @@ import { ITerminalOptions, ITheme } from 'xterm';
import { ClientOptions, FlowControl, Xterm } from './terminal';
if ((module as any).hot) {
// tslint:disable-next-line:no-var-requires
require('preact/debug');
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const path = window.location.pathname.replace(/[\/]+$/, '');
const path = window.location.pathname.replace(/[/]+$/, '');
const wsUrl = [protocol, '//', window.location.host, path, '/ws', window.location.search].join('');
const tokenUrl = [window.location.protocol, '//', window.location.host, path, '/token'].join('');
const clientOptions = {
@@ -17,6 +16,7 @@ const clientOptions = {
disableLeaveAlert: false,
disableResizeOverlay: false,
enableZmodem: false,
enableTrzsz: false,
enableSixel: false,
titleFixed: null,
} as ClientOptions;

View File

@@ -44,7 +44,7 @@ export interface ClientOptions {
enableZmodem: boolean;
enableTrzsz: boolean;
enableSixel: boolean;
titleFixed: string;
titleFixed: string | null;
}
type Options = ITerminalOptions & ClientOptions;
@@ -70,21 +70,21 @@ interface State {
}
export class Xterm extends Component<Props, State> {
private textEncoder: TextEncoder;
private textDecoder: TextDecoder;
private textEncoder = new TextEncoder();
private textDecoder = new TextDecoder();
private container: HTMLElement;
private terminal: Terminal;
private written = 0;
private pending = 0;
private fitAddon: FitAddon;
private overlayAddon: OverlayAddon;
private webglAddon: WebglAddon;
private canvasAddon: CanvasAddon;
private fitAddon = new FitAddon();
private overlayAddon = new OverlayAddon();
private webglAddon?: WebglAddon;
private canvasAddon?: CanvasAddon;
private socket: WebSocket;
private writeFunc: (data: ArrayBuffer) => void;
private writeFunc = (data: ArrayBuffer) => this.writeData(new Uint8Array(data));
private token: string;
private opened = false;
private title: string;
@@ -96,12 +96,6 @@ export class Xterm extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.writeFunc = data => this.writeData(new Uint8Array(data));
this.textEncoder = new TextEncoder();
this.textDecoder = new TextDecoder();
this.fitAddon = new FitAddon();
this.overlayAddon = new OverlayAddon();
}
async componentDidMount() {
@@ -123,7 +117,7 @@ export class Xterm extends Component<Props, State> {
render({ id }: Props, { zmodem, trzsz }: State) {
return (
<div id={id} ref={c => (this.container = c)}>
<div id={id} ref={c => (this.container = c as HTMLElement)}>
{(zmodem || trzsz) && (
<ZmodemAddon
zmodem={zmodem}
@@ -293,9 +287,9 @@ export class Xterm extends Component<Props, State> {
disposeWebglRenderer();
try {
this.terminal.loadAddon(this.canvasAddon);
console.log(`[ttyd] canvas renderer loaded`);
console.log('[ttyd] canvas renderer loaded');
} catch (e) {
console.log(`[ttyd] canvas renderer could not be loaded, falling back to dom renderer`, e);
console.log('[ttyd] canvas renderer could not be loaded, falling back to dom renderer', e);
disposeCanvasRenderer();
}
};
@@ -308,9 +302,9 @@ export class Xterm extends Component<Props, State> {
this.webglAddon?.dispose();
});
terminal.loadAddon(this.webglAddon);
console.log(`[ttyd] WebGL renderer loaded`);
console.log('[ttyd] WebGL renderer loaded');
} catch (e) {
console.log(`[ttyd] WebGL renderer could not be loaded, falling back to canvas renderer`, e);
console.log('[ttyd] WebGL renderer could not be loaded, falling back to canvas renderer', e);
disposeWebglRenderer();
enableCanvasRenderer();
}
@@ -346,13 +340,13 @@ export class Xterm extends Component<Props, State> {
break;
case 'disableResizeOverlay':
if (value) {
console.log(`[ttyd] Resize overlay disabled`);
console.log('[ttyd] Resize overlay disabled');
this.resizeOverlay = false;
}
break;
case 'disableReconnect':
if (value) {
console.log(`[ttyd] Reconnect disabled`);
console.log('[ttyd] Reconnect disabled');
this.reconnect = false;
this.doReconnect = false;
}
@@ -360,13 +354,13 @@ export class Xterm extends Component<Props, State> {
case 'enableZmodem':
if (value) {
this.setState({ zmodem: true });
console.log(`[ttyd] Zmodem enabled`);
console.log('[ttyd] Zmodem enabled');
}
break;
case 'enableTrzsz':
if (value) {
this.setState({ trzsz: true });
console.log(`[ttyd] trzsz enabled`);
console.log('[ttyd] trzsz enabled');
}
break;
case 'enableSixel':
@@ -375,7 +369,7 @@ export class Xterm extends Component<Props, State> {
new Blob([worker], { type: 'text/javascript' })
);
terminal.loadAddon(new ImageAddon(imageWorkerUrl));
console.log(`[ttyd] Sixel enabled`);
console.log('[ttyd] Sixel enabled');
}
break;
case 'titleFixed':
@@ -401,21 +395,19 @@ export class Xterm extends Component<Props, State> {
private onSocketOpen() {
console.log('[ttyd] websocket connection opened');
const { socket, textEncoder, terminal, fitAddon, overlayAddon } = this;
const dims = fitAddon.proposeDimensions();
const { socket, textEncoder, terminal, overlayAddon } = this;
socket.send(
textEncoder.encode(
JSON.stringify({
AuthToken: this.token,
columns: dims.cols,
rows: dims.rows,
columns: terminal.cols,
rows: terminal.rows,
})
)
);
if (this.opened) {
terminal.reset();
terminal.resize(dims.cols, dims.rows);
terminal.options.disableStdin = false;
overlayAddon.showOverlay('Reconnected', 300);
} else {
@@ -432,12 +424,12 @@ export class Xterm extends Component<Props, State> {
console.log(`[ttyd] websocket connection closed with code: ${event.code}`);
const { refreshToken, connect, doReconnect, overlayAddon } = this;
overlayAddon.showOverlay('Connection Closed', null);
overlayAddon.showOverlay('Connection Closed');
this.setState({ zmodem: false, trzsz: false });
// 1000: CLOSE_NORMAL
if (event.code !== 1000 && doReconnect) {
overlayAddon.showOverlay('Reconnecting...', null);
overlayAddon.showOverlay('Reconnecting...');
refreshToken().then(connect);
} else {
const { terminal } = this;
@@ -445,11 +437,11 @@ export class Xterm extends Component<Props, State> {
const event = e.domEvent;
if (event.key === 'Enter') {
keyDispose.dispose();
overlayAddon.showOverlay('Reconnecting...', null);
overlayAddon.showOverlay('Reconnecting...');
refreshToken().then(connect);
}
});
overlayAddon.showOverlay('Press ⏎ to Reconnect', null);
overlayAddon.showOverlay('Press ⏎ to Reconnect');
}
}
@@ -475,8 +467,10 @@ export class Xterm extends Component<Props, State> {
document.title = this.title;
break;
case Command.SET_PREFERENCES:
const prefs = JSON.parse(textDecoder.decode(data));
this.applyOptions({ ...this.props.clientOptions, ...prefs } as Options);
this.applyOptions({
...this.props.clientOptions,
...JSON.parse(textDecoder.decode(data)),
} as Options);
break;
default:
console.warn(`[ttyd] unknown command: ${cmd}`);
@@ -494,7 +488,7 @@ export class Xterm extends Component<Props, State> {
if (resizeOverlay) {
setTimeout(() => {
overlayAddon.showOverlay(`${size.cols}x${size.rows}`);
overlayAddon.showOverlay(`${size.cols}x${size.rows}`, 300);
}, 500);
}
}

View File

@@ -1,11 +1,12 @@
// ported from hterm.Terminal.prototype.showOverlay
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js
import { bind } from 'decko';
import { ITerminalAddon, Terminal } from 'xterm';
export class OverlayAddon implements ITerminalAddon {
private terminal: Terminal | undefined;
private overlayNode: HTMLElement | null;
private overlayTimeout: number | null;
private terminal: Terminal;
private overlayNode: HTMLElement;
private overlayTimeout?: number;
constructor() {
this.overlayNode = document.createElement('div');
@@ -35,8 +36,10 @@ position: absolute;
dispose(): void {}
@bind
showOverlay(msg: string, timeout?: number): void {
const { terminal, overlayNode } = this;
if (!terminal.element) return;
overlayNode.style.color = '#101010';
overlayNode.style.backgroundColor = '#f0f0f0';
@@ -53,21 +56,16 @@ position: absolute;
overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + 'px';
overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
if (this.overlayTimeout) {
clearTimeout(this.overlayTimeout);
}
if (timeout === null) {
return;
}
if (this.overlayTimeout) clearTimeout(this.overlayTimeout);
if (!timeout) return;
const self = this;
self.overlayTimeout = setTimeout(() => {
this.overlayTimeout = setTimeout(() => {
overlayNode.style.opacity = '0';
self.overlayTimeout = setTimeout(() => {
this.overlayTimeout = setTimeout(() => {
if (overlayNode.parentNode) {
overlayNode.parentNode.removeChild(overlayNode);
}
self.overlayTimeout = null;
this.overlayTimeout = undefined;
overlayNode.style.opacity = '0.75';
}, 200) as any;
}, timeout || 1500) as any;

View File

@@ -20,7 +20,7 @@ interface State {
}
export class ZmodemAddon extends Component<Props, State> implements ITerminalAddon {
private terminal: Terminal | undefined;
private terminal: Terminal;
private disposables: IDisposable[] = [];
private sentry: Zmodem.Sentry;
private session: Zmodem.Session;
@@ -81,7 +81,7 @@ export class ZmodemAddon extends Component<Props, State> implements ITerminalAdd
}
@bind
private handleError(e: Error, reason: string) {
private handleError(e: any, reason: string) {
console.error(`[ttyd] zmodem ${reason}: `, e);
this.reset();
}
@@ -115,12 +115,14 @@ export class ZmodemAddon extends Component<Props, State> implements ITerminalAdd
on_retract: () => reset(),
on_detect: detection => zmodemDetect(detection),
});
this.disposables.push(terminal.onKey(e => {
const event = e.domEvent;
if (event.ctrlKey && event.key === 'c') {
if (this.denier) this.denier();
}
}));
this.disposables.push(
terminal.onKey(e => {
const event = e.domEvent;
if (event.ctrlKey && event.key === 'c') {
if (this.denier) this.denier();
}
})
);
}
@bind
@@ -144,7 +146,7 @@ export class ZmodemAddon extends Component<Props, State> implements ITerminalAdd
this.setState({ modal: false });
const { session, writeProgress, handleError } = this;
const files: FileList = (event.target as HTMLInputElement).files;
const files = (event.target as HTMLInputElement).files;
Zmodem.Browser.send_files(session, files, {
on_progress: (_, offer) => writeProgress(offer),
@@ -158,15 +160,11 @@ export class ZmodemAddon extends Component<Props, State> implements ITerminalAdd
const { session, writeProgress, handleError } = this;
session.on('offer', offer => {
const fileBuffer = [];
offer.on('input', payload => {
writeProgress(offer);
fileBuffer.push(new Uint8Array(payload));
});
offer.on('input', () => writeProgress(offer));
offer
.accept()
.then(() => {
const blob = new Blob(fileBuffer, { type: 'application/octet-stream' });
.then(payloads => {
const blob = new Blob(payloads, { type: 'application/octet-stream' });
saveAs(blob, offer.get_details().name);
})
.catch(e => handleError(e, 'receive'));

View File

@@ -1,20 +1,15 @@
{
"extends": "./node_modules/gts/tsconfig-google.json",
"compilerOptions": {
"target": "es2015",
"module": "es2015",
"lib": [
"es2015",
"dom"
],
"allowJs": true,
"jsx": "react",
"jsxFactory": "h",
"sourceMap": true,
"moduleResolution": "node",
"esModuleInterop": true,
"jsx": "react",
"jsxFactory": "h",
"allowJs": true,
"noImplicitAny": false,
"declaration": false,
"experimentalDecorators": true,
"noImplicitReturns": true,
"noUnusedParameters": true
"strictPropertyInitialization": false,
},
"include": [
"src/**/*.tsx",

View File

@@ -1,7 +0,0 @@
{
"extends": "gts/tslint.json",
"rules": {
"deprecation": false,
"no-any": false
}
}

View File

@@ -1,8 +1,9 @@
const path = require('path');
const { merge } = require('webpack-merge');
const ESLintPlugin = require('eslint-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
@@ -11,7 +12,7 @@ const devMode = process.env.NODE_ENV !== 'production';
const baseConfig = {
context: path.resolve(__dirname, 'src'),
entry: {
app: './index.tsx'
app: './index.tsx',
},
output: {
path: path.resolve(__dirname, 'dist'),
@@ -19,23 +20,14 @@ const baseConfig = {
},
module: {
rules: [
{
test: /\.ts$/,
enforce: 'pre',
use: 'tslint-loader',
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
exclude: /node_modules/,
},
{
test: /\.s?[ac]ss$/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
],
use: [devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
{
test: /xterm-addon-image-worker/,
@@ -43,19 +35,21 @@ const baseConfig = {
generator: {
dataUrl: content => {
return content.toString();
}
}
},
},
},
]
],
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ]
extensions: ['.tsx', '.ts', '.js'],
},
plugins: [
new ESLintPlugin({
context: path.resolve(__dirname, '.'),
extensions: ['js', 'jsx', 'ts', 'tsx'],
}),
new CopyWebpackPlugin({
patterns:[
{ from: './favicon.png', to: '.' }
],
patterns: [{ from: './favicon.png', to: '.' }],
}),
new MiniCssExtractPlugin({
filename: devMode ? '[name].css' : '[name].[contenthash].css',
@@ -68,25 +62,27 @@ const baseConfig = {
collapseWhitespace: true,
},
title: 'ttyd - Terminal',
template: './template.html'
})
template: './template.html',
}),
],
performance : {
hints : false
performance: {
hints: false,
},
};
const devConfig = {
const devConfig = {
mode: 'development',
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
proxy: [{
context: ['/token', '/ws'],
target: 'http://localhost:7681',
ws: true
}]
proxy: [
{
context: ['/token', '/ws'],
target: 'http://localhost:7681',
ws: true,
},
],
},
devtool: 'inline-source-map',
};
@@ -94,13 +90,9 @@ const devConfig = {
const prodConfig = {
mode: 'production',
optimization: {
minimizer: [
new TerserPlugin(),
new CssMinimizerPlugin(),
]
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
},
devtool: 'source-map',
};
module.exports = merge(baseConfig, devMode ? devConfig : prodConfig);

File diff suppressed because it is too large Load Diff

28283
src/html.h generated

File diff suppressed because it is too large Load Diff