diff --git a/.gitignore b/.gitignore index 2f84708f..d87ce222 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,4 @@ webshare/dist-dev/ temp.sql .idea/ test/ - +.vscode/ diff --git a/BUILD.md b/BUILD.md index 6adbf821..9d8866a8 100644 --- a/BUILD.md +++ b/BUILD.md @@ -57,27 +57,23 @@ scripthaus run electron-rebuild ## Running WebPack -Two webpacks are required to run the client. One webpack will build the React code that runs inside of Electron. The other webpack builds the node.js code for the Electron App Wrapper. +We use webpack to build both the React and Electron App Wrapper code. They are both run together using: -For the React code: ``` scripthaus run webpack-watch ``` -For the Electron App: -``` -scripthaus run webpack-electron-watch -``` +## Running the WaveTerm Dev Client -## Running the Prompt Dev Client - -Now that webpack is running (and watching for file changes) we can finally run the Prompt Dev Client! To start the client run: +Now that webpack is running (and watching for file changes) we can finally run the WaveTerm Dev Client! To start the client run: ``` scripthaus run electron ``` To kill the client, either exit the Electron App normally or just Ctrl-C the ```scripthaus run electron``` command. -Because we're running webpack in watch mode, any changes you make to the typescript will be automatically picked up by the client after a refresh. Note that I've disabled hot-reloading in the webpack config, so to pick up new changes you'll have to manually refresh the Prompt Client window. To do that use "Command-Shift-R" (Command-R is used internally by Prompt and will not force a refresh). +Because we're running webpack in watch mode, any changes you make to the typescript will be automatically picked up by the client after a refresh. Note that I've disabled hot-reloading in the webpack config, so to pick up new changes you'll have to manually refresh the WaveTerm Client window. To do that use "Command-Shift-R" (Command-R is used internally by WaveTerm and will not force a refresh). +## Debugging the Dev Client +You can use the regular Chrome DevTools to debug the frontend application. You can open the DevTools using the keyboard shortcut `Cmd-Option-I`. diff --git a/build-linux.md b/build-linux.md index 9d9c8c05..19b6bbeb 100644 --- a/build-linux.md +++ b/build-linux.md @@ -81,27 +81,23 @@ scripthaus run electron-rebuild ## Running WebPack -Two webpacks are required to run the client. One webpack will build the React code that runs inside of Electron. The other webpack builds the node.js code for the Electron App Wrapper. +We use webpack to build both the React and Electron App Wrapper code. They are both run together using: -For the React code: ``` scripthaus run webpack-watch ``` -For the Electron App: -``` -scripthaus run webpack-electron-watch -``` +## Running the WaveTerm Dev Client -## Running the Prompt Dev Client - -Now that webpack is running (and watching for file changes) we can finally run the Prompt Dev Client! To start the client run: +Now that webpack is running (and watching for file changes) we can finally run the WaveTerm Dev Client! To start the client run: ``` scripthaus run electron ``` To kill the client, either exit the Electron App normally or just Ctrl-C the ```scripthaus run electron``` command. -Because we're running webpack in watch mode, any changes you make to the typescript will be automatically picked up by the client after a refresh. Note that I've disabled hot-reloading in the webpack config, so to pick up new changes you'll have to manually refresh the Prompt Client window. To do that use "Command-Shift-R" (Command-R is used internally by Prompt and will not force a refresh). +Because we're running webpack in watch mode, any changes you make to the typescript will be automatically picked up by the client after a refresh. Note that I've disabled hot-reloading in the webpack config, so to pick up new changes you'll have to manually refresh the WaveTerm Client window. To do that use "Command-Shift-R" (Command-R is used internally by Wave and will not force a refresh). +## Debugging the Dev Client +You can use the regular Chrome DevTools to debug the frontend application. You can open the DevTools using the keyboard shortcut `Cmd-Option-I`. diff --git a/forge.config.js b/forge.config.js index 83b60401..09f7183e 100644 --- a/forge.config.js +++ b/forge.config.js @@ -55,7 +55,7 @@ module.exports = { "node_modules/fs-ext/**", "node_modules/fsevents/**", ], - icon: "public/Prompt.icns", + icon: "public/waveterm.icns", osxNotarize: { tool: "notarytool", keychainProfile: "notarytool-creds", diff --git a/go.work b/go.work new file mode 100644 index 00000000..85c99458 --- /dev/null +++ b/go.work @@ -0,0 +1,4 @@ +go 1.18 + +use ./wavesrv +use ./waveshell \ No newline at end of file diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 00000000..f356d042 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,6 @@ +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= diff --git a/housekeeping/waveterminal.iconset.zip b/housekeeping/waveterminal.iconset.zip new file mode 100644 index 00000000..8544e9b3 Binary files /dev/null and b/housekeeping/waveterminal.iconset.zip differ diff --git a/package-lock.json b/package-lock.json index ed7a356c..72d37f2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "Prompt", - "version": "0.3.1", + "name": "waveterm", + "version": "0.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2289,6 +2289,15 @@ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2306,6 +2315,40 @@ "strip-ansi": "^7.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -2315,6 +2358,23 @@ "ansi-regex": "^6.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + } + } + }, "wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -2325,6 +2385,60 @@ "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } } } }, @@ -4846,25 +4960,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, - "electron": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.4.0.tgz", - "integrity": "sha512-VLTRxDhL4UvQbqM7pTNENnJo62cdAPZT92N+B7BZQ5Xfok1wuVPEewIjBot4K7U3EpLUuHn1veeLzho3ihiP+Q==", - "dev": true, - "requires": { - "@electron/get": "^2.0.0", - "@types/node": "^18.11.18", - "extract-zip": "^2.0.1" - }, - "dependencies": { - "@types/node": { - "version": "18.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", - "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==", - "dev": true - } - } - }, "electron-installer-common": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz", @@ -8897,6 +8992,53 @@ } } }, + "raw-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, "rcedit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-3.1.0.tgz", @@ -9897,17 +10039,6 @@ "strip-ansi": "^6.0.1" } }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -9925,15 +10056,6 @@ "ansi-regex": "^5.0.1" } }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -11050,17 +11172,6 @@ "strip-ansi": "^6.0.0" } }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 9c4f590a..89c8fd68 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { - "name": "Prompt", - "version": "0.4.0", + "name": "waveterm", + "productName": "Wave", + "version": "0.5.0", "main": "dist/emain.js", "license": "Apache-2.0", "dependencies": { "@monaco-editor/react": "^4.5.1", + "@table-nav/core": "^0.0.7", + "@table-nav/react": "^0.0.7", "@tanstack/match-sorter-utils": "^8.8.4", "@tanstack/react-table": "^8.10.3", "autobind-decorator": "^2.4.0", @@ -56,6 +59,7 @@ "@types/papaparse": "^5.3.9", "@types/react": "^18.0.12", "@types/uuid": "9.0.0", + "@types/webpack-env": "^1.18.3", "babel-loader": "^9.1.3", "babel-plugin-jsx-control-statements": "^4.1.2", "copy-webpack-plugin": "^11.0.0", @@ -69,6 +73,7 @@ "lodash-webpack-plugin": "^0.11.6", "mini-css-extract-plugin": "^2.6.0", "prettier": "^2.8.8", + "raw-loader": "^4.0.2", "react-split-it": "^2.0.0", "style-loader": "^3.3.1", "typescript": "^4.7.3", diff --git a/public/index-dev.html b/public/index-dev.html index 1c7b8d87..71496a21 100644 --- a/public/index-dev.html +++ b/public/index-dev.html @@ -3,9 +3,9 @@ - + - +
diff --git a/public/index.html b/public/index.html index 9c5a0f76..66891547 100644 --- a/public/index.html +++ b/public/index.html @@ -3,9 +3,9 @@ - + - +
diff --git a/public/waveterm.icns b/public/waveterm.icns new file mode 100644 index 00000000..79cd56e6 Binary files /dev/null and b/public/waveterm.icns differ diff --git a/scripthaus.md b/scripthaus.md index c62c0061..c3716788 100644 --- a/scripthaus.md +++ b/scripthaus.md @@ -1,27 +1,21 @@ -# Prompt Commands +# WaveTerm Commands ```bash # @scripthaus command webpack-watch # @scripthaus cd :playbook -node_modules/.bin/webpack --watch --config webpack.dev.js +node_modules/.bin/webpack --env dev --watch ``` ```bash # @scripthaus command webpack-build # @scripthaus cd :playbook -node_modules/.bin/webpack --config webpack.dev.js +node_modules/.bin/webpack --env dev ``` ```bash -# @scripthaus command webpack-electron-watch +# @scripthaus command webpack-build-prod # @scripthaus cd :playbook -node_modules/.bin/webpack --watch --config webpack.electron.js -``` - -```bash -# @scripthaus command webpack-electron-build -# @scripthaus cd :playbook -node_modules/.bin/webpack --config webpack.electron.js +node_modules/.bin/webpack --env prod ``` ```bash @@ -36,40 +30,10 @@ node_modules/.bin/electron-rebuild PROMPT_DEV=1 PCLOUD_ENDPOINT="https://ot2e112zx5.execute-api.us-west-2.amazonaws.com/dev" node_modules/.bin/electron dist-dev/emain.js ``` -```bash -# @scripthaus command devserver -# @scripthaus cd :playbook -node_modules/.bin/webpack-dev-server --config webpack.dev.js --host 0.0.0.0 -``` - -```bash -# @scripthaus command webshare-devserver -# @scripthaus cd :playbook -node_modules/.bin/webpack-dev-server --config webpack.share.dev.js --host 127.0.0.1 -``` - -```bash -# @scripthaus command webshare-build -# @scripthaus cd :playbook -node_modules/.bin/webpack --config webpack.share.dev.js -``` - -```bash -# @scripthaus command webshare-build-prod -# @scripthaus cd :playbook -node_modules/.bin/webpack --config webpack.share.prod.js -``` - ```bash # @scripthaus command typecheck # @scripthaus cd :playbook -node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/index.ts -``` - -```bash -# @scripthaus command typecheck-webshare -# @scripthaus cd :playbook -node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/webshare.ts +node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/index.ts src/types/custom.d.ts ``` ```bash @@ -78,8 +42,7 @@ node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --e rm -rf dist/ rm -rf bin/ rm -rf build/ -node_modules/.bin/webpack --config webpack.prod.js -node_modules/.bin/webpack --config webpack.electron.prod.js +node_modules/.bin/webpack --env prod GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')" (cd waveshell; GOOS=darwin GOARCH=amd64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.3-darwin.amd64 main-waveshell.go) (cd waveshell; GOOS=darwin GOARCH=arm64 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-v0.3-darwin.arm64 main-waveshell.go) @@ -111,43 +74,43 @@ node_modules/.bin/electron-forge make ```bash # @scripthaus command open-electron-package # @scripthaus cd :playbook -open out/Prompt-darwin-x64/Prompt.app +open out/Wave-darwin-x64/Wave.app ``` ```bash # @scripthaus command create-dmg # @scripthaus cd :playbook DMG_VERSION=$(node -e 'console.log(require("./version.js"))') -DMG_NAME="prompt-macos-x86-${DMG_VERSION}.dmg" +DMG_NAME="waveterm-macos-x86-${DMG_VERSION}.dmg" rm *.dmg /Users/mike/work/gopath/src/github.com/create-dmg/create-dmg/create-dmg \ - --volname "Prompt" \ + --volname "WaveTerm" \ --window-pos 200 120 \ --window-size 600 300 \ --icon-size 100 \ - --icon "Prompt.app" 200 130 \ - --hide-extension "Prompt.app" \ + --icon "Wave.app" 200 130 \ + --hide-extension "Wave.app" \ --app-drop-link 400 125 \ $DMG_NAME \ - "out/Prompt-darwin-x64/Prompt.app" + "out/Wave-darwin-x64/Wave.app" ``` ```bash # @scripthaus command create-dmg-m1 # @scripthaus cd :playbook DMG_VERSION=$(node -e 'console.log(require("./version.js"))') -DMG_NAME="prompt-macos-arm64-${DMG_VERSION}.dmg" +DMG_NAME="waveterm-macos-arm64-${DMG_VERSION}.dmg" rm *.dmg -../../create-dmg/create-dmg/create-dmg \ - --volname "Prompt" \ +/Users/sawka/work/gopath/src/github.com/create-dmg/create-dmg/create-dmg \ + --volname "WaveTerm" \ --window-pos 200 120 \ --window-size 600 300 \ --icon-size 100 \ - --icon "Prompt.app" 200 130 \ - --hide-extension "Prompt.app" \ + --icon "Wave.app" 200 130 \ + --hide-extension "Wave.app" \ --app-drop-link 400 125 \ $DMG_NAME \ - "out/Prompt-darwin-arm64/Prompt.app" + "out/Wave-darwin-arm64/Wave.app" ``` ```bash @@ -181,6 +144,7 @@ go build -ldflags="$GO_LDFLAGS" -o bin/mshell-v0.3-darwin.amd64 main-waveshell.g ```bash # @scripthaus command fullbuild-waveshell +set -e cd waveshell GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')" go build -ldflags="$GO_LDFLAGS" -o ~/.mshell/mshell-v0.2 main-waveshell.go diff --git a/src/app/app.less b/src/app/app.less index ad94b9a4..e9e9cfcd 100644 --- a/src/app/app.less +++ b/src/app/app.less @@ -36,6 +36,50 @@ textarea { color: @base-color; } +// typography +.text-s1 { + font-size: 12.5px; + font-weight: 300; + line-height: 20px; + font-family: @text-s1-font; + + .icon { + width: 16px; + height: 16px; + } +} + +.text-standard { + font-size: 12.5px; + font-weight: 300; + line-height: 20px; + font-family: @text-s1-font; + + .icon { + width: 16px; + height: 16px; + } +} + +.text-secondary { + font-family: @fixed-font; + font-size: 11px; + font-weight: 300; + line-height: 16px; +} + +.text-caption { + font-family: @fixed-font; + font-size: 10px; + font-weight: 300; + line-height: 14px; +} + +.text-s1.icon { + width: 16px; + height: 16px; +} + body code { font-family: @terminal-font; } @@ -58,7 +102,7 @@ svg.icon { .hover-effect-base:hover { cursor: pointer; - + .hover-effect-target { box-shadow: 0px 2px 2px 0 rgba(255, 255, 255, 0.1), 0px 4px 5px 0 rgba(255, 255, 255, 0.2), 0px 0px 5px 2.5px rgba(255, 255, 255, 0.5); @@ -179,7 +223,8 @@ a.a-block { height: 100%; .history-view, - .bookmarks-view { + .bookmarks-view, + .plugins-view { flex-grow: 1; display: flex; flex-direction: column; @@ -373,6 +418,15 @@ a.a-block { } } +.load-error-text { + color: @term-red; + padding-top: 5px; +} + +.view-error { + padding: 10px 20px; +} + @keyframes loader-ring { 0% { transform: rotate(0deg); @@ -409,3 +463,87 @@ a.a-block { opacity: 0; } } + +.icon.color-red { + path, circle { + fill: @tab-red; + } +} + +.icon.color-green { + path, circle { + fill: @tab-green; + } +} + +.icon.color-orange { + path, circle { + fill: @tab-orange; + } +} + +.icon.color-blue { + path, circle { + fill: @tab-blue; + } +} + +.icon.color-yellow { + path, circle { + fill: @tab-yellow; + } +} + +.icon.color-pink { + path, circle { + fill: @tab-pink; + } +} + +.icon.color-magenta { + path, circle { + fill: @tab-magenta; + } +} + +.icon.color-cyan { + path, circle { + fill: @tab-cyan; + } +} + +.icon.color-violet { + path, circle { + fill: @tab-violet; + } +} + +.icon.color-white { + path, circle { + fill: @tab-white; + } +} + +.status-icon.status-connected { + path, circle { + fill: @status-connected; + } +} + +.status-icon.status-connecting { + path, circle { + fill: @status-connecting; + } +} + +.status-icon.status-disconnected { + path, circle { + fill: @status-disconnected; + } +} + +.status-icon.status-error { + path, circle { + fill: @status-error; + } +} diff --git a/src/app/app.tsx b/src/app/app.tsx index b15daf7a..f60e6da2 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -8,6 +11,8 @@ import type { ContextMenuOpts } from "../types/types"; import localizedFormat from "dayjs/plugin/localizedFormat"; import { GlobalModel } from "../model/model"; import { isBlank } from "../util/util"; +import { WorkspaceView } from "./workspace/workspaceview"; +import { PluginsView } from "./pluginsview/pluginsview"; import { BookmarksView } from "./bookmarks/bookmarks"; import { HistoryView } from "./history/history"; import { @@ -18,9 +23,9 @@ import { } from "./common/modals/settings"; import { RemotesModal } from "./connections/connections"; import { TosModal } from "./common/modals/modals"; -import { WorkspaceView } from "../app/workspace/workspaceview"; import { MainSideBar } from "./sidebar/MainSideBar"; import { DisconnectedModal, ClientStopModal, AlertModal, WelcomeModal } from "./common/modals/modals"; +import { ErrorBoundary } from "./common/error/errorboundary"; import "./app.less"; dayjs.extend(localizedFormat); @@ -103,13 +108,17 @@ class App extends React.Component<{}, {}> { if (dcWait) { setTimeout(() => this.updateDcWait(false), 0); } + //console.log(`GlobalModel.activeMainView.get() = ${GlobalModel.activeMainView.get()}`); // @mike - if I remove this, I cant see plugins return (
- - - + + + + + +
diff --git a/src/app/assets/icons/arrowsupdown.svg b/src/app/assets/icons/arrowsupdown.svg new file mode 100644 index 00000000..3e14648d --- /dev/null +++ b/src/app/assets/icons/arrowsupdown.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/app/assets/icons/check12.svg b/src/app/assets/icons/check12.svg new file mode 100644 index 00000000..bfbe29b3 --- /dev/null +++ b/src/app/assets/icons/check12.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/app/assets/icons/ellipse.svg b/src/app/assets/icons/ellipse.svg new file mode 100644 index 00000000..18886088 --- /dev/null +++ b/src/app/assets/icons/ellipse.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/app/assets/icons/github.svg b/src/app/assets/icons/github.svg new file mode 100644 index 00000000..08d53d2e --- /dev/null +++ b/src/app/assets/icons/github.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/app/assets/icons/globe.svg b/src/app/assets/icons/globe.svg new file mode 100644 index 00000000..75a6930c --- /dev/null +++ b/src/app/assets/icons/globe.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/app/assets/icons/help_filled.svg b/src/app/assets/icons/help_filled.svg new file mode 100644 index 00000000..675a3f2a --- /dev/null +++ b/src/app/assets/icons/help_filled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/app/assets/icons/line/modals.tsx b/src/app/assets/icons/line/modals.tsx new file mode 100644 index 00000000..02bfc364 --- /dev/null +++ b/src/app/assets/icons/line/modals.tsx @@ -0,0 +1,367 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import { boundMethod } from "autobind-decorator"; +import { If, For } from "tsx-control-statements/components"; +import cn from "classnames"; +import dayjs from "dayjs"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import { GlobalModel, GlobalCommandRunner } from "../../../model/model"; +import { Markdown } from "../common"; +import * as util from "../../../util/util"; + +import { ReactComponent as XmarkIcon } from "../../assets/icons/line/xmark.svg"; +import { ReactComponent as WarningIcon } from "../../assets/icons/line/triangle-exclamation.svg"; +import { ReactComponent as ShieldCheck } from "../../assets/icons/line/shield_check.svg"; +import { ReactComponent as Help } from "../../assets/icons/line/help_filled.svg"; +import { ReactComponent as Github } from "../../assets/icons/line/github.svg"; + +dayjs.extend(localizedFormat); + +type OV = mobx.IObservableValue; + +@mobxReact.observer +class DisconnectedModal extends React.Component<{}, {}> { + logRef: any = React.createRef(); + showLog: mobx.IObservableValue = mobx.observable.box(false); + + @boundMethod + restartServer() { + GlobalModel.restartWaveSrv(); + } + + @boundMethod + tryReconnect() { + GlobalModel.ws.connectNow("manual"); + } + + componentDidMount() { + if (this.logRef.current != null) { + this.logRef.current.scrollTop = this.logRef.current.scrollHeight; + } + } + + componentDidUpdate() { + if (this.logRef.current != null) { + this.logRef.current.scrollTop = this.logRef.current.scrollHeight; + } + } + + @boundMethod + handleShowLog(): void { + mobx.action(() => { + this.showLog.set(!this.showLog.get()); + })(); + } + + render() { + let model = GlobalModel; + let logLine: string = null; + let idx: number = 0; + return ( +
+
+
+
+
Prompt Client Disconnected
+
+ +
+
+ +
+ {logLine} +
+
+
+
+
+
+
+ + Show Log + + + Hide Log + +
+
+ + +
+
+
+ ); + } +} + +@mobxReact.observer +class ClientStopModal extends React.Component<{}, {}> { + @boundMethod + refreshClient() { + GlobalModel.refreshClient(); + } + + render() { + let model = GlobalModel; + let cdata = model.clientData.get(); + let title = "Client Not Ready"; + return ( +
+
+
+
+
[prompt] {title}
+
+
+ +
Cannot get client data.
+
+
+
+ +
+
+
+ ); + } +} + +@mobxReact.observer +class LoadingSpinner extends React.Component<{}, {}> { + render() { + return ( +
+
+
+
+
+
+ ); + } +} + +@mobxReact.observer +class AlertModal extends React.Component<{}, {}> { + @boundMethod + closeModal(): void { + GlobalModel.cancelAlert(); + } + + @boundMethod + handleOK(): void { + GlobalModel.confirmAlert(); + } + + render() { + let message = GlobalModel.alertMessage.get(); + if (message == null) { + return null; + } + let title = message.title ?? (message.confirm ? "Confirm" : "Alert"); + let isConfirm = message.confirm; + return ( +
+
+
+
+

+ + {title} +

+
+ +
+
+ + + + +
+

{message.message}

+
+
+
+ +
+ Cancel +
+
+ OK +
+
+ +
+ OK +
+
+
+
+
+ ); + } +} + +@mobxReact.observer +class WelcomeModal extends React.Component<{}, {}> { + totalPages: number = 3; + pageNum: OV = mobx.observable.box(1, { name: "welcome-pagenum" }); + + @boundMethod + closeModal(): void { + mobx.action(() => { + GlobalModel.welcomeModalOpen.set(false); + })(); + } + + @boundMethod + goNext(): void { + mobx.action(() => { + this.pageNum.set(this.pageNum.get() + 1); + })(); + } + + @boundMethod + goPrev(): void { + mobx.action(() => { + this.pageNum.set(this.pageNum.get() - 1); + })(); + } + + renderDot(num: number): any { + if (num == this.pageNum.get()) { + return ; + } + return ; + } + + renderDots(): any { + let elems: any = []; + for (let i = 1; i <= this.totalPages; i++) { + let elem = this.renderDot(i); + elems.push(elem); + } + return elems; + } + + render() { + let pageNum = this.pageNum.get(); + return ( +
+
+
+
+
welcome to [prompt]
+
+ +
+
+
+

+ Prompt is a new terminal to help save you time and keep your command-line life organized. + Here's a couple quick tips to get your started! +

+
+
+ 1}> + + + +
+ +
+
{this.renderDots()}
+
+ + + + + + +
+
+
+ ); + } +} + +@mobxReact.observer +class TosModal extends React.Component<{}, {}> { + @boundMethod + acceptTos(): void { + GlobalCommandRunner.clientAcceptTos(); + } + + render() { + return ( +
+
+
+
+
+
Welcome to Wave Terminal!
+
Lets set everything for you
+
+
+
+ +
+
Telemetry
+
+
+
+ +
+
+
+ +
+
+ {/*

+ + Full Terms of Service + +

*/} +
+
+
+
+ Accept Terms of Service +
+
+
+
+
+ ); + } +} + +export { WelcomeModal, LoadingSpinner, ClientStopModal, AlertModal, DisconnectedModal, TosModal }; diff --git a/src/app/assets/icons/shield_check.svg b/src/app/assets/icons/shield_check.svg new file mode 100644 index 00000000..62dcb641 --- /dev/null +++ b/src/app/assets/icons/shield_check.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/app/assets/icons/statuscircle.svg b/src/app/assets/icons/statuscircle.svg new file mode 100644 index 00000000..924b09bb --- /dev/null +++ b/src/app/assets/icons/statuscircle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/assets/waveterm-logo.png b/src/app/assets/waveterm-logo.png new file mode 100644 index 00000000..daaf606e Binary files /dev/null and b/src/app/assets/waveterm-logo.png differ diff --git a/src/app/assets/waveterm-logo.svg b/src/app/assets/waveterm-logo.svg new file mode 100644 index 00000000..630fdfa6 --- /dev/null +++ b/src/app/assets/waveterm-logo.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/bookmarks/bookmarks.tsx b/src/app/bookmarks/bookmarks.tsx index 2e423c7a..dd7edaa6 100644 --- a/src/app/bookmarks/bookmarks.tsx +++ b/src/app/bookmarks/bookmarks.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; diff --git a/src/app/common/common.less b/src/app/common/common.less index 6e6bf8b5..5685fe03 100644 --- a/src/app/common/common.less +++ b/src/app/common/common.less @@ -176,6 +176,77 @@ } } +.checkbox { + display: flex; + + input[type="checkbox"] { + height: 0; + width: 0; + } + + input[type="checkbox"] + label { + position: relative; + display: flex; + align-items: center; + color: #9e9e9e; + transition: color 250ms cubic-bezier(0.4, 0, 0.23, 1); + } + input[type="checkbox"] + label > span { + display: flex; + justify-content: center; + align-items: center; + margin-right: 16px; + width: 20px; + height: 20px; + background: transparent; + border: 2px solid #9e9e9e; + border-radius: 2px; + cursor: pointer; + transition: all 250ms cubic-bezier(0.4, 0, 0.23, 1); + } + + input[type="checkbox"] + label:hover, + input[type="checkbox"]:focus + label { + color: #fff; + } + input[type="checkbox"] + label:hover > span, + input[type="checkbox"]:focus + label > span { + background: rgba(255, 255, 255, 0.1); + } + input[type="checkbox"]:checked + label > ins { + height: 100%; + } + + input[type="checkbox"]:checked + label > span { + border: 10px solid @term-green; + } + input[type="checkbox"]:checked + label > span:before { + content: ""; + position: absolute; + top: -2px; + left: 3px; + width: 7px; + height: 12px; + border-right: 2px solid #fff; + border-bottom: 2px solid #fff; + transform: rotate(45deg); + transform-origin: 0% 100%; + animation: checkbox-check 500ms cubic-bezier(0.4, 0, 0.23, 1); + } + + @keyframes checkbox-check { + 0% { + opacity: 0; + } + 33% { + opacity: 0.5; + } + 100% { + opacity: 1; + } + } +} + .button.is-prompt-green { background-color: #222; color: @term-white; @@ -186,6 +257,27 @@ } } +.button.is-wave-green { + margin-top: 16px; + display: flex; + padding: 6px 16px !important; + color: @term-bright-white !important; + align-items: center; + gap: 4px; + border-radius: 6px !important; + font-size: 14px !important; + height: auto !important; + background: @term-green !important; + box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5), + 0px 0px 0.5px 0px rgba(255, 255, 255, 0.8) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.6) inset; + + &:hover { + background-color: @term-green; + color: @term-bright-white !important; + box-shadow: none; + } +} + .button.is-plain, .button.is-prompt-cancel { background-color: #222; diff --git a/src/app/common/common.tsx b/src/app/common/common.tsx index f4b2b3dc..4953bcc6 100644 --- a/src/app/common/common.tsx +++ b/src/app/common/common.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -96,6 +99,32 @@ class Toggle extends React.Component<{ checked: boolean; onChange: (value: boole } } +class Checkbox extends React.Component< + { checked: boolean; onChange: (value: boolean) => void; label: string; id: string }, + {} +> { + render() { + const { checked, onChange, label, id } = this.props; + + return ( +
+ onChange(e.target.checked)} + aria-checked={checked} + role="checkbox" + /> + +
+ ); + } +} + @mobxReact.observer class RemoteStatusLight extends React.Component<{ remote: RemoteType }, {}> { render() { @@ -328,6 +357,7 @@ class SettingsError extends React.Component<{ errorMessage: OV }, {}> { export { CmdStrCode, Toggle, + Checkbox, renderCmdText, RemoteStatusLight, InlineSettingsTextEdit, diff --git a/src/app/common/error/errorboundary.tsx b/src/app/common/error/errorboundary.tsx new file mode 100644 index 00000000..d4d32a7b --- /dev/null +++ b/src/app/common/error/errorboundary.tsx @@ -0,0 +1,65 @@ +import React, { Component, ReactNode } from "react"; +import { RendererContext } from "../../../types/types"; +import cn from "classnames"; + +interface ErrorBoundaryState { + hasError: boolean; + error: Error | null; +} + +interface ErrorBoundaryProps { + children: ReactNode; + plugin?: string; + lineContext?: RendererContext; +} + +class ErrorBoundary extends Component { + state: ErrorBoundaryState = { + hasError: false, + error: null, + }; + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + const { plugin, lineContext } = this.props; + + if (plugin && lineContext) { + console.log(`Plugin Name: ${plugin}\n`); + console.log(`Line Context: \n`); + console.log(`${JSON.stringify(lineContext, null, 4)}\n`); + } + + console.log(error); + } + + resetErrorBoundary = (): void => { + this.setState({ hasError: false, error: null }); + }; + + renderFallback() { + const { error } = this.state; + const { plugin } = this.props; + + return ( +
+
{`${error?.message}`}
+ {plugin &&
An error occurred in the {plugin} plugin
} +
+ ); + } + + render() { + const { hasError } = this.state; + + if (hasError) { + return this.renderFallback(); + } + + return this.props.children; + } +} + +export { ErrorBoundary }; diff --git a/src/app/common/modals/modals.less b/src/app/common/modals/modals.less index 5de14313..5c687214 100644 --- a/src/app/common/modals/modals.less +++ b/src/app/common/modals/modals.less @@ -158,8 +158,6 @@ display: inline; width: 1em; height: 1em; - margin: 0 0.25em 0 1em; - vertical-align: middle; } &.is-hidden { display: none; @@ -183,6 +181,109 @@ } } +.modal.wave-modal { + .modal-content { + display: flex; + padding: 32px 48px; + flex-direction: column; + align-items: flex-start; + gap: 8px; + border-radius: 10px; + background: var(--olive-dark-1, #151715); + box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45), + 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset; + + .modal-content-wrapper { + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; + width: 100%; + + header { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--sizing-sm, 12px); + width: 100%; + + .modal-title { + color: @term-bright-white; + text-align: center; + font-size: 20px; + font-style: normal; + font-weight: 300; + line-height: 20px; + } + + .modal-subtitle { + color: @term-white; + text-align: center; + + font-style: normal; + font-weight: 300; + line-height: 20px; + } + } + + .content { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 32px; + width: 100%; + margin-bottom: 0; + + .item { + display: flex; + width: 100%; + align-items: center; + gap: 16px; + + .item-inner { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + flex: 1 0 0; + + .item-title { + color: @term-bright-white; + font-style: normal; + line-height: 20px; + } + + .item-text { + color: @term-white; + font-style: normal; + line-height: 20px; + } + + .item-field { + display: flex; + align-items: center; + gap: 8px; + } + } + } + } + } + + footer { + .button-wrapper { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + button.disabled-button { + cursor: default; + } + } + } + } +} + .modal.welcome-modal { footer { .prev-button { @@ -343,8 +444,11 @@ .tab-color-icon.color-white path { fill: @tab-white; } - .tab-color-icon.color-black path { - fill: @tab-black; + .tab-color-icon.color-violet path { + fill: @tab-violet; + } + .tab-color-icon.color-pink path { + fill: @tab-pink; } .tab-colors { diff --git a/src/app/common/modals/modals.tsx b/src/app/common/modals/modals.tsx index 7b652f32..5d3427ed 100644 --- a/src/app/common/modals/modals.tsx +++ b/src/app/common/modals/modals.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -9,9 +12,14 @@ import localizedFormat from "dayjs/plugin/localizedFormat"; import { GlobalModel, GlobalCommandRunner } from "../../../model/model"; import { Markdown } from "../common"; import * as util from "../../../util/util"; +import { Toggle, Checkbox } from "../common"; +import { ClientDataType } from "../../../types/types"; import { ReactComponent as XmarkIcon } from "../../assets/icons/line/xmark.svg"; import { ReactComponent as WarningIcon } from "../../assets/icons/line/triangle-exclamation.svg"; +import shield from "../../assets/icons/shield_check.svg"; +import help from "../../assets/icons/help_filled.svg"; +import github from "../../assets/icons/github.svg"; dayjs.extend(localizedFormat); @@ -309,48 +317,112 @@ class WelcomeModal extends React.Component<{}, {}> { @mobxReact.observer class TosModal extends React.Component<{}, {}> { + state = { + isChecked: false, + }; + + @boundMethod + handleCheckboxChange(checked: boolean): void { + this.setState({ isChecked: checked }); + } + @boundMethod acceptTos(): void { GlobalCommandRunner.clientAcceptTos(); } + @boundMethod + handleChangeTelemetry(val: boolean): void { + if (val) { + GlobalCommandRunner.telemetryOn(false); + } else { + GlobalCommandRunner.telemetryOff(false); + } + } + render() { + let cdata: ClientDataType = GlobalModel.clientData.get(); + return ( -
+
-
-
Welcome to [prompt]
-
-
+
+
+
Welcome to Wave Terminal!
+
Lets set everything for you
+
-

Thank you for downloading Prompt!

-

- Prompt is a new terminal designed to help you save time and organize your command life. - Prompt is currently in beta. If you'd like to give feedback, run into problems, have - questions, or need help, please join the Prompt{" "} - - discord server - - . -

-

- Prompt is free to use, no email or registration required (unless you're using the cloud - features). -

-

- - Full Terms of Service - -

+
+ Privacy +
+
Telemetry
+
+ We don’t collect any personal info, only crash logs and IP address to make Wave + better. If you like, you can disable telemetry now or late. +
+
+ +
Basic Telemetry
+
+
+
+
+ Help +
+
Help
+
+ If you need any help or you have feature request, you can join{" "} + + our Discord + + . +
+
+
+
+ Github +
+
Like Wave? Give us a star
+
+ Rankings are very important for small startups like us, it helps other people to + know about us. If you like Wave, please consider giving us a star on our{" "} + + Github Repository + + . +
+
+
+
+
+ +
+
+ +
+
-
-
-
- Accept Terms of Service -
-
); diff --git a/src/app/common/modals/settings.tsx b/src/app/common/modals/settings.tsx index 98d69525..44e1f505 100644 --- a/src/app/common/modals/settings.tsx +++ b/src/app/common/modals/settings.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -7,8 +10,10 @@ import cn from "classnames"; import { GlobalModel, GlobalCommandRunner, TabColors } from "../../../model/model"; import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common"; import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types"; +import { RemotesSelector } from "../../connections/connections"; import { PluginModel } from "../../../plugins/plugins"; import * as util from "../../../util/util"; +import { commandRtnHandler } from "../../../util/util"; import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg"; import { ReactComponent as XmarkIcon } from "../../assets/icons/line/xmark.svg"; import { ReactComponent as AngleDownIcon } from "../../assets/icons/history/angle-down.svg"; @@ -29,9 +34,9 @@ All commands and output will be deleted, and removed from history. To hide the `.trim(); const SessionDeleteMessage = ` -Are you sure you want to delete this session? +Are you sure you want to delete this workspace? -All commands and output will be deleted, and removed from history. To hide the session, and retain the commands in history, use 'archive'. +All commands and output will be deleted, and removed from history. To hide the workspace, and retain the commands in history, use 'archive'. `.trim(); const WebShareConfirmMarkdown = ` @@ -44,19 +49,8 @@ const WebStopShareConfirmMarkdown = ` Are you sure you want to stop web-sharing this screen? `.trim(); -function commandRtnHandler(prtn: Promise, errorMessage: OV) { - prtn.then((crtn) => { - if (crtn.success) { - return; - } - mobx.action(() => { - errorMessage.set(crtn.error); - })(); - }); -} - @mobxReact.observer -class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string }, {}> { +class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string; inline?: boolean }, {}> { shareCopied: OV = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" }); errorMessage: OV = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" }); @@ -201,30 +195,38 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: } render() { - let { sessionId, screenId } = this.props; + let { sessionId, screenId, inline } = this.props; let screen = GlobalModel.getScreenById(sessionId, screenId); if (screen == null) { return null; } let color: string = null; return ( -
-
-
- -
- -
-
screen settings ({screen.name.get()})
-
- -
-
+
+ {!inline &&
} +
+ {this.shareCopied.get() &&
} + {!inline && ( +
+
screen settings ({screen.name.get()})
+
+ +
+
+ )}
-
-
Screen Id
-
{screen.screenId}
-
+ {!inline && ( +
+
Screen Id
+
{screen.screenId}
+
+ )}
Name
@@ -259,40 +261,47 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
-
-
-
Archived
- - Archive will hide the screen tab. Commands and output will be retained in history. - -
-
- -
-
-
-
-
Actions
- - Delete will remove the screen, removing all commands and output from history. - -
-
-
- Delete Screen + {!inline && ( +
+
+
Archived
+ + Archive will hide the screen tab. Commands and output will be retained in + history. + +
+
+
-
+ )} + {!inline && ( +
+
+
Actions
+ + Delete will remove the screen, removing all commands and output from history. + +
+
+
+ Delete Screen +
+
+
+ )}
-
-
- Close -
-
+ {!inline && ( +
+
+ Close +
+
+ )}
); @@ -402,7 +411,7 @@ class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
Archived
- Archive will hide the session from the active menu. Commands and output will be + Archive will hide the workspace from the active menu. Commands and output will be retained in history.
@@ -414,7 +423,7 @@ class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
Actions
- Delete will remove the session, removing all commands and output from history. + Delete will remove the workspace, removing all commands and output from history.
@@ -422,7 +431,7 @@ class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> { onClick={this.handleDeleteSession} className="button is-prompt-danger is-outlined is-small" > - Delete Session + Delete Workspace
@@ -625,7 +634,7 @@ class ClientSettingsModal extends React.Component<{}, {}> { renderFontSizeDropdown(): any { let availableFontSizes = [8, 9, 10, 11, 12, 13, 14, 15]; let fsize: number = 0; - let curSize = GlobalModel.termFontSize.get() + let curSize = GlobalModel.termFontSize.get(); return (
diff --git a/src/app/common/prompt/prompt.tsx b/src/app/common/prompt/prompt.tsx index 90ef1a73..3cdc9ebd 100644 --- a/src/app/common/prompt/prompt.tsx +++ b/src/app/common/prompt/prompt.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -30,17 +33,6 @@ type RendererComponentType = { new (props: RendererComponentProps): React.Component; }; -function getShortVEnv(venvDir: string): string { - if (isBlank(venvDir)) { - return ""; - } - let lastSlash = venvDir.lastIndexOf("/"); - if (lastSlash == -1) { - return venvDir; - } - return venvDir.substr(lastSlash + 1); -} - function makeFullRemoteRef(ownerName: string, remoteRef: string, name: string): string { if (isBlank(ownerName) && isBlank(name)) { return remoteRef; @@ -64,6 +56,17 @@ function getRemoteStr(rptr: RemotePtrType): string { return fullRef; } +function getShortVEnv(venvDir: string): string { + if (isBlank(venvDir)) { + return ""; + } + let lastSlash = venvDir.lastIndexOf("/"); + if (lastSlash == -1) { + return venvDir; + } + return venvDir.substr(lastSlash + 1); +} + function replaceHomePath(path: string, homeDir: string): string { if (path == homeDir) { return "~"; @@ -153,4 +156,4 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record; tempPassword: OV; tempKeyFile: OV; - tempAutoInstall: OV; errorStr: OV; constructor(props: any) { @@ -163,7 +165,6 @@ class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdi this.tempConnectMode = mobx.observable.box("auto", { name: "CreateRemote-connectMode" }); this.tempKeyFile = mobx.observable.box("", { name: "CreateRemote-keystr" }); this.tempPassword = mobx.observable.box("", { name: "CreateRemote-password" }); - this.tempAutoInstall = mobx.observable.box(true, { name: "CreateRemote-autoinstall" }); this.errorStr = mobx.observable.box(remoteEdit.errorstr, { name: "CreateRemote-errorStr" }); } @@ -220,10 +221,31 @@ class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdi kwargs["password"] = ""; } kwargs["connectmode"] = this.tempConnectMode.get(); - kwargs["autoinstall"] = this.tempAutoInstall.get() ? "1" : "0"; kwargs["visual"] = "1"; kwargs["submit"] = "1"; - GlobalCommandRunner.createRemote(cname, kwargs); + let model = this.props.model; + let shouldCr = model.onlyAddNewRemote.get(); + let prtn = GlobalCommandRunner.createRemote(cname, kwargs, false); + prtn.then((crtn) => { + if (crtn.success) { + if (shouldCr) { + let crRtn = GlobalCommandRunner.screenSetRemote(cname, true, false); + crRtn.then((crcrtn) => { + if (crcrtn.success) { + model.closeModal(); + return; + } + mobx.action(() => { + this.errorStr.set(crcrtn.error); + })(); + }); + } + return; + } + mobx.action(() => { + this.errorStr.set(crtn.error); + })(); + }); } @boundMethod @@ -261,13 +283,6 @@ class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdi })(); } - @boundMethod - handleChangeAutoInstall(val: boolean): void { - mobx.action(() => { - this.tempAutoInstall.set(val); - })(); - } - render() { let { model, remoteEdit } = this.props; let authMode = this.tempAuthMode.get(); @@ -413,19 +428,6 @@ class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdi
-
-
-
Auto Install
-
- - If selected, will try to auto-install the mshell client if it is not installed or out of - date. - -
-
- -
-
Error: {this.getErrorStr()}
@@ -459,7 +461,6 @@ class EditRemoteSettings extends React.Component< tempManualMode: OV; tempPassword: OV; tempKeyFile: OV; - tempAutoInstall: OV; constructor(props: any) { super(props); @@ -471,7 +472,6 @@ class EditRemoteSettings extends React.Component< this.tempPassword = mobx.observable.box(remoteEdit.haspassword ? PasswordUnchangedSentinel : "", { name: "EditRemoteSettings-password", }); - this.tempAutoInstall = mobx.observable.box(!!remote.autoinstall, { name: "EditRemoteSettings-autoinstall" }); } componentDidUpdate() { @@ -527,13 +527,6 @@ class EditRemoteSettings extends React.Component< })(); } - @boundMethod - handleChangeAutoInstall(val: boolean): void { - mobx.action(() => { - this.tempAutoInstall.set(val); - })(); - } - @boundMethod canResetPw(): boolean { let { remoteEdit } = this.props; @@ -584,9 +577,6 @@ class EditRemoteSettings extends React.Component< if (!util.isStrEq(this.tempConnectMode.get(), remote.connectmode)) { kwargs["connectmode"] = this.tempConnectMode.get(); } - if (!util.isBoolEq(this.tempAutoInstall.get(), remote.autoinstall)) { - kwargs["autoinstall"] = this.tempAutoInstall.get() ? "1" : "0"; - } if (Object.keys(kwargs).length == 0) { return; } @@ -733,19 +723,6 @@ class EditRemoteSettings extends React.Component<
-
-
-
Auto Install
-
- - If selected, will try to auto-install the mshell client if it is not installed or out of - date. - -
-
- -
-
Actions
@@ -866,9 +843,6 @@ class RemoteDetailView extends React.Component<{ model: RemotesModalModel; remot if (statusStr == null) { return null; } - if (remote.autoinstall) { - statusStr = statusStr + " (autoinstall)"; - } return (
Install Status
@@ -1102,7 +1076,7 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> { renderRemoteMenuItem(remote: RemoteType, selectedId: string): any { return (
this.selectRemote(remote.remoteid)} className={cn("remote-menu-item", { "is-selected": remote.remoteid == selectedId })} > @@ -1150,6 +1124,7 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> { let isAuthEditMode = model.isAuthEditMode(); let selectedRemote = GlobalModel.getRemote(selectedRemoteId); let remoteEdit = model.remoteEdit.get(); + let onlyAddNewRemote = model.onlyAddNewRemote.get(); return (
@@ -1161,19 +1136,21 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> {
-
- {this.renderAddRemoteMenuItem()} - - {this.renderRemoteMenuItem(remote, selectedRemoteId)} - -
+ +
+ {this.renderAddRemoteMenuItem()} + + {this.renderRemoteMenuItem(remote, selectedRemoteId)} + +
{" "} +
{this.renderEmptyDetail()} - + { } } -export { RemotesModal }; +@mobxReact.observer +class RemotesSelector extends React.Component<{ model: RemotesModalModel; isChangeRemoteOnSelect?: boolean }, { isOpen: boolean }> { + constructor(props: any) { + super(props); + this.state = { + isOpen: false, + }; + } + + @boundMethod + selectRemote(remoteid: string, remotecanonicalname: string): void { + this.props.model.selectRemote(remoteid); + if (this.props.isChangeRemoteOnSelect) { + let prtn = GlobalCommandRunner.screenSetRemote(remotecanonicalname, true, false); + // TODO: see settings.tsx. use prtn to set error message + } + this.setState({ isOpen: false }); + } + + @boundMethod + clickAddRemote(): void { + GlobalModel.remotesModalModel.openModalForEdit({remoteedit: true}, true); + this.setState({ isOpen: false }); + } + + renderRemoteMenuItem(remote: RemoteType, selectedId: string): any { + return ( +
this.selectRemote(remote.remoteid, remote.remotecanonicalname)} + className={cn("dropdown-item remote-menu-item hoverEffect", { + "is-selected": remote.remoteid == selectedId, + })} + > +
+ +
+ +
+
{remote.remotecanonicalname}
+
+
+ +
+
{remote.remotealias}
+
{remote.remotecanonicalname}
+
+
+
+ ); + } + + render() { + const allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice()); + const remote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid); + const selectedRemoteDiv = ( +
+
+ +
+
{remote.remotealias}
+
{remote.remotecanonicalname}
+
+ ); + return ( +
+
+
+
+ +
+ +
+
+
+ ); + } +} + +export { RemotesModal, RemotesSelector }; diff --git a/src/app/history/history.less b/src/app/history/history.less index 5b22b414..d71a4365 100644 --- a/src/app/history/history.less +++ b/src/app/history/history.less @@ -323,7 +323,7 @@ margin-right: 1em; } - td.session { + td.workspace { flex: 0 0 auto; flex-basis: 120px; text-overflow: ellipsis; diff --git a/src/app/history/history.tsx b/src/app/history/history.tsx index 65629625..3f19cde5 100644 --- a/src/app/history/history.tsx +++ b/src/app/history/history.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -373,7 +376,7 @@ class HistoryView extends React.Component<{}, {}> {
{hvm.searchSessionId.get() == null - ? "Limit Session" + ? "Limit Workspace" : formatSessionName(snames, hvm.searchSessionId.get())} @@ -385,7 +388,7 @@ class HistoryView extends React.Component<{}, {}> { key="all" className="dropdown-item" > - (all sessions) + (all workspaces)
{ {getHistoryViewTs(nowDate, item.ts)} - {formatSSName(snames, scrnames, item)} + {formatSSName(snames, scrnames, item)} {formatRemoteName(rnames, item.remote)} this.activateItem(item.historyid)}>
- - - - - - - - - + + + + + + + + + + +
; renderMode: RenderModeType; noSelect?: boolean; + topBorder: boolean; }, {} > { diff --git a/src/app/line/lines.less b/src/app/line/lines.less index 33da4498..66ebfc7e 100644 --- a/src/app/line/lines.less +++ b/src/app/line/lines.less @@ -467,7 +467,7 @@ display: flex; flex-direction: column; overflow: auto; - padding: 10px 0; + padding: 0 0 10px 0; flex-grow: 1; position: relative; diff --git a/src/app/line/linesview.tsx b/src/app/line/linesview.tsx index 5ed1f125..79d048b1 100644 --- a/src/app/line/linesview.tsx +++ b/src/app/line/linesview.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; diff --git a/src/app/line/lineutil.ts b/src/app/line/lineutil.ts index 1fcb9db5..44e32f19 100644 --- a/src/app/line/lineutil.ts +++ b/src/app/line/lineutil.ts @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; import { isBlank, getDateStr } from "../../util/util"; diff --git a/src/app/line/renderer/basicrenderer.tsx b/src/app/line/renderer/basicrenderer.tsx index 75aad6d0..df760944 100644 --- a/src/app/line/renderer/basicrenderer.tsx +++ b/src/app/line/renderer/basicrenderer.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; diff --git a/src/app/magiclayout.ts b/src/app/magiclayout.ts new file mode 100644 index 00000000..dcba4620 --- /dev/null +++ b/src/app/magiclayout.ts @@ -0,0 +1,34 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +// magical layout constants to power TypeScript calculations +// these need to match the CSS (usually margins, paddings, positions, etc.) +let MagicLayout = { + CmdInputHeight: 92, // height of full cmd-input div + CmdInputBottom: 12, // .cmd-input + + LineHeaderHeight: 46, // .line-header + LinePadding: 24, // .line-header (12px * 2) + WindowHeightOffset: 6, // .window-view, height is calc(100%-0.5rem) + LinesBottomPadding: 10, // .lines, padding + LineMarginTop: 12, // .line, margin + + ScreenMaxContentWidthBuffer: 50, + ScreenMaxContentHeightBuffer: 0, // calc below + ScreenMinContentSize: 100, + ScreenMaxContentSize: 5000, + + // the 3 is for descenders, which get cut off in the terminal without this + TermDescendersHeight: 3, + TermWidthBuffer: 15, + +}; + +let m = MagicLayout; + +// add up all the line overhead + padding. subtract 2 so we don't see the border of neighboring line +m.ScreenMaxContentHeightBuffer = m.LineHeaderHeight + m.LinePadding + m.WindowHeightOffset + m.LinesBottomPadding + m.LineMarginTop - 2; + +(window as any).MagicLayout = MagicLayout; + +export { MagicLayout }; diff --git a/src/app/pluginsview/pluginsview.less b/src/app/pluginsview/pluginsview.less new file mode 100644 index 00000000..e8233ee5 --- /dev/null +++ b/src/app/pluginsview/pluginsview.less @@ -0,0 +1,137 @@ +@import "../../app/common/themes/themes.less"; + +.plugins-view { + background-color: @background-session; + + .header { + margin: 1.5em 1.5em 0.5em; + + .plugins-title { + margin-bottom: 0.5em; + font-weight: bold; + font-size: 1.5em; + } + + .close-button { + position: absolute; + right: 1em; + top: 0.8em; + cursor: pointer; + width: 1.5em; + height: 1.5em; + border-radius: 50%; + svg { + width: 1.5em; + height: 1.5em; + fill: @base-color; + } + } + } + + .body { + display: flex; + .plugins-list { + padding: 1em; + border-right: 1px solid @base-border; + min-height: calc(100vh - 6em); + max-height: calc(100vh - 6em); + overflow-y: auto; + flex: 0 0 auto; + .plugin-summary { + display: block; + padding: 10px; + max-width: 323px; + border-radius: 8px; + margin-bottom: 1em; + border: 1px solid transparent; + &.selected { + border-color: @prompt-green; + } + .plugin-summary-header { + display: flex; + .plugin-summary-icon { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + svg { + width: 32px; + height: 32px; + fill: white; + } + } + .plugin-summary-info { + flex: 1; + margin: 0 12px; + .plugin-summary-title { + } + .plugin-summary-vendor { + font-size: 0.8em; + } + } + } + .plugin-summary-body { + margin-top: 8px; + min-height: 54px; + max-height: 54px; + overflow-y: auto; + } + } + } + .plugins-details { + flex: 1 !important; + padding: 2em; + min-height: calc(100vh - 6em); + max-height: calc(100vh - 6em); + overflow-y: auto; + flex: 0 0 auto; + .plugin-label { + min-width: 100%; + margin-bottom: 1em; + } + .plugin-summary-header { + display: flex; + .plugin-summary-icon { + svg { + width: 48px; + height: 48px; + fill: white; + } + } + .plugin-summary-info { + flex: 1; + margin: 0 12px; + .plugin-summary-title { + font-size: 1.1em; + margin-top: 0.3em; + } + .plugin-summary-vendor { + font-size: 0.9em; + margin-top: 0.4em; + } + } + } + .plugin-summary-body { + margin-top: 1em; + } + .plugin-screenshots-container { + margin-top: 3em; + .plugin-screenshots { + display: flex; + overflow-x: auto; + width: 100%; + white-space: nowrap; + align-items: flex-start; + img { + flex-shrink: 0; + margin-right: 10px; + max-height: 50vh; + } + } + } + .plugin-readme { + margin-top: 3em; + } + } + } +} diff --git a/src/app/pluginsview/pluginsview.tsx b/src/app/pluginsview/pluginsview.tsx new file mode 100644 index 00000000..318b2355 --- /dev/null +++ b/src/app/pluginsview/pluginsview.tsx @@ -0,0 +1,106 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import { boundMethod } from "autobind-decorator"; +import { GlobalModel } from "../../model/model"; +import { PluginModel } from "../../plugins/plugins"; +import { Markdown } from "../common/common"; + +import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg"; + +import "./pluginsview.less"; + +@mobxReact.observer +class PluginsView extends React.Component<{}, {}> { + @boundMethod + closeView(): void { + GlobalModel.pluginsModel.closeView(); + } + + renderPluginIcon(plugin): any { + let Comp = plugin.iconComp; + return ; + } + + render() { + if (GlobalModel.activeMainView.get() !== "plugins") { + return <>; + } + const { pluginsModel } = GlobalModel; + console.log(`rendering details for ${pluginsModel.selectedPlugin.get().name}`); + const PluginList = () => ( +
+ {PluginModel.allPlugins().map((plugin, i) => ( +
pluginsModel.setSelectedPlugin(plugin)} + > +
+
{this.renderPluginIcon(plugin)}
+
+
{plugin.title}
+
{plugin.vendor}
+
+
+
{plugin.summary}
+
+ ))} +
+ ); + + const PluginDetails = () => { + const plugin = pluginsModel.selectedPlugin.get(); + return ( +
+
+
{this.renderPluginIcon(plugin)}
+
+
{plugin.title}
+
{plugin.vendor}
+
+
+
{plugin.summary}
+ {plugin.screenshots && plugin.screenshots.length > 0 && ( +
+
{"Screenshots"}
+
+ {plugin.screenshots.map((path, index) => ( + {`Screenshot + ))} +
+
+ )} + {plugin.readme && ( +
+
{"Readme"}
+ +
+ )} +
+ ); + }; + + return ( +
+
+
Apps
+
+ +
+
+
+ + +
+
+ ); + } +} + +export { PluginsView }; diff --git a/src/app/sidebar/MainSideBar.tsx b/src/app/sidebar/MainSideBar.tsx index ca624ef0..c5a17173 100644 --- a/src/app/sidebar/MainSideBar.tsx +++ b/src/app/sidebar/MainSideBar.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -74,16 +77,18 @@ class MainSideBar extends React.Component<{}, {}> { } @boundMethod - handleAddRemote(): void { - GlobalCommandRunner.openCreateRemote(); + handlePluginsClick(): void { + if (GlobalModel.activeMainView.get() == "plugins") { + GlobalModel.showSessionView(); + return; + } + GlobalModel.pluginsModel.showPluginsView(); } @boundMethod handleHistoryClick(): void { if (GlobalModel.activeMainView.get() == "history") { - mobx.action(() => { - GlobalModel.activeMainView.set("session"); - })(); + GlobalModel.showSessionView(); return; } GlobalModel.historyViewModel.reSearch(); @@ -152,14 +157,12 @@ class MainSideBar extends React.Component<{}, {}> { } return sessionList.map((session, index) => { const isActive = GlobalModel.activeMainView.get() == "session" && activeSessionId == session.sessionId; - /** @TODO: Handle archived sessions and talk to Mike about session settings */ return (
this.handleSessionClick(session.sessionId)} > - ^⌘ {index + 1} {session.name.get()} { let mainView = GlobalModel.activeMainView.get(); return (
-
+
- {/*
+
Apps ⌘A -
*/} +
History @@ -224,7 +227,7 @@ class MainSideBar extends React.Component<{}, {}> {
-
+
Workspaces
diff --git a/src/app/sidebar/sidebar.less b/src/app/sidebar/sidebar.less index 1570d546..3325ba08 100644 --- a/src/app/sidebar/sidebar.less +++ b/src/app/sidebar/sidebar.less @@ -7,6 +7,8 @@ display: flex; flex-direction: column; position: relative; + font-size: 12.5px; + line-height: 20px; .title-bar-drag { -webkit-app-region: drag; @@ -32,7 +34,7 @@ .arrow-container { position: absolute; - top: 2em; + top: 1em; right: 2em; width: 1.5em; height: 1.5em; @@ -45,7 +47,7 @@ } .top { - margin-top: 4em; + margin-top: 32px; } .separator { @@ -54,9 +56,13 @@ background-color: @base-border; } + .item.workspaces-item { + margin-bottom: -4px; + } + .middle { max-height: calc(100vh - 32em); - padding: 8px 6px; + padding: 4px 6px 8px 6px; border-bottom: 1px solid @base-border; .item { &.active { @@ -89,30 +95,27 @@ } .item { - padding: 6px; + padding: 5px; margin: 0 6px; border-radius: 4px; opacity: 1; visibility: visible; transition: opacity 0.1s ease-in-out, visibility 0.1s step-end; .sessionName { - width: 11rem; + width: 12rem; display: inline-block; vertical-align: middle; } .icon { - margin: 0 0.5em 2px 0.5em; - width: 1em; - height: 1em; + margin: -2px 8px 0px 4px; + width: 16px; + height: 16px; display: inline-block; vertical-align: middle; border-radius: 50%; - - &.discord { - width: 1.2em; - height: 1.2em; - margin: 0 0.4em 2px 0.4em; - } + } + .actions.icon { + margin-left: 8px; } .hotkey { float: right; diff --git a/src/app/workspace/cmdinput/cmdInput.less b/src/app/workspace/cmdinput/cmdInput.less index 772db8b2..6b5c63c0 100644 --- a/src/app/workspace/cmdinput/cmdInput.less +++ b/src/app/workspace/cmdinput/cmdInput.less @@ -6,10 +6,11 @@ display: flex; flex-direction: column; position: absolute; - bottom: 16px; - right: 16px; - width: calc(100% - 28px); + bottom: 12px; + right: 12px; + width: calc(100% - 24px); padding: 12px; + padding-bottom: 6px; z-index: 100; background: @background-session-components; box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5), 0px 3px 8px 0px rgba(0, 0, 0, 0.35), diff --git a/src/app/workspace/cmdinput/cmdinput.tsx b/src/app/workspace/cmdinput/cmdinput.tsx index 0ea52420..9149e74d 100644 --- a/src/app/workspace/cmdinput/cmdinput.tsx +++ b/src/app/workspace/cmdinput/cmdinput.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -64,6 +67,11 @@ class CmdInput extends React.Component<{}, {}> { GlobalModel.inputModel.giveFocus(); } + @boundMethod + cmdInputClick(): void { + GlobalModel.inputModel.giveFocus(); + } + @boundMethod clickHistoryHint(e: any): void { e.preventDefault(); @@ -106,7 +114,11 @@ class CmdInput extends React.Component<{}, {}> { let inputMode: string = inputModel.inputMode.get(); let textAreaInputKey = screen == null ? "null" : screen.screenId; return ( -
+
@@ -153,10 +165,10 @@ class CmdInput extends React.Component<{}, {}> {
-
+
{/**
{inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")}) -
**/} +
**/} {!focusVal && (
focus input ({renderCmdText("I")}) @@ -168,6 +180,7 @@ class CmdInput extends React.Component<{}, {}> {
)}
diff --git a/src/app/workspace/cmdinput/historyinfo.tsx b/src/app/workspace/cmdinput/historyinfo.tsx index c403a1e2..9d2a13de 100644 --- a/src/app/workspace/cmdinput/historyinfo.tsx +++ b/src/app/workspace/cmdinput/historyinfo.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; diff --git a/src/app/workspace/cmdinput/infomsg.tsx b/src/app/workspace/cmdinput/infomsg.tsx index 0f79d24c..67b754f7 100644 --- a/src/app/workspace/cmdinput/infomsg.tsx +++ b/src/app/workspace/cmdinput/infomsg.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import { If, For } from "tsx-control-statements/components"; @@ -16,6 +19,9 @@ class InfoMsg extends React.Component<{}, {}> { if (s.startsWith("^/")) { return s.substr(1); } + if (s.startsWith("^")) { + return s.substr(1); + } let slashIdx = s.lastIndexOf("/"); if (slashIdx == s.length - 1) { slashIdx = s.lastIndexOf("/", slashIdx - 1); diff --git a/src/app/workspace/cmdinput/textareainput.tsx b/src/app/workspace/cmdinput/textareainput.tsx index 715cb10e..bf1b370d 100644 --- a/src/app/workspace/cmdinput/textareainput.tsx +++ b/src/app/workspace/cmdinput/textareainput.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; diff --git a/src/app/workspace/screen/screenview.less b/src/app/workspace/screen/screenview.less index c9b19a51..131dbae8 100644 --- a/src/app/workspace/screen/screenview.less +++ b/src/app/workspace/screen/screenview.less @@ -11,7 +11,7 @@ flex-direction: column; position: absolute; width: 100%; - height: calc(100% - 3em); + height: calc(100% - 0.5rem); overflow-x: hidden; .rendermode-tag { @@ -78,25 +78,209 @@ } } } + } - .window-empty { + .screen-settings-inline { + padding: 2em; + .settings-field { + display: block; + padding: 1.5em 1em; + margin-top: 0; + line-height: 2.5em; + border-top: 1px solid @base-border; + &:first-child { + border-top: none; + } + } + } +} + +.newtab-container { + margin: 16px; + + .newtab-section { + padding: 16px; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + + &.conn-section { + gap: 8px; + } + } + + .newtab-spacer { + height: 1px; + background: rgba(241, 246, 243, 0.15); + } + + .control-iconlist { + display: flex; + padding: 8px 0; + align-items: flex-start; + gap: 27px; + + .icondiv { + width: 16px; + height: 16px; + cursor: pointer; + position: relative; + + .icon { + width: 16px; + height: 16px; + } + + .check-icon { + width: 12px; + height: 12px; + position: absolute; + top: 2px; + left: 2px; + } + + .icon.color-white + .check-icon { + path { + fill: black; + } + } + } + } + + .dropdown.conn-dropdown { + padding-left: 0; + border-radius: 8px; + background-color: rgba(241, 246, 243, 0.08); + + .conn-dd-trigger { display: flex; + flex-direction: row; + width: 413px; + padding: 6px 8px 6px 12px; align-items: center; - justify-content: center; - width: 100%; - padding: 10px; - height: 100%; - color: @term-white; + height: 42px; + + .lefticon { + margin-right: 8px; + margin-top: 4px; + position: relative; + + .status-icon { + width: 10px; + height: 10px; + stroke-width: 2px; + stroke: @status-outline; + position: absolute; + bottom: 3px; + right: -2px; + } + } + + .dd-control { + display: flex; + padding: 4px; + align-items: center; - code { - color: @prompt-green; - background-color: transparent; - font-family: @fixed-font; + .icon { + height: 16px; + width: 16px; + } } + + .globe-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + } + + .conntext { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + flex: 1 0 0; + + .conntext-solo { + color: @text-primary; + text-overflow: ellipsis; + } + + .conntext-1 { + color: @text-primary; + text-overflow: ellipsis; + } + + .conntext-2 { + color: @text-secondary; + text-overflow: ellipsis; + } + } + } + + .conn-dd-menu { + display: flex; + width: 413px; + padding: 6px; + flex-direction: column; + align-items: flex-start; + border-radius: 8px; + background-color: @dropdown-menu; + + .dropdown-item { + display: flex; + padding: 5px 12px 5px 8px; + align-items: center; + gap: 8px; + align-self: stretch; + border-radius: 6px; + + .status-div { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + padding: 3px; + + svg.status-icon { + width: 10px; + height: 10px; + } + } + + .add-div { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + + svg.add-icon { + width: 16px; + height: 16px; + + path { + fill: @text-primary; + } + } + } + + .text-standard { + color: @text-secondary; + } + + .text-caption { + color: @text-caption; + } + + .ellipsis { + text-overflow: ellipsis; + } - &.should-fade { - opacity: 1; - animation: fade-in 2.5s; + &:hover { + background-color: rgba(241, 246, 243, 0.08); + } } } } diff --git a/src/app/workspace/screen/screenview.tsx b/src/app/workspace/screen/screenview.tsx index 706c1da0..0ac81b4d 100644 --- a/src/app/workspace/screen/screenview.tsx +++ b/src/app/workspace/screen/screenview.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -7,11 +10,23 @@ import { If, For } from "tsx-control-statements/components"; import cn from "classnames"; import { debounce } from "throttle-debounce"; import dayjs from "dayjs"; -import type { LineType, RenderModeType, LineFactoryProps } from "../../../types/types"; +import { GlobalCommandRunner, TabColors } from "../../../model/model"; +import type { LineType, RenderModeType, LineFactoryProps, CommandRtnType } from "../../../types/types"; +import * as T from "../../../types/types"; import localizedFormat from "dayjs/plugin/localizedFormat"; +import { InlineSettingsTextEdit, RemoteStatusLight } from "../../common/common"; +import { getRemoteStr } from "../../common/prompt/prompt"; import { GlobalModel, ScreenLines, Screen } from "../../../model/model"; import { Line } from "../../line/linecomps"; import { LinesView } from "../../line/linesview"; +import * as util from "../../../util/util"; +import { ReactComponent as EllipseIcon } from "../../assets/icons/ellipse.svg"; +import { ReactComponent as Check12Icon } from "../../assets/icons/check12.svg"; +import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg"; +import { ReactComponent as StatusCircleIcon } from "../../assets/icons/statuscircle.svg"; +import { ReactComponent as ArrowsUpDownIcon } from "../../assets/icons/arrowsupdown.svg"; +import { ReactComponent as CircleIcon } from "../../assets/icons/circle.svg"; +import { ReactComponent as AddIcon } from "../../assets/icons/add.svg"; import "./screenview.less"; import "./tabs.less"; @@ -36,6 +51,172 @@ class ScreenView extends React.Component<{ screen: Screen }, {}> { } } +@mobxReact.observer +class NewTabSettings extends React.Component<{ screen: Screen }, {}> { + connDropdownActive: OV = mobx.observable.box(false, { name: "NewTabSettings-connDropdownActive" }); + errorMessage: OV = mobx.observable.box(null, { name: "NewTabSettings-errorMessage" }); + + @boundMethod + selectTabColor(color: string): void { + let { screen } = this.props; + if (screen.getTabColor() == color) { + return; + } + let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { tabcolor: color }, false); + util.commandRtnHandler(prtn, this.errorMessage); + } + + @boundMethod + inlineUpdateName(val: string): void { + let { screen } = this.props; + if (util.isStrEq(val, screen.name.get())) { + return; + } + let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { name: val }, false); + util.commandRtnHandler(prtn, this.errorMessage); + } + + @boundMethod + toggleConnDropdown(): void { + mobx.action(() => { + this.connDropdownActive.set(!this.connDropdownActive.get()); + })(); + } + + @boundMethod + selectRemote(cname: string): void { + mobx.action(() => { + this.connDropdownActive.set(false); + })(); + let prtn = GlobalCommandRunner.screenSetRemote(cname, true, false); + util.commandRtnHandler(prtn, this.errorMessage); + } + + @boundMethod + clickNewConnection(): void { + mobx.action(() => { + this.connDropdownActive.set(false); + })(); + GlobalModel.remotesModalModel.openModalForEdit({remoteedit: true}, true); + } + + renderConnDropdown(): any { + let { screen } = this.props; + let allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice()); + let remote: T.RemoteType = null; + let curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid); + // TODO no remote? + return ( +
+
+
+
+ + +
+
+ +
+ {curRemote.remotecanonicalname} +
+
+ +
+ {curRemote.remotealias} +
+
+ {curRemote.remotecanonicalname} +
+
+
+
+ +
+
+
+
+
+ +
this.selectRemote(remote.remotecanonicalname)}> +
+ +
+ +
{remote.remotecanonicalname}
+
+ +
{remote.remotealias}
+
{remote.remotecanonicalname}
+
+
+
+
+
+ +
+
New Connection
+
+
+
+
+ ); + } + + render() { + let { screen } = this.props; + let rptr = screen.curRemote.get(); + let curColor = screen.getTabColor(); + if (util.isBlank(curColor) || curColor == "default") { + curColor = "green"; + } + let color: string = null; + return ( +
+
+
+ You're connected to [{getRemoteStr(rptr)}]. Do you want to change it? +
+
+ {this.renderConnDropdown()} +
+
+
+
+
+ Name +
+
+ +
+
+
+
+
+ Select the color +
+
+ +
this.selectTabColor(color)}> + + + + +
+
+
+
+
+ ); + } +} + // screen is not null @mobxReact.observer class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { @@ -178,11 +359,7 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { if (cdata == null) { return this.renderError("loading client data", true); } - let idx = 0; - let line: LineType = null; - let session = GlobalModel.getSessionById(screen.sessionId); let isActive = screen.isActive(); - let selectedLine = screen.getSelectedLine(); let lines = win.getNonArchivedLines(); let renderMode = this.renderMode.get(); return ( @@ -201,6 +378,9 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
+ + +
@@ -237,13 +417,6 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { lineFactory={this.buildLineComponent} /> - -
-
- [workspace="{session.name.get()}" screen="{screen.name.get()}"] -
-
-
); } diff --git a/src/app/workspace/screen/tabs.less b/src/app/workspace/screen/tabs.less index 503f9bb1..088059b1 100644 --- a/src/app/workspace/screen/tabs.less +++ b/src/app/workspace/screen/tabs.less @@ -1,8 +1,12 @@ @import "../../../app/common/themes/themes.less"; #main .screen-tabs .screen-tab { - border-radius: 12px 0px 0px 0px; border-top: 1px solid transparent; + font-size: 12.5px; + + &:first-child { + border-radius: 12px 0px 0px 0px; + } &.color-default { svg.left-icon path { @@ -58,9 +62,15 @@ } } - &.color-black { + &.color-violet { svg.left-icon path { - fill: @tab-black; + fill: @tab-violet; + } + } + + &.color-pink { + svg.left-icon path { + fill: @tab-pink; } } @@ -139,7 +149,9 @@ } .new-screen { + flex-shrink: 0; margin-left: 1em; + margin-right: 1em; cursor: pointer; .icon { height: 2rem; diff --git a/src/app/workspace/screen/tabs.tsx b/src/app/workspace/screen/tabs.tsx index 4390c8dd..90f6cac9 100644 --- a/src/app/workspace/screen/tabs.tsx +++ b/src/app/workspace/screen/tabs.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -191,11 +194,6 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
- {/**
-
move left {renderCmdText("[")}
-
move right {renderCmdText("]")}
-
new tab {renderCmdText("T")}
-
*/}
); } diff --git a/src/app/workspace/workspace.less b/src/app/workspace/workspace.less index 565a4639..f8d4777a 100644 --- a/src/app/workspace/workspace.less +++ b/src/app/workspace/workspace.less @@ -12,8 +12,8 @@ background: @background-session; border: 1px solid @base-border; border-radius: 8px; - margin-bottom: 1em; transition: width 0.2s ease; + margin-bottom: 0.5em; } .collapsed + .session-view { max-width: calc(100% - 6.7em); diff --git a/src/app/workspace/workspaceview.tsx b/src/app/workspace/workspaceview.tsx index 328b4b52..d9868593 100644 --- a/src/app/workspace/workspaceview.tsx +++ b/src/app/workspace/workspaceview.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -8,6 +11,8 @@ import { GlobalModel } from "../../model/model"; import { CmdInput } from "./cmdinput/cmdinput"; import { ScreenView } from "./screen/screenview"; import { ScreenTabs } from "./screen/tabs"; +import { ErrorBoundary } from "../../app/common/error/errorboundary"; +import { MagicLayout } from "../magiclayout"; import "./workspace.less"; dayjs.extend(localizedFormat); @@ -25,15 +30,19 @@ class WorkspaceView extends React.Component<{}, {}> { let activeScreen = session.getActiveScreen(); let cmdInputHeight = model.inputModel.cmdInputHeight.get(); if (cmdInputHeight == 0) { - cmdInputHeight = 110; + cmdInputHeight = MagicLayout.CmdInputHeight; // this is the base size of cmdInput (measured using devtools) } + cmdInputHeight += MagicLayout.CmdInputBottom; // reference to .cmd-input, bottom: 12px let isHidden = GlobalModel.activeMainView.get() != "session"; + return (
- -
- + + +
+ +
); } diff --git a/src/electron/emain.ts b/src/electron/emain.ts index 6e7a6d7a..527bad30 100644 --- a/src/electron/emain.ts +++ b/src/electron/emain.ts @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as electron from "electron"; import * as path from "path"; import * as fs from "fs"; @@ -40,7 +43,7 @@ let loggerConfig = { winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), winston.format.printf((info) => `${info.timestamp} ${info.message}`) ), - transports: [new winston.transports.File({ filename: path.join(scHome, "prompt-app.log"), level: "info" })], + transports: [new winston.transports.File({ filename: path.join(scHome, "waveterm-app.log"), level: "info" })], }; if (isDev) { loggerConfig.transports.push(new winston.transports.Console()); @@ -56,7 +59,7 @@ function log(...msg) { console.log = log; console.log( sprintf( - "prompt-app starting, PROMPT_HOME=%s, apppath=%s arch=%s/%s", + "waveterm-app starting, PROMPT_HOME=%s, apppath=%s arch=%s/%s", scHome, getAppBasePath(), unamePlatform, @@ -67,7 +70,7 @@ if (isDev) { console.log("prompt-app PROMPT_DEV set"); } let app = electron.app; -app.setName(isDev ? "Prompt (Dev)" : "Prompt"); +app.setName(isDev ? "Wave (Dev)" : "Wave"); let waveSrvProc = null; let waveSrvShouldRestart = false; @@ -336,7 +339,7 @@ function mainResizeHandler(e, win) { return; } let bounds = win.getBounds(); - console.log("resize/move", win.getBounds()); + // console.log("resize/move", win.getBounds()); let winSize = { width: bounds.width, height: bounds.height, top: bounds.y, left: bounds.x }; let url = getBaseHostPort() + "/api/set-winsize"; let fetchHeaders = getFetchHeaders(); @@ -603,7 +606,7 @@ function runActiveTimer() { (async () => { let instanceLock = app.requestSingleInstanceLock(); if (!instanceLock) { - console.log("prompt-app could not get single-instance-lock, shutting down"); + console.log("waveterm-app could not get single-instance-lock, shutting down"); app.quit(); return; } diff --git a/src/model/model.ts b/src/model/model.ts index 3129a92c..3fcae67b 100644 --- a/src/model/model.ts +++ b/src/model/model.ts @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as mobx from "mobx"; import { sprintf } from "sprintf-js"; import { boundMethod } from "autobind-decorator"; @@ -12,6 +15,7 @@ import { isModKeyPress, } from "../util/util"; import { TermWrap } from "../plugins/terminal/term"; +import { PluginModel } from "../plugins/plugins"; import type { SessionDataType, LineType, @@ -72,6 +76,8 @@ import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; import customParseFormat from "dayjs/plugin/customParseFormat"; import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil"; +import { sortAndFilterRemotes } from "../util/util"; +import { MagicLayout } from "../app/magiclayout"; dayjs.extend(customParseFormat); dayjs.extend(localizedFormat); @@ -88,7 +94,7 @@ const MinFontSize = 8; const MaxFontSize = 15; const InputChunkSize = 500; const RemoteColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"]; -const TabColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange", "black"]; +const TabColors = ["green", "blue", "yellow", "pink", "magenta", "cyan", "violet", "orange", "red", "white"]; // @ts-ignore const VERSION = __PROMPT_VERSION__; @@ -640,10 +646,11 @@ class Screen { let height = termHeightFromRows(25, GlobalModel.termFontSize.get()); return { width, height }; } - // TODO calculate these sizes more deliberately let winSize = this.lastScreenSize; - let width = boundInt(winSize.width - 50, 100, 5000); - let height = boundInt(winSize.height - 120, 100, 5000); + let minSize = MagicLayout.ScreenMinContentSize; + let maxSize = MagicLayout.ScreenMaxContentSize; + let width = boundInt(winSize.width - MagicLayout.ScreenMaxContentWidthBuffer, minSize, maxSize); + let height = boundInt(winSize.height - MagicLayout.ScreenMaxContentHeightBuffer, minSize, maxSize); return { width, height }; } @@ -1977,7 +1984,7 @@ class HistoryViewModel { return; } let prtn = GlobalModel.showAlert({ - message: "Deleting lines from history also deletes their content from your sessions.", + message: "Deleting lines from history also deletes their content from your workspaces.", confirm: true, }); prtn.then((result) => { @@ -2417,13 +2424,45 @@ class BookmarksModel { return; } } - return; +} + +class PluginsModel { + selectedPlugin: OV = mobx.observable.box(null, { name: "selectedPlugin" }); + + showPluginsView(): void { + mobx.action(() => { + this.reset(); + GlobalModel.activeMainView.set("plugins"); + const allPlugins = PluginModel.allPlugins(); + this.selectedPlugin.set(allPlugins.length > 0 ? allPlugins[0] : null); + })(); + } + + setSelectedPlugin(plugin: RendererPluginType): void { + mobx.action(() => { + this.selectedPlugin.set(plugin); + })(); + } + + reset(): void { + mobx.action(() => { + this.selectedPlugin.set(null); + })(); + } + + closeView(): void { + GlobalModel.showSessionView(); + setTimeout(() => GlobalModel.inputModel.giveFocus(), 50); + } } class RemotesModalModel { openState: OV = mobx.observable.box(false, { name: "RemotesModalModel-isOpen", }); + onlyAddNewRemote: OV = mobx.observable.box(false, { + name: "RemotesModalModel-onlyAddNewRemote", + }); selectedRemoteId: OV = mobx.observable.box(null, { name: "RemotesModalModel-selectedRemoteId", }); @@ -2465,8 +2504,9 @@ class RemotesModalModel { })(); } - openModalForEdit(redit: RemoteEditType): void { + openModalForEdit(redit: RemoteEditType, onlyAddNewRemote: boolean): void { mobx.action(() => { + this.onlyAddNewRemote.set(onlyAddNewRemote); this.openState.set(true); this.selectedRemoteId.set(redit.remoteid); this.remoteEdit.set(redit); @@ -2495,6 +2535,11 @@ class RemotesModalModel { cancelEditAuth(): void { mobx.action(() => { this.remoteEdit.set(null); + if (this.onlyAddNewRemote.get()) { + this.onlyAddNewRemote.set(false); + this.openState.set(false); + return; + } if (this.selectedRemoteId.get() == null) { this.openModal(); } @@ -2517,6 +2562,7 @@ class RemotesModalModel { this.openState.set(false); this.selectedRemoteId.set(null); this.remoteEdit.set(null); + this.onlyAddNewRemote.set(false); })(); setTimeout(() => GlobalModel.refocus(), 10); } @@ -2642,7 +2688,7 @@ class Model { authKey: string; isDev: boolean; platform: string; - activeMainView: OV<"session" | "history" | "bookmarks" | "webshare"> = mobx.observable.box("session", { + activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare"> = mobx.observable.box("session", { name: "activeMainView", }); termFontSize: CV; @@ -2668,6 +2714,7 @@ class Model { remotesModalModel: RemotesModalModel; inputModel: InputModel; + pluginsModel: PluginsModel; bookmarksModel: BookmarksModel; historyViewModel: HistoryViewModel; clientData: OV = mobx.observable.box(null, { @@ -2686,6 +2733,7 @@ class Model { ); this.ws.reconnect(); this.inputModel = new InputModel(); + this.pluginsModel = new PluginsModel(); this.bookmarksModel = new BookmarksModel(); this.historyViewModel = new HistoryViewModel(); this.remotesModalModel = new RemotesModalModel(); @@ -3187,7 +3235,9 @@ class Model { this.updateRemotes(update.remotes); } if ("mainview" in update) { - if (update.mainview == "bookmarks") { + if (update.mainview == "plugins") { + this.pluginsModel.showPluginsView(); + } else if (update.mainview == "bookmarks") { this.bookmarksModel.showBookmarksView(update.bookmarks, update.selectedbookmark); } else if (update.mainview == "session") { this.activeMainView.set("session"); @@ -3211,7 +3261,7 @@ class Model { if (rview.remoteshowall) { this.remotesModalModel.openModal(); } else if (rview.remoteedit != null) { - this.remotesModalModel.openModalForEdit(rview.remoteedit); + this.remotesModalModel.openModalForEdit(rview.remoteedit, false); } else if (rview.ptyremoteid) { this.remotesModalModel.openModal(rview.ptyremoteid); } @@ -3448,7 +3498,15 @@ class Model { uicontext: this.getUIContext(), interactive: interactive, }; - // console.log("CMD", pk.metacmd + (pk.metasubcmd != null ? ":" + pk.metasubcmd : ""), pk.args, pk.kwargs, pk.interactive); + /** + console.log( + "CMD", + pk.metacmd + (pk.metasubcmd != null ? ":" + pk.metasubcmd : ""), + pk.args, + pk.kwargs, + pk.interactive + ); + */ return this.submitCommandPacket(pk, interactive); } @@ -3462,7 +3520,7 @@ class Model { interactive: interactive, rawstr: cmdStr, }; - if (!addToHistory) { + if (!addToHistory && pk.kwargs) { pk.kwargs["nohist"] = "1"; } return this.submitCommandPacket(pk, interactive); @@ -3618,7 +3676,7 @@ class Model { return remote.remotecanonicalname; } - readRemoteFile(screenId: string, lineId: string, path: string): Promise { + readRemoteFile(screenId: string, lineId: string, path: string): Promise { let urlParams = { screenid: screenId, lineid: lineId, @@ -3653,7 +3711,7 @@ class Model { let isWriteable = (fileInfo.perm & 0o222) > 0; // checks for unix permission "w" bits (file as any).readOnly = !isWriteable; (file as any).notFound = !!fileInfo.notfound; - return file; + return file as T.ExtFile; } else { let textError: string = blobOrText; if (textError == null || textError.length == 0) { @@ -3817,16 +3875,24 @@ class CommandRunner { GlobalModel.submitCommand("remote", "installcancel", null, { nohist: "1", remote: remoteid }, true); } - createRemote(cname: string, kwargsArg: Record) { + createRemote(cname: string, kwargsArg: Record, interactive: boolean): Promise { let kwargs = Object.assign({}, kwargsArg); kwargs["nohist"] = "1"; - GlobalModel.submitCommand("remote", "new", [cname], kwargs, true); + return GlobalModel.submitCommand("remote", "new", [cname], kwargs, interactive); } openCreateRemote(): void { GlobalModel.submitCommand("remote", "new", null, { nohist: "1", visual: "1" }, true); } + screenSetRemote(remoteArg: string, nohist: boolean, interactive: boolean): Promise { + let kwargs = {}; + if (nohist) { + kwargs["nohist"] = "1"; + } + return GlobalModel.submitCommand("connect", null, [remoteArg], kwargs, interactive); + } + editRemote(remoteid: string, kwargsArg: Record): void { let kwargs = Object.assign({}, kwargsArg); kwargs["nohist"] = "1"; diff --git a/src/model/ws.ts b/src/model/ws.ts index bd6cdc0a..bd0af743 100644 --- a/src/model/ws.ts +++ b/src/model/ws.ts @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as mobx from "mobx"; import { sprintf } from "sprintf-js"; import { boundMethod } from "autobind-decorator"; diff --git a/src/plugins/code/code.tsx b/src/plugins/code/code.tsx index 3a582026..87da0780 100644 --- a/src/plugins/code/code.tsx +++ b/src/plugins/code/code.tsx @@ -1,5 +1,8 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; -import { RendererContext, RendererOpts, LineStateType, RendererModelContainerApi } from "../../types/types"; +import * as T from "../../types/types"; import Editor from "@monaco-editor/react"; import { Markdown } from "../../app/common/common"; import { GlobalModel, GlobalCommandRunner } from "../../model/model"; @@ -18,20 +21,20 @@ declare var monaco: any; class SourceCodeRenderer extends React.Component< { - data: Blob; + data: T.ExtBlob; cmdstr: string; cwd: string; readOnly: boolean; notFound: boolean; exitcode: number; - context: RendererContext; - opts: RendererOpts; + context: T.RendererContext; + opts: T.RendererOpts; savedHeight: number; scrollToBringIntoViewport: () => void; - lineState: LineStateType; + lineState: T.LineStateType; isSelected: boolean; shouldFocus: boolean; - rendererApi: RendererModelContainerApi; + rendererApi: T.RendererModelContainerApi; }, { code: string; diff --git a/src/plugins/code/icon.svg b/src/plugins/code/icon.svg new file mode 100644 index 00000000..be5696e3 --- /dev/null +++ b/src/plugins/code/icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/plugins/code/meta.json b/src/plugins/code/meta.json new file mode 100644 index 00000000..baf22987 --- /dev/null +++ b/src/plugins/code/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Code Viewer", + "vendor": "Wave", + "summary": "View and Edit source code with syntax highlightng and code completion." +} diff --git a/src/plugins/code/readme.md b/src/plugins/code/readme.md new file mode 100644 index 00000000..8319f338 --- /dev/null +++ b/src/plugins/code/readme.md @@ -0,0 +1,8 @@ +# Code Editor for Wave Terminal + +These instructions are for setting up the build on MacOS. +If you're developing on Linux please use the [Linux Build Instructions](./build-linux.md). + +## Running the Development Version of Wave + +If you install the production version of Wave, you'll see a semi-transparent sidebar, and the data for Wave is stored in the directory ~/prompt. The development version has a red/brown sidebar and stores its data in ~/prompt-dev. This allows the production and development versions to be run simultaneously with no conflicts. If the dev database is corrupted by development bugs, or the schema changes in development it will not affect the production copy. diff --git a/src/plugins/code/screenshots/1.png b/src/plugins/code/screenshots/1.png new file mode 100644 index 00000000..9743341d Binary files /dev/null and b/src/plugins/code/screenshots/1.png differ diff --git a/src/plugins/core/basicrenderer.tsx b/src/plugins/core/basicrenderer.tsx index 866dbc4a..fc1caa84 100644 --- a/src/plugins/core/basicrenderer.tsx +++ b/src/plugins/core/basicrenderer.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -14,6 +17,7 @@ import type { TermContextUnion, RendererContainerType, } from "../../types/types"; +import * as T from "../../types/types"; import { debounce, throttle } from "throttle-debounce"; import * as util from "../../util/util"; import { GlobalModel } from "../../model/model"; @@ -34,7 +38,7 @@ class SimpleBlobRendererModel { lineState: LineStateType; ptyData: PtyDataType; ptyDataSource: (termContext: TermContextUnion) => Promise; - dataBlob: Blob; + dataBlob: T.ExtBlob; readOnly: boolean; notFound: boolean; @@ -143,7 +147,9 @@ class SimpleBlobRendererModel { } rtnp.then((ptydata) => { this.ptyData = ptydata; - this.dataBlob = new Blob([this.ptyData.data]); + let blob: T.ExtBlob = new Blob([this.ptyData.data]) as T.ExtBlob; + blob.notFound = false; + this.dataBlob = blob; mobx.action(() => { this.loading.set(false); this.loadError.set(null); diff --git a/src/plugins/core/incrementalrenderer.tsx b/src/plugins/core/incrementalrenderer.tsx index 2a4d1b70..adf859ca 100644 --- a/src/plugins/core/incrementalrenderer.tsx +++ b/src/plugins/core/incrementalrenderer.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; diff --git a/src/plugins/core/ptydata.ts b/src/plugins/core/ptydata.ts index 45c8e55e..04206523 100644 --- a/src/plugins/core/ptydata.ts +++ b/src/plugins/core/ptydata.ts @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as mobx from "mobx"; import { incObs } from "../../util/util"; diff --git a/src/plugins/csv/csv.less b/src/plugins/csv/csv.less index b77a5c46..1c8398da 100644 --- a/src/plugins/csv/csv.less +++ b/src/plugins/csv/csv.less @@ -1,6 +1,8 @@ @import "../../app/common/themes/themes.less"; .csv-renderer { + opacity: 0; /* Start with an opacity of 0, meaning it's invisible */ + .ellipsis() { display: block; white-space: nowrap; @@ -14,42 +16,43 @@ .cursor-pointer { cursor: pointer; } - + .select-none { user-select: none; } - - .global-search-render { - margin-bottom: 10px; + + table.probe { + position: absolute; + visibility: hidden; } table { - border-collapse: collapse; - overflow-x: auto; + border-collapse: collapse; + overflow-x: auto; border: 1px solid @scrollbar-thumb; thead { - position:relative; + position: relative; display: block; width: 100%; overflow-y: scroll; - tr { - border-bottom: 1px solid @scrollbar-thumb; + tr { + border-bottom: 1px solid @scrollbar-thumb; th { - color:@term-white; - border: 1px solid @scrollbar-thumb; + color: @term-white; + border-right: 1px solid @scrollbar-thumb; border-bottom: none; - padding: 2px 10px 4px 10px; - flex-basis:100%; - flex-grow:2; + padding: 2px 10px; + flex-basis: 100%; + flex-grow: 2; display: block; - text-align:left; + text-align: left; position: relative; .inner { - text-align:left; + text-align: left; padding-right: 15px; position: relative; .ellipsis(); @@ -65,28 +68,32 @@ } } } - + tbody { - display: block; - position:relative; + display: block; + position: relative; overflow-y: scroll; overscroll-behavior: contain; } tr { width: 100%; - display:flex; + display: flex; td { border-right: 1px solid @scrollbar-thumb; border-left: 1px solid @scrollbar-thumb; - padding: 2px 10px; - flex-basis:100%; - flex-grow:2; + padding: 3px 10px; + flex-basis: 100%; + flex-grow: 2; display: block; - text-align:left; + text-align: left; .ellipsis(); } } } -} \ No newline at end of file +} + +.csv-renderer.show { + opacity: 1; /* When loaded class is added, set the opacity to 1, making it visible */ +} diff --git a/src/plugins/csv/csv.tsx b/src/plugins/csv/csv.tsx index f5257351..62f537c9 100644 --- a/src/plugins/csv/csv.tsx +++ b/src/plugins/csv/csv.tsx @@ -1,86 +1,72 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import React, { FC, useEffect, useState, useRef, useMemo } from "react"; import { RendererContext, RendererOpts, LineStateType, RendererModelContainerApi } from "../../types/types"; +import * as T from "../../types/types"; import { GlobalModel } from "../../model/model"; -import Papa from 'papaparse'; +import Papa from "papaparse"; import { createColumnHelper, flexRender, useReactTable, getCoreRowModel, - getFilteredRowModel, getSortedRowModel, FilterFn, - } from '@tanstack/react-table' - import { - rankItem, - } from '@tanstack/match-sorter-utils' -import SortUpIcon from './img/sort-up-solid.svg'; -import SortDownIcon from './img/sort-down-solid.svg'; - +} from "@tanstack/react-table"; +import { useTableNav } from "@table-nav/react"; +import SortUpIcon from "./img/sort-up-solid.svg"; +import SortDownIcon from "./img/sort-down-solid.svg"; +import cn from "classnames"; + import "./csv.less"; -const MAX_DATA_SIZE = 10 * 1024 * 1024 // 10MB in bytes +const MAX_DATA_SIZE = 10 * 1024 * 1024; // 10MB in bytes type CSVRow = { [key: string]: string | number; }; -const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { - // Rank the item - const itemRank = rankItem(row.getValue(columnId), value) - - // Store the itemRank info - addMeta({ - itemRank, - }) - - // Return if the item should be filtered in/out - return itemRank.passed -} - interface Props { - data: Blob; - cmdstr: string; - cwd: string; + data: T.ExtBlob; readOnly: boolean; - notFound: boolean; - exitcode: number; - context: RendererContext; - opts: RendererOpts; + context: T.RendererContext; + opts: T.RendererOpts; savedHeight: number; - scrollToBringIntoViewport: () => void; - lineState: LineStateType; - isSelected: boolean; + lineState: T.LineStateType; shouldFocus: boolean; - rendererApi: RendererModelContainerApi; + rendererApi: T.RendererModelContainerApi; + scrollToBringIntoViewport: () => void; } interface State { content: string | null; - message: { status: string; text: string } | null; - isPreviewerAvailable: boolean; showReadonly: boolean; - totalHeight: number; + tbodyHeight: number; } const columnHelper = createColumnHelper(); const CSVRenderer: FC = (props: Props) => { + const { data, opts, lineState, context, shouldFocus, rendererApi, savedHeight } = props; + const { height: maxHeight } = opts.maxSize; + const csvCacheRef = useRef(new Map()); const rowRef = useRef<(HTMLTableRowElement | null)[]>([]); const headerRef = useRef(null); + const probeRef = useRef(null); + const tbodyRef = useRef(null); const [state, setState] = useState({ content: null, - message: null, - isPreviewerAvailable: false, showReadonly: true, - totalHeight: 0, + tbodyHeight: 0, }); - const [globalFilter, setGlobalFilter] = useState('') const [isFileTooLarge, setIsFileTooLarge] = useState(false); + const [tableLoaded, setTableLoaded] = useState(false); + const { listeners } = useTableNav(); - const filePath = props.lineState["prompt:file"]; - const { screenId, lineId } = props.context; + const filePath = lineState["prompt:file"]; + const { screenId, lineId } = context; const cacheKey = `${screenId}-${lineId}-${filePath}`; // Parse the CSV data @@ -89,7 +75,7 @@ const CSVRenderer: FC = (props: Props) => { // Trim the content and then check for headers based on the first row's content. const trimmedContent = state.content.trim(); - const firstRow = trimmedContent.split('\n')[0]; + const firstRow = trimmedContent.split("\n")[0]; // This checks if the first row starts with a letter or a quote const hasHeaders = !!firstRow.match(/^[a-zA-Z"]/); @@ -98,9 +84,9 @@ const CSVRenderer: FC = (props: Props) => { // Check for non-header CSVs if (!hasHeaders && Array.isArray(results.data) && Array.isArray(results.data[0])) { - const dataArray = results.data as string[][]; // Asserting the type + const dataArray = results.data as string[][]; // Asserting the type const headers = Array.from({ length: dataArray[0].length }, (_, i) => `Column ${i + 1}`); - results.data = dataArray.map(row => { + results.data = dataArray.map((row) => { const newRow: CSVRow = {}; row.forEach((value, index) => { newRow[headers[index]] = value; @@ -108,11 +94,11 @@ const CSVRenderer: FC = (props: Props) => { return newRow; }); } - - return results.data.map(row => { + + return results.data.map((row) => { return Object.fromEntries( Object.entries(row as CSVRow).map(([key, value]) => { - if (typeof value === 'string') { + if (typeof value === "string") { const numberValue = parseFloat(value); if (!isNaN(numberValue) && String(numberValue) === value) { return [key, numberValue]; @@ -130,10 +116,10 @@ const CSVRenderer: FC = (props: Props) => { return []; } const headers = Object.keys(parsedData[0]); - return headers.map(header => + return headers.map((header) => columnHelper.accessor(header, { header: () => header, - cell: info => info.renderValue(), + cell: (info) => info.renderValue(), }) ); }, [parsedData]); @@ -144,61 +130,55 @@ const CSVRenderer: FC = (props: Props) => { setState((prevState) => ({ ...prevState, content })); } else { // Check if the file size exceeds 10MB - if (props.data.size > MAX_DATA_SIZE) { // 10MB in bytes + if (data.size > MAX_DATA_SIZE) { + // 10MB in bytes setIsFileTooLarge(true); return; } - - props.data.text().then((content: string) => { + + data.text().then((content: string) => { setState((prevState) => ({ ...prevState, content })); csvCacheRef.current.set(cacheKey, content); }); } - }, []); + }, []); - // Effect to compute height after rendering useEffect(() => { - if (headerRef.current && rowRef.current && rowRef.current[0]) { - const rowHeight = rowRef.current[0]?.offsetHeight ?? 0; // Using optional chaining - const totalHeight = rowHeight * parsedData.length; - const th = Math.min(totalHeight, props.opts.maxSize.height); + if (probeRef.current && headerRef.current && parsedData.length) { + const rowHeight = probeRef.current.offsetHeight; + const fullTBodyHeight = rowHeight * parsedData.length; + const headerHeight = headerRef.current.offsetHeight; + const maxHeightLessHeader = maxHeight - headerHeight; + const tbodyHeight = Math.min(maxHeightLessHeader, fullTBodyHeight); - setState((prevState) => ({ ...prevState, totalHeight: th })); + setState((prevState) => ({ ...prevState, tbodyHeight })); } - }, [parsedData, props.opts]); - + }, [probeRef, headerRef, maxHeight, parsedData]); - const getMessage = () => ( -
-
- {state.message?.text} -
-
- ); + // Makes sure rows are rendered before setting the renderer as loaded + useEffect(() => { + let timer: any; - const { exitcode } = props; - const { content, message } = state; + if (rowRef.current.length === parsedData.length) { + timer = setTimeout(() => { + setTableLoaded(true); + }, 50); // Delay a bit to make sure the rows are rendered + } + + return () => clearTimeout(timer); + }, [rowRef, parsedData]); + + useEffect(() => { + if (shouldFocus) { + rendererApi.onFocusChanged(true); + } + }, [shouldFocus]); const table = useReactTable({ manualPagination: true, data: parsedData, columns, - filterFns: { - fuzzy: fuzzyFilter, - }, - state: { - globalFilter, - }, - globalFilterFn: fuzzyFilter, - onGlobalFilterChange: setGlobalFilter, getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), }); @@ -210,61 +190,74 @@ const CSVRenderer: FC = (props: Props) => { ); } - if (content == null) return
; - return ( -
- +
+
+ + + + + +
dummy data
+ - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - + {headerGroup.headers.map((header, index) => ( + ))} ))} - + {table.getRowModel().rows.map((row, index) => ( - rowRef.current[index] = el}> - {row.getVisibleCells().map(cell => ( - (rowRef.current[index] = el)} id={row.id} tabIndex={index}> + {row.getVisibleCells().map((cell) => ( + ))} ))}
( +
- {header.isPlaceholder - ? null - : ( -
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - { - header.column.getIsSorted() === 'asc' ? Ascending : - header.column.getIsSorted() === 'desc' ? Descending : null - } -
- )} + {header.isPlaceholder ? null : ( +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {header.column.getIsSorted() === "asc" ? ( + Ascending + ) : header.column.getIsSorted() === "desc" ? ( + Descending + ) : null} +
+ )}
- {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
- {message && getMessage()}
); -} +}; export { CSVRenderer }; diff --git a/src/plugins/csv/icon.svg b/src/plugins/csv/icon.svg new file mode 100644 index 00000000..5377e8f8 --- /dev/null +++ b/src/plugins/csv/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/plugins/csv/meta.json b/src/plugins/csv/meta.json new file mode 100644 index 00000000..63d87873 --- /dev/null +++ b/src/plugins/csv/meta.json @@ -0,0 +1,5 @@ +{ + "title": "CSV Viewer", + "vendor": "Wave", + "summary": "View CSV files inline from within the terminal. I am now trying an animated gif :)" +} diff --git a/src/plugins/csv/screenshots/csvview.gif b/src/plugins/csv/screenshots/csvview.gif new file mode 100644 index 00000000..98c6c8a3 Binary files /dev/null and b/src/plugins/csv/screenshots/csvview.gif differ diff --git a/src/plugins/csv/search.tsx b/src/plugins/csv/search.tsx deleted file mode 100644 index 8a5c891c..00000000 --- a/src/plugins/csv/search.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { FC, useEffect, useState } from "react"; - -const DebouncedInput: FC<{ - value: string | number; - onChange: (value: string | number) => void; - debounce?: number; - } & Omit, 'onChange'>> = ({ - value: initialValue, - onChange, - debounce = 500, - ...props - }) => { - const [value, setValue] = useState(initialValue); - - useEffect(() => { - setValue(initialValue); - }, [initialValue]); - - useEffect(() => { - const timeout = setTimeout(() => { - onChange(value); - }, debounce); - - return () => clearTimeout(timeout); - }, [value]); - - return
- setValue(e.target.value)} /> -
; -}; - -export default DebouncedInput \ No newline at end of file diff --git a/src/plugins/image/icon.svg b/src/plugins/image/icon.svg new file mode 100644 index 00000000..c1637e15 --- /dev/null +++ b/src/plugins/image/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/plugins/image/image.tsx b/src/plugins/image/image.tsx index af8d2c67..dbe77fb2 100644 --- a/src/plugins/image/image.tsx +++ b/src/plugins/image/image.tsx @@ -1,33 +1,19 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobx from "mobx"; import * as mobxReact from "mobx-react"; -import { RendererContext, RendererOpts } from "../../types/types"; +import * as T from "../../types/types"; import "./image.less"; type OV = mobx.IObservableValue; type CV = mobx.IComputedValue; -// ctor(RendererContext, RenderOpts, isDone); -// type RendererModel = { -// dispose : () => void, -// reload : (delayMs : number) => void, -// receiveData : (pos : number, data : Uint8Array, reason? : string) => void, -// cmdDone : () => void, -// resizeWindow : (size : WindowSize) => void, -// resizeCols : (cols : number) => void, -// giveFocus : () => void, -// getUsedRows : () => number, -// }; - -// two types of renderers -// JSON -// blob -// - @mobxReact.observer class SimpleImageRenderer extends React.Component< - { data: Blob; context: RendererContext; opts: RendererOpts; savedHeight: number }, + { data: T.ExtBlob; context: T.RendererContext; opts: T.RendererOpts; savedHeight: number }, {} > { objUrl: string = null; diff --git a/src/plugins/image/meta.json b/src/plugins/image/meta.json new file mode 100644 index 00000000..7b54c93e --- /dev/null +++ b/src/plugins/image/meta.json @@ -0,0 +1,5 @@ +{ + "title": "ImageViewer", + "vendor": "Wave", + "summary": "View Images inline in the terminal." +} diff --git a/src/plugins/image/screenshots/imagviewlion.gif b/src/plugins/image/screenshots/imagviewlion.gif new file mode 100644 index 00000000..786a6bc3 Binary files /dev/null and b/src/plugins/image/screenshots/imagviewlion.gif differ diff --git a/src/plugins/markdown/icon.svg b/src/plugins/markdown/icon.svg new file mode 100644 index 00000000..2d4ebcd0 --- /dev/null +++ b/src/plugins/markdown/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/plugins/markdown/markdown.tsx b/src/plugins/markdown/markdown.tsx index 1871a077..dd720f3f 100644 --- a/src/plugins/markdown/markdown.tsx +++ b/src/plugins/markdown/markdown.tsx @@ -1,7 +1,10 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobx from "mobx"; import * as mobxReact from "mobx-react"; -import { RendererContext, RendererOpts } from "../../types/types"; +import * as T from "../../types/types"; import { sprintf } from "sprintf-js"; import { Markdown } from "../../app/common/common"; @@ -13,7 +16,7 @@ const MaxMarkdownSize = 200000; @mobxReact.observer class SimpleMarkdownRenderer extends React.Component< - { data: Blob; context: RendererContext; opts: RendererOpts; savedHeight: number }, + { data: T.ExtBlob; context: T.RendererContext; opts: T.RendererOpts; savedHeight: number }, {} > { markdownText: OV = mobx.observable.box(null, { name: "markdownText" }); diff --git a/src/plugins/markdown/meta.json b/src/plugins/markdown/meta.json new file mode 100644 index 00000000..6442c41b --- /dev/null +++ b/src/plugins/markdown/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Markdown", + "vendor": "Wave", + "summary": "Markdown is a lightweight markup language for creating formatted text using a plain-text editor." +} diff --git a/src/plugins/mustache/icon.svg b/src/plugins/mustache/icon.svg new file mode 100644 index 00000000..4e11dd0b --- /dev/null +++ b/src/plugins/mustache/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/plugins/mustache/meta.json b/src/plugins/mustache/meta.json new file mode 100644 index 00000000..dc4ac3a7 --- /dev/null +++ b/src/plugins/mustache/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Mustache", + "vendor": "Wave", + "summary": "Mustache templates help create html files." +} diff --git a/src/plugins/mustache/mustache.tsx b/src/plugins/mustache/mustache.tsx index bba34830..971f4f32 100644 --- a/src/plugins/mustache/mustache.tsx +++ b/src/plugins/mustache/mustache.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobx from "mobx"; import * as mobxReact from "mobx-react"; @@ -14,7 +17,7 @@ type OV = mobx.IObservableValue; @mobxReact.observer class SimpleMustacheRenderer extends React.Component< - { data: Blob; context: T.RendererContext; opts: T.RendererOpts; savedHeight: number; lineState: T.LineStateType }, + { data: T.ExtBlob; context: T.RendererContext; opts: T.RendererOpts; savedHeight: number; lineState: T.LineStateType }, {} > { templateLoading: OV = mobx.observable.box(true, { name: "templateLoading" }); diff --git a/src/plugins/openai/icon.svg b/src/plugins/openai/icon.svg new file mode 100644 index 00000000..be5696e3 --- /dev/null +++ b/src/plugins/openai/icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/plugins/openai/meta.json b/src/plugins/openai/meta.json new file mode 100644 index 00000000..eec8cb52 --- /dev/null +++ b/src/plugins/openai/meta.json @@ -0,0 +1,5 @@ +{ + "title": "OpenAI", + "vendor": "Wave", + "summary": "OpenAI plugin allows chatting with OpenAI APIs." +} diff --git a/src/plugins/openai/openai.tsx b/src/plugins/openai/openai.tsx index 68be150a..80bd7354 100644 --- a/src/plugins/openai/openai.tsx +++ b/src/plugins/openai/openai.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobx from "mobx"; import * as mobxReact from "mobx-react"; diff --git a/src/plugins/plugins.ts b/src/plugins/plugins.ts index 60a39874..34cd9caa 100644 --- a/src/plugins/plugins.ts +++ b/src/plugins/plugins.ts @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import { RendererPluginType } from "../types/types"; import { SimpleImageRenderer } from "./image/image"; import { SimpleMarkdownRenderer } from "./markdown/markdown"; @@ -8,89 +11,156 @@ import { OpenAIRenderer, OpenAIRendererModel } from "./openai/openai"; import { isBlank } from "../util/util"; import { sprintf } from "sprintf-js"; -const ImagePlugin: RendererPluginType = { - name: "image", - rendererType: "simple", - heightType: "pixels", - dataType: "blob", - collapseType: "hide", - globalCss: null, - mimeTypes: ["image/*"], - simpleComponent: SimpleImageRenderer, -}; - -const MarkdownPlugin: RendererPluginType = { - name: "markdown", - rendererType: "simple", - heightType: "pixels", - dataType: "blob", - collapseType: "hide", - globalCss: null, - mimeTypes: ["text/markdown"], - simpleComponent: SimpleMarkdownRenderer, -}; - -const MustachePlugin: RendererPluginType = { - name: "mustache", - rendererType: "simple", - heightType: "pixels", - dataType: "blob", - collapseType: "hide", - globalCss: null, - mimeTypes: ["text/plain"], - simpleComponent: SimpleMustacheRenderer, -}; - -const CodePlugin: RendererPluginType = { - name: "code", - rendererType: "simple", - heightType: "pixels", - dataType: "blob", - collapseType: "hide", - globalCss: null, - mimeTypes: ["text/plain"], - simpleComponent: SourceCodeRenderer, -}; - -const OpenAIPlugin: RendererPluginType = { - name: "openai", - rendererType: "full", - heightType: "pixels", - dataType: "model", - collapseType: "remove", - hidePrompt: true, - globalCss: null, - mimeTypes: ["application/json"], - fullComponent: OpenAIRenderer, - modelCtor: () => new OpenAIRendererModel(), -}; - -const CSVPlugin: RendererPluginType = { - name: "csv", - rendererType: "simple", - heightType: "pixels", - dataType: "blob", - collapseType: "hide", - globalCss: null, - mimeTypes: ["text/csv"], - simpleComponent: CSVRenderer, -}; +// TODO: @mike - I did refactoring with the though that I can move config out of this plugins.ts file to a +// plugins.json file. This way, adding a new plugin would reuire adding an entry to the json config. At a later +// stage, a plugin can become a self-contained-bundle, which would have my_plugin.json into it. it will be easy to +// merge this my_plugin.json into the big plugins.json. I got stuck while defining 'simpleComponent: SimpleImageRenderer' +// in a json definition (something like Java.Reflection can be used to compose a class from its name. will try later) +const PluginConfigs: RendererPluginType[] = [ + { + name: "markdown", + rendererType: "simple", + heightType: "pixels", + dataType: "blob", + collapseType: "hide", + globalCss: null, + mimeTypes: ["text/markdown"], + simpleComponent: SimpleMarkdownRenderer, + }, + { + name: "mustache", + rendererType: "simple", + heightType: "pixels", + dataType: "blob", + collapseType: "hide", + globalCss: null, + mimeTypes: ["text/plain"], + simpleComponent: SimpleMustacheRenderer, + }, + { + name: "code", + rendererType: "simple", + heightType: "pixels", + dataType: "blob", + collapseType: "hide", + globalCss: null, + mimeTypes: ["text/plain"], + simpleComponent: SourceCodeRenderer, + }, + { + name: "openai", + rendererType: "full", + heightType: "pixels", + dataType: "model", + collapseType: "remove", + hidePrompt: true, + globalCss: null, + mimeTypes: ["application/json"], + fullComponent: OpenAIRenderer, + modelCtor: () => new OpenAIRendererModel(), + }, + { + name: "csv", + rendererType: "simple", + heightType: "pixels", + dataType: "blob", + collapseType: "hide", + globalCss: null, + mimeTypes: ["text/csv"], + simpleComponent: CSVRenderer, + }, + { + name: "image", + rendererType: "simple", + heightType: "pixels", + dataType: "blob", + collapseType: "hide", + globalCss: null, + mimeTypes: ["image/*"], + simpleComponent: SimpleImageRenderer, + }, +]; class PluginModelClass { rendererPlugins: RendererPluginType[] = []; - registerRendererPlugin(plugin: RendererPluginType) { - if (isBlank(plugin.name)) { - throw new Error("invalid plugin, no name"); + constructor(pluginConfigs: RendererPluginType[]) { + this.rendererPlugins = pluginConfigs.map((plugin: RendererPluginType): RendererPluginType => { + if (isBlank(plugin.name)) { + throw new Error("invalid plugin, no name"); + } + if (plugin.name == "terminal" || plugin.name == "none") { + throw new Error(sprintf("invalid plugin, name '%s' is reserved", plugin.name)); + } + let existingPlugin = this.getRendererPluginByName(plugin.name); + if (existingPlugin != null) { + throw new Error(sprintf("plugin with name %s already registered", plugin.name)); + } + this.rendererPlugins.push(plugin); + this.loadPluginResources(plugin); + return plugin; + }); + } + + // attach all screenshots. webpack doesnt allow dynamic paths, hence, we have to put static paths for each plugin + attachScreenshots(plugin) { + let screenshotsContext; + let imagePaths = []; + try { + switch (plugin.name) { + case "image": + screenshotsContext = require.context(`../plugins/image/screenshots`, false, /\.(png|jpe?g|gif)$/); + break; + case "markdown": + screenshotsContext = require.context( + `../plugins/markdown/screenshots`, + false, + /\.(png|jpe?g|gif)$/ + ); + break; + case "mustache": + screenshotsContext = require.context( + `../plugins/mustache/screenshots`, + false, + /\.(png|jpe?g|gif)$/ + ); + break; + case "code": + screenshotsContext = require.context(`../plugins/code/screenshots`, false, /\.(png|jpe?g|gif)$/); + break; + case "openai": + screenshotsContext = require.context(`../plugins/openai/screenshots`, false, /\.(png|jpe?g|gif)$/); + break; + case "csv": + screenshotsContext = require.context(`../plugins/csv/screenshots`, false, /\.(png|jpe?g|gif)$/); + break; + default: + return; + } + + imagePaths = screenshotsContext.keys().map(screenshotsContext); + } catch (error) { + console.error(`Failed to load screenshots for plugin ${plugin.name}`); } - if (plugin.name == "terminal" || plugin.name == "none") { - throw new Error(sprintf("invalid plugin, name '%s' is reserved", plugin.name)); - } - let existingPlugin = this.getRendererPluginByName(plugin.name); - if (existingPlugin != null) { - throw new Error(sprintf("plugin with name %s already registered", plugin.name)); - } - this.rendererPlugins.push(plugin); + plugin.screenshots = imagePaths.map((path) => path.default); + } + + // use dynamic import to attach the icon etc. ensure that the 'name' matches the dir the plugin is in + async loadPluginResources(plugin) { + this.attachScreenshots(plugin); + // attach other resources + const handleImportError = (error, resourceType) => + console.error(`Failed to load ${resourceType} for plugin ${plugin.name}`); + const iconPromise = import(`../plugins/${plugin.name}/icon.svg`) + .then((icon) => (plugin.iconComp = icon.ReactComponent)) + .catch((error) => handleImportError(error, "icon")); + const readmePromise = import(`../plugins/${plugin.name}/readme.md`) + .then((content) => (plugin.readme = content.default)) + .catch((error) => handleImportError(error, "readme")); + const metaPromise = import(`../plugins/${plugin.name}/meta.json`) + .then((json) => Object.assign(plugin, json)) + .catch((error) => handleImportError(error, "meta")); + return Promise.allSettled([iconPromise, readmePromise, metaPromise]); } getRendererPluginByName(name: string): RendererPluginType { @@ -102,17 +172,15 @@ class PluginModelClass { } return null; } + + allPlugins() { + return this.rendererPlugins; + } } let PluginModel: PluginModelClass = null; if ((window as any).PluginModel == null) { - PluginModel = new PluginModelClass(); - PluginModel.registerRendererPlugin(ImagePlugin); - PluginModel.registerRendererPlugin(MarkdownPlugin); - PluginModel.registerRendererPlugin(CodePlugin); - PluginModel.registerRendererPlugin(OpenAIPlugin); - PluginModel.registerRendererPlugin(MustachePlugin); - PluginModel.registerRendererPlugin(CSVPlugin); + PluginModel = new PluginModelClass(PluginConfigs); (window as any).PluginModel = PluginModel; } diff --git a/src/plugins/terminal/term.ts b/src/plugins/terminal/term.ts index 5689cec1..38e0571c 100644 --- a/src/plugins/terminal/term.ts +++ b/src/plugins/terminal/term.ts @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as mobx from "mobx"; import { Terminal } from "xterm"; import { sprintf } from "sprintf-js"; @@ -185,6 +188,10 @@ class TermWrap { let termNumLines = termBuf.lines.length; let termYPos = termBuf.y; if (termNumLines > term.rows) { + // TODO: there is a weird case here. for commands that output more than term.rows rows of output + // they get an "extra" blank line at the bottom because the cursor is positioned on the next line! + // hard problem to solve because the line is already written to the buffer. we only want to "fix" + // this when the command is no longer running. return term.rows; } let usedRows = this.isRunning ? 1 : 0; diff --git a/src/plugins/terminal/terminal.tsx b/src/plugins/terminal/terminal.tsx index d4185886..2e3c657d 100644 --- a/src/plugins/terminal/terminal.tsx +++ b/src/plugins/terminal/terminal.tsx @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; @@ -150,8 +153,7 @@ class TerminalRenderer extends React.Component< .get(); let cmd = screen.getCmd(line); // will not be null let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); - // TODO: replace the +2 with some calculation based on termFontSize. the +2 is for descenders, which get cut off without this. - let termHeight = termHeightFromRows(usedRows, GlobalModel.termFontSize.get()) + 2; + let termHeight = termHeightFromRows(usedRows, GlobalModel.termFontSize.get()); if (usedRows === 0) { termHeight = 0; } diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts new file mode 100644 index 00000000..3b9a9c8f --- /dev/null +++ b/src/types/custom.d.ts @@ -0,0 +1,6 @@ +declare module '*.svg' { + import * as React from 'react'; + export const ReactComponent: React.FunctionComponent & { title?: string }>; + const src: string; + export default src; +} diff --git a/src/types/svg.d.ts b/src/types/svg.d.ts index ba70195c..35086723 100644 --- a/src/types/svg.d.ts +++ b/src/types/svg.d.ts @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + declare module "*.svg" { import React from "react"; export const ReactComponent: React.FunctionComponent & { title?: string }>; diff --git a/src/types/types.ts b/src/types/types.ts index 709234cb..ddd50d2f 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as React from "react"; import * as mobx from "mobx"; @@ -378,6 +381,12 @@ type RendererPluginType = { modelCtor?: () => RendererModel; simpleComponent?: SimpleBlobRendererComponent; fullComponent?: FullRendererComponent; + readme?: string; + screenshots?: any[]; + vendor?: string; + summary?: string; + title?: string; + iconComp?: React.Component<{}, {}>; }; type RendererModelContainerApi = { @@ -409,7 +418,7 @@ type RendererModel = { }; type SimpleBlobRendererComponent = React.ComponentType<{ - data: Blob; + data: ExtBlob; readOnly?: boolean; notFound?: boolean; isSelected?: boolean; @@ -621,6 +630,14 @@ type FileInfoType = { notfound: boolean; }; +type ExtBlob = Blob & { + notFound: boolean; +}; + +type ExtFile = File & { + notFound: boolean; +}; + export type { SessionDataType, LineStateType, @@ -690,4 +707,6 @@ export type { CommandRtnType, OpenAIPacketType, FileInfoType, + ExtBlob, + ExtFile, }; diff --git a/src/util/textmeasure.ts b/src/util/textmeasure.ts index 211ee8e7..0c37e970 100644 --- a/src/util/textmeasure.ts +++ b/src/util/textmeasure.ts @@ -1,4 +1,8 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import { boundInt } from "./util"; +import { MagicLayout } from "../app/magiclayout"; const MinTermCols = 10; const MaxTermCols = 1024; @@ -59,14 +63,14 @@ function measureText( function windowWidthToCols(width: number, fontSize: number): number { let dr = getMonoFontSize(fontSize); - let cols = Math.trunc((width - 50) / dr.width) - 1; + let cols = Math.trunc((width - MagicLayout.ScreenMaxContentWidthBuffer) / dr.width) - 1; cols = boundInt(cols, MinTermCols, MaxTermCols); return cols; } function windowHeightToRows(height: number, fontSize: number): number { let dr = getMonoFontSize(fontSize); - let rows = Math.floor((height - 80) / dr.height) - 1; + let rows = Math.floor((height - MagicLayout.ScreenMaxContentHeightBuffer) / dr.height) - 1; if (rows <= 0) { rows = 1; } @@ -75,12 +79,13 @@ function windowHeightToRows(height: number, fontSize: number): number { function termWidthFromCols(cols: number, fontSize: number): number { let dr = getMonoFontSize(fontSize); - return Math.ceil(dr.width * cols) + 15; + return Math.ceil(dr.width * cols) + MagicLayout.TermWidthBuffer; } function termHeightFromRows(rows: number, fontSize: number): number { let dr = getMonoFontSize(fontSize); - return Math.ceil(dr.height * rows); + // TODO: replace the TermDescendersHeight with some calculation based on termFontSize. + return Math.ceil(dr.height * rows) + MagicLayout.TermDescendersHeight; } export { measureText, getMonoFontSize, windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows }; diff --git a/src/util/util.ts b/src/util/util.ts index 6a7993b7..477f9248 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -1,8 +1,13 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import * as mobx from "mobx"; import { sprintf } from "sprintf-js"; import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; -import type { RemoteType } from "../types/types"; +import type { RemoteType, CommandRtnType } from "../types/types"; + +type OV = mobx.IObservableValue; dayjs.extend(localizedFormat); @@ -398,6 +403,17 @@ function generateBackgroundWithGradient(colorName = "white", decay = 3) { return `linear-gradient(180deg, rgba(${r}, ${g}, ${b}, ${opacities[0]}) ${percentages[0]}%, rgba(${r}, ${g}, ${b}, ${opacities[1]}) ${percentages[1]}%, rgba(${r}, ${g}, ${b}, 0) ${percentages[2]}%)`; } +function commandRtnHandler(prtn: Promise, errorMessage: OV) { + prtn.then((crtn) => { + if (crtn.success) { + return; + } + mobx.action(() => { + errorMessage.set(crtn.error); + })(); + }); +} + export { handleJsonFetchResponse, base64ToArray, @@ -421,4 +437,5 @@ export { openLink, generateBackgroundWithGradient, getColorRGB, + commandRtnHandler, }; diff --git a/version.js b/version.js index c73424dd..2f7a3581 100644 --- a/version.js +++ b/version.js @@ -1,2 +1,2 @@ -const VERSION = "v0.4.0"; +const VERSION = "v0.5.0"; module.exports = VERSION; diff --git a/waveshell/main-waveshell.go b/waveshell/main-waveshell.go index 086ad0be..d861cf77 100644 --- a/waveshell/main-waveshell.go +++ b/waveshell/main-waveshell.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package main import ( @@ -539,15 +542,6 @@ func main() { } else if firstArg == "--version" { fmt.Printf("mshell %s+%s\n", base.MShellVersion, base.BuildTime) return - } else if firstArg == "--test-env" { - state, err := shexec.GetShellState() - if state != nil { - - } - if err != nil { - fmt.Fprintf(os.Stderr, "[error] %v\n", err) - os.Exit(1) - } } else if firstArg == "--single" { base.InitDebugLog("single") handleSingle(false) diff --git a/waveshell/pkg/base/base.go b/waveshell/pkg/base/base.go index d9204f4f..7782d62f 100644 --- a/waveshell/pkg/base/base.go +++ b/waveshell/pkg/base/base.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package base import ( @@ -26,6 +29,7 @@ const MShellInstallBinVarName = "MSHELL_INSTALLBIN_PATH" const SSHCommandVarName = "SSH_COMMAND" const MShellDebugVarName = "MSHELL_DEBUG" const SessionsDirBaseName = "sessions" +const RcFilesDirBaseName = "rcfiles" const MShellVersion = "v0.3.0" const RemoteIdFile = "remoteid" const DefaultMShellInstallBinDir = "/opt/mshell/bin" @@ -35,7 +39,8 @@ const ForceDebugLog = false const DebugFlag_LogRcFile = "logrc" const LogRcFileName = "debug.rcfile" -var sessionDirCache = make(map[string]string) +// keys are sessionids (also the key RcFilesDirBaseName) +var ensureDirCache = make(map[string]bool) var baseLock = &sync.Mutex{} var DebugLogEnabled = false var DebugLogger *log.Logger @@ -222,35 +227,26 @@ func GetSessionsDir() string { return sdir } +func EnsureRcFilesDir() (string, error) { + mhome := GetMShellHomeDir() + dirName := path.Join(mhome, RcFilesDirBaseName) + err := CacheEnsureDir(dirName, RcFilesDirBaseName, 0700, "rcfiles dir") + if err != nil { + return "", err + } + return dirName, nil +} + func EnsureSessionDir(sessionId string) (string, error) { if sessionId == "" { return "", fmt.Errorf("Bad sessionid, cannot be empty") } - baseLock.Lock() - sdir, ok := sessionDirCache[sessionId] - baseLock.Unlock() - if ok { - return sdir, nil - } mhome := GetMShellHomeDir() - sdir = path.Join(mhome, SessionsDirBaseName, sessionId) - info, err := os.Stat(sdir) - if errors.Is(err, fs.ErrNotExist) { - err = os.MkdirAll(sdir, 0777) - if err != nil { - return "", fmt.Errorf("cannot make mshell session directory[%s]: %w", sdir, err) - } - info, err = os.Stat(sdir) - } + sdir := path.Join(mhome, SessionsDirBaseName, sessionId) + err := CacheEnsureDir(sdir, sessionId, 0777, "mshell session dir") if err != nil { return "", err } - if !info.IsDir() { - return "", fmt.Errorf("session dir '%s' must be a directory", sdir) - } - baseLock.Lock() - sessionDirCache[sessionId] = sdir - baseLock.Unlock() return sdir, nil } @@ -379,3 +375,38 @@ func BoundInt64(ival int64, minVal int64, maxVal int64) int64 { } return ival } + +func CacheEnsureDir(dirName string, cacheKey string, perm os.FileMode, dirDesc string) error { + baseLock.Lock() + ok := ensureDirCache[cacheKey] + baseLock.Unlock() + if ok { + return nil + } + err := TryMkdirs(dirName, perm, dirDesc) + if err != nil { + return err + } + baseLock.Lock() + ensureDirCache[cacheKey] = true + baseLock.Unlock() + return nil +} + +func TryMkdirs(dirName string, perm os.FileMode, dirDesc string) error { + info, err := os.Stat(dirName) + if errors.Is(err, fs.ErrNotExist) { + err = os.MkdirAll(dirName, perm) + if err != nil { + return fmt.Errorf("cannot make %s %q: %w", dirDesc, dirName, err) + } + info, err = os.Stat(dirName) + } + if err != nil { + return fmt.Errorf("error trying to stat %s: %w", dirDesc, err) + } + if !info.IsDir() { + return fmt.Errorf("%s %q must be a directory", dirDesc, dirName) + } + return nil +} diff --git a/waveshell/pkg/base/optsiter.go b/waveshell/pkg/base/optsiter.go index f607c670..c9e33e16 100644 --- a/waveshell/pkg/base/optsiter.go +++ b/waveshell/pkg/base/optsiter.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package base import "strings" diff --git a/waveshell/pkg/binpack/binpack.go b/waveshell/pkg/binpack/binpack.go index 67e25337..50d4a015 100644 --- a/waveshell/pkg/binpack/binpack.go +++ b/waveshell/pkg/binpack/binpack.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package binpack import ( diff --git a/waveshell/pkg/cirfile/cirfile.go b/waveshell/pkg/cirfile/cirfile.go index fef9a764..4706c565 100644 --- a/waveshell/pkg/cirfile/cirfile.go +++ b/waveshell/pkg/cirfile/cirfile.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package cirfile import ( diff --git a/waveshell/pkg/cirfile/cirfile_test.go b/waveshell/pkg/cirfile/cirfile_test.go index d38cb09a..65f473f4 100644 --- a/waveshell/pkg/cirfile/cirfile_test.go +++ b/waveshell/pkg/cirfile/cirfile_test.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package cirfile import ( diff --git a/waveshell/pkg/cmdtail/cmdtail.go b/waveshell/pkg/cmdtail/cmdtail.go index f60ed67e..83fa92b3 100644 --- a/waveshell/pkg/cmdtail/cmdtail.go +++ b/waveshell/pkg/cmdtail/cmdtail.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package cmdtail import ( diff --git a/waveshell/pkg/mpio/bufreader.go b/waveshell/pkg/mpio/bufreader.go index f3ad32e8..1dba54ed 100644 --- a/waveshell/pkg/mpio/bufreader.go +++ b/waveshell/pkg/mpio/bufreader.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package mpio import ( diff --git a/waveshell/pkg/mpio/bufwriter.go b/waveshell/pkg/mpio/bufwriter.go index 86d2efa3..2aeca503 100644 --- a/waveshell/pkg/mpio/bufwriter.go +++ b/waveshell/pkg/mpio/bufwriter.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package mpio import ( diff --git a/waveshell/pkg/mpio/mpio.go b/waveshell/pkg/mpio/mpio.go index d58dd5f6..d75d1cc3 100644 --- a/waveshell/pkg/mpio/mpio.go +++ b/waveshell/pkg/mpio/mpio.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package mpio import ( diff --git a/waveshell/pkg/packet/combined.go b/waveshell/pkg/packet/combined.go index 0f15fd93..76ce0948 100644 --- a/waveshell/pkg/packet/combined.go +++ b/waveshell/pkg/packet/combined.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package packet type CombinedPacket struct { diff --git a/waveshell/pkg/packet/packet.go b/waveshell/pkg/packet/packet.go index 88ce0420..7b9352b3 100644 --- a/waveshell/pkg/packet/packet.go +++ b/waveshell/pkg/packet/packet.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package packet import ( diff --git a/waveshell/pkg/packet/parser.go b/waveshell/pkg/packet/parser.go index f84be258..8b1a207a 100644 --- a/waveshell/pkg/packet/parser.go +++ b/waveshell/pkg/packet/parser.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package packet import ( diff --git a/waveshell/pkg/packet/shellstate.go b/waveshell/pkg/packet/shellstate.go index 33907238..3fc5ba16 100644 --- a/waveshell/pkg/packet/shellstate.go +++ b/waveshell/pkg/packet/shellstate.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package packet import ( @@ -6,9 +9,11 @@ import ( "encoding/base64" "encoding/json" "fmt" + "strings" "github.com/wavetermdev/waveterm/waveshell/pkg/binpack" "github.com/wavetermdev/waveterm/waveshell/pkg/statediff" + "golang.org/x/mod/semver" ) const ShellStatePackVersion = 0 @@ -60,6 +65,18 @@ func (state ShellState) EncodeAndHash() (string, []byte) { return sha1Hash(buf.Bytes()), buf.Bytes() } +// returns a string like "v4" ("" is an unparseable version) +func GetBashMajorVersion(versionStr string) string { + if versionStr == "" { + return "" + } + fields := strings.Split(versionStr, " ") + if len(fields) < 2 { + return "" + } + return semver.Major(fields[1]) +} + func (state ShellState) MarshalJSON() ([]byte, error) { _, encodedBytes := state.EncodeAndHash() return json.Marshal(encodedBytes) diff --git a/waveshell/pkg/server/server.go b/waveshell/pkg/server/server.go index fa0bcb5b..a31016e8 100644 --- a/waveshell/pkg/server/server.go +++ b/waveshell/pkg/server/server.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package server import ( diff --git a/waveshell/pkg/shexec/client.go b/waveshell/pkg/shexec/client.go index 14d8c9d9..ab9e0c02 100644 --- a/waveshell/pkg/shexec/client.go +++ b/waveshell/pkg/shexec/client.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package shexec import ( diff --git a/waveshell/pkg/shexec/parser.go b/waveshell/pkg/shexec/parser.go index 215a4c05..6a91dbe0 100644 --- a/waveshell/pkg/shexec/parser.go +++ b/waveshell/pkg/shexec/parser.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package shexec import ( diff --git a/waveshell/pkg/shexec/shexec.go b/waveshell/pkg/shexec/shexec.go index d5912194..3069d368 100644 --- a/waveshell/pkg/shexec/shexec.go +++ b/waveshell/pkg/shexec/shexec.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package shexec import ( @@ -10,6 +13,7 @@ import ( "os/exec" "os/signal" "os/user" + "path" "runtime" "strconv" "strings" @@ -18,11 +22,12 @@ import ( "time" "github.com/alessio/shellescape" + "github.com/creack/pty" + "github.com/google/uuid" "github.com/wavetermdev/waveterm/waveshell/pkg/base" "github.com/wavetermdev/waveterm/waveshell/pkg/cirfile" "github.com/wavetermdev/waveterm/waveshell/pkg/mpio" "github.com/wavetermdev/waveterm/waveshell/pkg/packet" - "github.com/creack/pty" "golang.org/x/mod/semver" "golang.org/x/sys/unix" ) @@ -47,6 +52,12 @@ const GetStateTimeout = 5 * time.Second const BaseBashOpts = `set +m; set +H; shopt -s extglob` +const ShellVersionCmdStr = `echo bash v${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]}` + +// do not use these directly, call GetLocalBashMajorVersion() +var LocalBashMajorVersionOnce = &sync.Once{} +var LocalBashMajorVersion = "" + var GetShellStateCmds = []string{ `echo bash v${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]};`, `pwd;`, @@ -122,6 +133,7 @@ type ShExecType struct { MsgSender *packet.PacketSender // where to send out-of-band messages back to calling proceess ReturnState *ReturnStateBuf Exited bool // locked via Lock + TmpRcFileName string } type StdContext struct{} @@ -237,6 +249,9 @@ func (c *ShExecType) Close() { if c.ReturnState != nil { c.ReturnState.Reader.Close() } + if c.TmpRcFileName != "" { + os.Remove(c.TmpRcFileName) + } } func (c *ShExecType) MakeCmdStartPacket(reqId string) *packet.CmdStartPacketType { @@ -1084,14 +1099,37 @@ func RunCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender, fro base.Logf("error writing %s: %v\n", debugRcFileName, err) } } - rcFileFdNum, err := AddRunData(pk, rcFileStr, "rcfile") - if err != nil { - return nil, err + bashVersion := GetLocalBashMajorVersion() + isOldBashVersion := (semver.Compare(bashVersion, "v4") < 0) + var rcFileName string + if isOldBashVersion { + rcFileDir, err := base.EnsureRcFilesDir() + if err != nil { + return nil, err + } + rcFileName = path.Join(rcFileDir, uuid.New().String()) + err = os.WriteFile(rcFileName, []byte(rcFileStr), 0600) + if err != nil { + return nil, fmt.Errorf("could not write temp rcfile: %w", err) + } + cmd.TmpRcFileName = rcFileName + go func() { + // cmd.Close() will also remove rcFileName + // adding this to also try to proactively clean up after 1-second. + time.Sleep(1 * time.Second) + os.Remove(rcFileName) + }() + } else { + rcFileFdNum, err := AddRunData(pk, rcFileStr, "rcfile") + if err != nil { + return nil, err + } + rcFileName = fmt.Sprintf("/dev/fd/%d", rcFileFdNum) } if pk.UsePty { - cmd.Cmd = exec.Command("bash", "--rcfile", fmt.Sprintf("/dev/fd/%d", rcFileFdNum), "-i", "-c", pk.Command) + cmd.Cmd = exec.Command("bash", "--rcfile", rcFileName, "-i", "-c", pk.Command) } else { - cmd.Cmd = exec.Command("bash", "--rcfile", fmt.Sprintf("/dev/fd/%d", rcFileFdNum), "-c", pk.Command) + cmd.Cmd = exec.Command("bash", "--rcfile", rcFileName, "-c", pk.Command) } if !pk.StateComplete { cmd.Cmd.Env = os.Environ() @@ -1100,7 +1138,7 @@ func RunCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender, fro if state.Cwd != "" { cmd.Cmd.Dir = base.ExpandHomeDir(state.Cwd) } - err = ValidateRemoteFds(pk.Fds) + err := ValidateRemoteFds(pk.Fds) if err != nil { return nil, err } @@ -1521,7 +1559,8 @@ func GetShellStateRedirectCommandStr(outputFdNum int) string { } func GetShellState() (*packet.ShellState, error) { - ctx, _ := context.WithTimeout(context.Background(), GetStateTimeout) + ctx, cancelFn := context.WithTimeout(context.Background(), GetStateTimeout) + defer cancelFn() cmdStr := BaseBashOpts + "; " + GetShellStateCmd() ecmd := exec.CommandContext(ctx, "bash", "-l", "-i", "-c", cmdStr) outputBytes, err := runSimpleCmdInPty(ecmd) @@ -1540,3 +1579,27 @@ func MShellEnvVars(termType string) map[string]string { rtn["MSHELL_VERSION"] = base.MShellVersion return rtn } + +func ExecGetLocalShellVersion() string { + ctx, cancelFn := context.WithTimeout(context.Background(), GetStateTimeout) + defer cancelFn() + ecmd := exec.CommandContext(ctx, "bash", "-c", ShellVersionCmdStr) + out, err := ecmd.Output() + if err != nil { + return "" + } + versionStr := strings.TrimSpace(string(out)) + if strings.Index(versionStr, "bash ") == -1 { + // invalid shell version (only bash is supported) + return "" + } + return versionStr +} + +func GetLocalBashMajorVersion() string { + LocalBashMajorVersionOnce.Do(func() { + fullVersion := ExecGetLocalShellVersion() + LocalBashMajorVersion = packet.GetBashMajorVersion(fullVersion) + }) + return LocalBashMajorVersion +} diff --git a/waveshell/pkg/simpleexpand/simpleexpand.go b/waveshell/pkg/simpleexpand/simpleexpand.go index d92d35c4..ddd63f1c 100644 --- a/waveshell/pkg/simpleexpand/simpleexpand.go +++ b/waveshell/pkg/simpleexpand/simpleexpand.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package simpleexpand import ( diff --git a/waveshell/pkg/statediff/linediff.go b/waveshell/pkg/statediff/linediff.go index fa7ce11b..3032a43a 100644 --- a/waveshell/pkg/statediff/linediff.go +++ b/waveshell/pkg/statediff/linediff.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package statediff import ( diff --git a/waveshell/pkg/statediff/mapdiff.go b/waveshell/pkg/statediff/mapdiff.go index 47db9f4b..b88628bb 100644 --- a/waveshell/pkg/statediff/mapdiff.go +++ b/waveshell/pkg/statediff/mapdiff.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package statediff import ( diff --git a/waveshell/pkg/statediff/statediff_test.go b/waveshell/pkg/statediff/statediff_test.go index 34dc478c..c9f706f8 100644 --- a/waveshell/pkg/statediff/statediff_test.go +++ b/waveshell/pkg/statediff/statediff_test.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package statediff import ( diff --git a/wavesrv/cmd/main-server.go b/wavesrv/cmd/main-server.go index 62806737..ca9a5fdd 100644 --- a/wavesrv/cmd/main-server.go +++ b/wavesrv/cmd/main-server.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package main import ( diff --git a/wavesrv/db/db.go b/wavesrv/db/db.go index de43f7c6..b83bbe33 100644 --- a/wavesrv/db/db.go +++ b/wavesrv/db/db.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + // provides the io/fs for DB migrations package db diff --git a/wavesrv/db/migrations/000023_autoinstall.down.sql b/wavesrv/db/migrations/000023_autoinstall.down.sql new file mode 100644 index 00000000..b4eaa212 --- /dev/null +++ b/wavesrv/db/migrations/000023_autoinstall.down.sql @@ -0,0 +1 @@ +-- no down migration diff --git a/wavesrv/db/migrations/000023_autoinstall.up.sql b/wavesrv/db/migrations/000023_autoinstall.up.sql new file mode 100644 index 00000000..d9eacd9e --- /dev/null +++ b/wavesrv/db/migrations/000023_autoinstall.up.sql @@ -0,0 +1 @@ +UPDATE remote SET autoinstall = 1; diff --git a/wavesrv/db/migrations/000024_updatetabcolornames.down.sql b/wavesrv/db/migrations/000024_updatetabcolornames.down.sql new file mode 100644 index 00000000..a08e72a5 --- /dev/null +++ b/wavesrv/db/migrations/000024_updatetabcolornames.down.sql @@ -0,0 +1 @@ +-- no down migration \ No newline at end of file diff --git a/wavesrv/db/migrations/000024_updatetabcolornames.up.sql b/wavesrv/db/migrations/000024_updatetabcolornames.up.sql new file mode 100644 index 00000000..92563172 --- /dev/null +++ b/wavesrv/db/migrations/000024_updatetabcolornames.up.sql @@ -0,0 +1,3 @@ +UPDATE screen +SET screenopts = json_set(screenopts, '$.tabcolor', 'default') +WHERE json_extract(screenopts, '$.tabcolor') = 'black'; diff --git a/wavesrv/go.mod b/wavesrv/go.mod index 2260f484..4d000883 100644 --- a/wavesrv/go.mod +++ b/wavesrv/go.mod @@ -3,7 +3,6 @@ module github.com/wavetermdev/waveterm/wavesrv go 1.18 require ( - github.com/wavetermdev/waveterm/waveshell v0.0.0 github.com/alessio/shellescape v1.4.1 github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 github.com/creack/pty v1.1.18 @@ -15,6 +14,7 @@ require ( github.com/mattn/go-sqlite3 v1.14.16 github.com/sashabaranov/go-openai v1.9.0 github.com/sawka/txwrap v0.1.2 + github.com/wavetermdev/waveterm/waveshell v0.0.0 golang.org/x/crypto v0.7.0 golang.org/x/mod v0.10.0 golang.org/x/sys v0.10.0 diff --git a/wavesrv/go.sum b/wavesrv/go.sum index 7c819d2f..c336d344 100644 --- a/wavesrv/go.sum +++ b/wavesrv/go.sum @@ -8,13 +8,11 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= -github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= @@ -29,19 +27,15 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= -github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sashabaranov/go-openai v1.9.0 h1:NoiO++IISxxJ1pRc0n7uZvMGMake0G+FJ1XPwXtprsA= github.com/sashabaranov/go-openai v1.9.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sawka/txwrap v0.1.2 h1:v8xS0Z1LE7/6vMZA81PYihI+0TSR6Zm1MalzzBIuXKc= @@ -49,7 +43,6 @@ github.com/sawka/txwrap v0.1.2/go.mod h1:T3nlw2gVpuolo6/XEetvBbk1oMXnY978YmBFy1U github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= @@ -59,6 +52,5 @@ golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index 16822107..316ee40e 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package cmdrunner import ( @@ -18,6 +21,7 @@ import ( "time" "unicode" + "github.com/google/uuid" "github.com/wavetermdev/waveterm/waveshell/pkg/base" "github.com/wavetermdev/waveterm/waveshell/pkg/packet" "github.com/wavetermdev/waveterm/waveshell/pkg/shexec" @@ -30,7 +34,6 @@ import ( "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore" "github.com/wavetermdev/waveterm/wavesrv/pkg/utilfn" - "github.com/google/uuid" ) const ( @@ -68,7 +71,7 @@ const ( KwArgLang = "lang" ) -var ColorNames = []string{"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"} +var ColorNames = []string{"yellow", "blue", "pink", "magenta", "cyan", "violet", "orange", "green", "red", "white"} var RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"} var RemoteSetArgs = []string{"alias", "connectmode", "key", "password", "autoinstall", "color"} @@ -1044,7 +1047,6 @@ func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType, isLocal b err := fmt.Errorf("invalid connectmode %q: valid modes are %s", connectMode, formatStrs([]string{sstore.ConnectModeStartup, sstore.ConnectModeAuto, sstore.ConnectModeManual}, "or", false)) return nil, err } - autoInstall := resolveBool(pk.Kwargs["autoinstall"], true) keyFile, err := resolveFile(pk.Kwargs["key"]) if err != nil { return nil, fmt.Errorf("invalid ssh keyfile %q: %v", pk.Kwargs["key"], err) @@ -1073,9 +1075,6 @@ func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType, isLocal b } editMap[sstore.RemoteField_ConnectMode] = connectMode } - if _, found := pk.Kwargs[sstore.RemoteField_AutoInstall]; found { - editMap[sstore.RemoteField_AutoInstall] = autoInstall - } if _, found := pk.Kwargs["key"]; found { if isLocal { return nil, fmt.Errorf("Cannot edit ssh key file for 'local' remote") @@ -1096,7 +1095,7 @@ func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType, isLocal b SSHOpts: sshOpts, ConnectMode: connectMode, Alias: alias, - AutoInstall: autoInstall, + AutoInstall: true, CanonicalName: canonicalName, SSHKeyFile: keyFile, SSHPassword: sshPassword, @@ -1113,7 +1112,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } editArgs, err := parseRemoteEditArgs(true, pk, false) if err != nil { - return makeRemoteEditErrorReturn_new(visualEdit, fmt.Errorf("/remote:new %v", err)) + return nil, fmt.Errorf("/remote:new %v", err) } r := &sstore.RemoteType{ RemoteId: scbase.GenPromptUUID(), @@ -1131,7 +1130,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } err = remote.AddRemote(ctx, r, true) if err != nil { - return makeRemoteEditErrorReturn_new(visualEdit, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err)) + return nil, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err) } // SUCCESS return &sstore.ModelUpdate{ @@ -1577,6 +1576,18 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up if err != nil { return nil, fmt.Errorf("/%s error: cannot update curremote: %w", GetCmdStr(pk), err) } + noHist := resolveBool(pk.Kwargs["nohist"], false) + if noHist { + screen, err := sstore.GetScreenById(ctx, ids.ScreenId) + if err != nil { + return nil, fmt.Errorf("/% error: cannot resolve screen for update: %w", err) + } + update := &sstore.ModelUpdate{ + Screens: []*sstore.ScreenType{screen}, + Interactive: pk.Interactive, + } + return update, nil + } outputStr := fmt.Sprintf("connected to %s", GetFullRemoteDisplayName(rptr, rstate)) cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr)) if err != nil { @@ -1727,10 +1738,22 @@ func simpleCompCommandMeta(ctx context.Context, prefix string, compCtx comp.Comp compsMeta, _ := simpleCompMeta(ctx, prefix, compCtx, nil) return comp.CombineCompReturn(comp.CGTypeCommandMeta, compsCmd, compsMeta), nil } else { - return comp.DoSimpleComp(ctx, comp.CGTypeCommand, prefix, compCtx, nil) + compsCmd, _ := comp.DoSimpleComp(ctx, comp.CGTypeCommand, prefix, compCtx, nil) + compsBareCmd, _ := simpleCompBareCmds(ctx, prefix, compCtx, nil) + return comp.CombineCompReturn(comp.CGTypeCommand, compsCmd, compsBareCmd), nil } } +func simpleCompBareCmds(ctx context.Context, prefix string, compCtx comp.CompContext, args []interface{}) (*comp.CompReturn, error) { + rtn := comp.CompReturn{} + for _, bmc := range BareMetaCmds { + if strings.HasPrefix(bmc.CmdStr, prefix) { + rtn.Entries = append(rtn.Entries, comp.CompEntry{Word: bmc.CmdStr, IsMetaCmd: true}) + } + } + return &rtn, nil +} + func simpleCompMeta(ctx context.Context, prefix string, compCtx comp.CompContext, args []interface{}) (*comp.CompReturn, error) { rtn := comp.CompReturn{} validCommands := getValidCommands() @@ -3654,12 +3677,13 @@ func setNoTelemetry(ctx context.Context, clientData *sstore.ClientData, noTeleme return fmt.Errorf("error trying to update client telemetry: %v", err) } log.Printf("client no-telemetry setting updated to %v\n", noTelemetryVal) - err = pcloud.SendNoTelemetryUpdate(ctx, clientOpts.NoTelemetry) - if err != nil { - // ignore error, just log - log.Printf("[error] sending no-telemetry update: %v\n", err) - log.Printf("note that telemetry update has still taken effect locally, and will be respected by the client\n") - } + go func() { + err := pcloud.SendNoTelemetryUpdate(ctx, clientOpts.NoTelemetry) + if err != nil { + log.Printf("[error] sending no-telemetry update: %v\n", err) + log.Printf("note that telemetry update has still taken effect locally, and will be respected by the client\n") + } + }() return nil } @@ -3675,11 +3699,13 @@ func TelemetryOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( if err != nil { return nil, err } - err = pcloud.SendTelemetry(ctx, false) - if err != nil { - // ignore error, but log - log.Printf("[error] sending telemetry update (in /telemetry:on): %v\n", err) - } + go func() { + err := pcloud.SendTelemetry(ctx, false) + if err != nil { + // ignore error, but log + log.Printf("[error] sending telemetry update (in /telemetry:on): %v\n", err) + } + }() clientData, err = sstore.EnsureClientData(ctx) if err != nil { return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) diff --git a/wavesrv/pkg/cmdrunner/resolver.go b/wavesrv/pkg/cmdrunner/resolver.go index fabd0e87..43ed6c43 100644 --- a/wavesrv/pkg/cmdrunner/resolver.go +++ b/wavesrv/pkg/cmdrunner/resolver.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package cmdrunner import ( diff --git a/wavesrv/pkg/cmdrunner/shparse.go b/wavesrv/pkg/cmdrunner/shparse.go index a37ec7c7..b75d5a73 100644 --- a/wavesrv/pkg/cmdrunner/shparse.go +++ b/wavesrv/pkg/cmdrunner/shparse.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package cmdrunner import ( diff --git a/wavesrv/pkg/cmdrunner/shparse_test.go b/wavesrv/pkg/cmdrunner/shparse_test.go index 682d028e..03b4ebc9 100644 --- a/wavesrv/pkg/cmdrunner/shparse_test.go +++ b/wavesrv/pkg/cmdrunner/shparse_test.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package cmdrunner import ( diff --git a/wavesrv/pkg/cmdrunner/termopts.go b/wavesrv/pkg/cmdrunner/termopts.go index 365a7a26..309a9666 100644 --- a/wavesrv/pkg/cmdrunner/termopts.go +++ b/wavesrv/pkg/cmdrunner/termopts.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package cmdrunner import ( diff --git a/wavesrv/pkg/comp/comp.go b/wavesrv/pkg/comp/comp.go index 4d0041da..c0e65f1d 100644 --- a/wavesrv/pkg/comp/comp.go +++ b/wavesrv/pkg/comp/comp.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + // scripthaus completion package comp @@ -496,7 +499,7 @@ func getCompType(compPos shparse.CompletionPos) string { return CGTypeCommandMeta case shparse.CompTypeCommand: - return CGTypeCommand + return CGTypeCommandMeta case shparse.CompTypeVar: return CGTypeVariable diff --git a/wavesrv/pkg/comp/comp_test.go b/wavesrv/pkg/comp/comp_test.go index 31b4ed75..332ff1f6 100644 --- a/wavesrv/pkg/comp/comp_test.go +++ b/wavesrv/pkg/comp/comp_test.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package comp import ( diff --git a/wavesrv/pkg/comp/simplecomp.go b/wavesrv/pkg/comp/simplecomp.go index 41b104bc..6fbf79e3 100644 --- a/wavesrv/pkg/comp/simplecomp.go +++ b/wavesrv/pkg/comp/simplecomp.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package comp import ( diff --git a/wavesrv/pkg/dbutil/dbutil.go b/wavesrv/pkg/dbutil/dbutil.go index 36387327..7f7e3e22 100644 --- a/wavesrv/pkg/dbutil/dbutil.go +++ b/wavesrv/pkg/dbutil/dbutil.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package dbutil import ( diff --git a/wavesrv/pkg/dbutil/map.go b/wavesrv/pkg/dbutil/map.go index dffe922b..ee908036 100644 --- a/wavesrv/pkg/dbutil/map.go +++ b/wavesrv/pkg/dbutil/map.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package dbutil import ( diff --git a/wavesrv/pkg/keygen/keygen.go b/wavesrv/pkg/keygen/keygen.go index a8ae232e..4155c578 100644 --- a/wavesrv/pkg/keygen/keygen.go +++ b/wavesrv/pkg/keygen/keygen.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + // Utility functions for generating and reading public/private keypairs. package keygen diff --git a/wavesrv/pkg/mapqueue/mapqueue.go b/wavesrv/pkg/mapqueue/mapqueue.go index aa5d4d9a..93db6091 100644 --- a/wavesrv/pkg/mapqueue/mapqueue.go +++ b/wavesrv/pkg/mapqueue/mapqueue.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package mapqueue import ( diff --git a/wavesrv/pkg/pcloud/pcloud.go b/wavesrv/pkg/pcloud/pcloud.go index 672de624..af034ad5 100644 --- a/wavesrv/pkg/pcloud/pcloud.go +++ b/wavesrv/pkg/pcloud/pcloud.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package pcloud import ( diff --git a/wavesrv/pkg/pcloud/pclouddata.go b/wavesrv/pkg/pcloud/pclouddata.go index a865bd0c..04f00dfb 100644 --- a/wavesrv/pkg/pcloud/pclouddata.go +++ b/wavesrv/pkg/pcloud/pclouddata.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package pcloud import ( diff --git a/wavesrv/pkg/promptenc/promptenc.go b/wavesrv/pkg/promptenc/promptenc.go index e5ac25b3..23bfe1bd 100644 --- a/wavesrv/pkg/promptenc/promptenc.go +++ b/wavesrv/pkg/promptenc/promptenc.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package promptenc import ( diff --git a/wavesrv/pkg/remote/circlelog.go b/wavesrv/pkg/remote/circlelog.go index b5d3eb29..752dcf41 100644 --- a/wavesrv/pkg/remote/circlelog.go +++ b/wavesrv/pkg/remote/circlelog.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package remote import ( diff --git a/wavesrv/pkg/remote/openai/openai.go b/wavesrv/pkg/remote/openai/openai.go index fe7cee68..66838445 100644 --- a/wavesrv/pkg/remote/openai/openai.go +++ b/wavesrv/pkg/remote/openai/openai.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package openai import ( diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go index 25692dba..c9106c2b 100644 --- a/wavesrv/pkg/remote/remote.go +++ b/wavesrv/pkg/remote/remote.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package remote import ( diff --git a/wavesrv/pkg/remote/updatequeue.go b/wavesrv/pkg/remote/updatequeue.go index 1a66549b..e6f07637 100644 --- a/wavesrv/pkg/remote/updatequeue.go +++ b/wavesrv/pkg/remote/updatequeue.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package remote import ( diff --git a/wavesrv/pkg/rtnstate/rtnstate.go b/wavesrv/pkg/rtnstate/rtnstate.go index 2fddb803..772dda01 100644 --- a/wavesrv/pkg/rtnstate/rtnstate.go +++ b/wavesrv/pkg/rtnstate/rtnstate.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package rtnstate import ( diff --git a/wavesrv/pkg/scbase/scbase.go b/wavesrv/pkg/scbase/scbase.go index da9d778f..b4508aeb 100644 --- a/wavesrv/pkg/scbase/scbase.go +++ b/wavesrv/pkg/scbase/scbase.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package scbase import ( @@ -33,7 +36,7 @@ const PromptLockFile = "prompt.lock" const PromptDirName = "prompt" const PromptDevDirName = "prompt-dev" const PromptAppPathVarName = "PROMPT_APP_PATH" -const PromptVersion = "v0.4.0" +const PromptVersion = "v0.5.0" const PromptAuthKeyFileName = "prompt.authkey" const MShellVersion = "v0.3.0" const DefaultMacOSShell = "/bin/bash" diff --git a/wavesrv/pkg/scpacket/scpacket.go b/wavesrv/pkg/scpacket/scpacket.go index a6ef1a31..c9e2e208 100644 --- a/wavesrv/pkg/scpacket/scpacket.go +++ b/wavesrv/pkg/scpacket/scpacket.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package scpacket import ( diff --git a/wavesrv/pkg/scws/scws.go b/wavesrv/pkg/scws/scws.go index 360c0e35..25cad69f 100644 --- a/wavesrv/pkg/scws/scws.go +++ b/wavesrv/pkg/scws/scws.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package scws import ( diff --git a/wavesrv/pkg/shparse/comp.go b/wavesrv/pkg/shparse/comp.go index 257341df..89608206 100644 --- a/wavesrv/pkg/shparse/comp.go +++ b/wavesrv/pkg/shparse/comp.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package shparse import ( diff --git a/wavesrv/pkg/shparse/expand.go b/wavesrv/pkg/shparse/expand.go index 9f872311..40af5752 100644 --- a/wavesrv/pkg/shparse/expand.go +++ b/wavesrv/pkg/shparse/expand.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package shparse import ( diff --git a/wavesrv/pkg/shparse/extend.go b/wavesrv/pkg/shparse/extend.go index 8642e430..83e81703 100644 --- a/wavesrv/pkg/shparse/extend.go +++ b/wavesrv/pkg/shparse/extend.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package shparse import ( diff --git a/wavesrv/pkg/shparse/shparse.go b/wavesrv/pkg/shparse/shparse.go index 5757f845..a902f998 100644 --- a/wavesrv/pkg/shparse/shparse.go +++ b/wavesrv/pkg/shparse/shparse.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package shparse import ( diff --git a/wavesrv/pkg/shparse/shparse_test.go b/wavesrv/pkg/shparse/shparse_test.go index ee7ca093..31b70089 100644 --- a/wavesrv/pkg/shparse/shparse_test.go +++ b/wavesrv/pkg/shparse/shparse_test.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package shparse import ( diff --git a/wavesrv/pkg/shparse/tokenize.go b/wavesrv/pkg/shparse/tokenize.go index 825e7387..32ae75b0 100644 --- a/wavesrv/pkg/shparse/tokenize.go +++ b/wavesrv/pkg/shparse/tokenize.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package shparse import ( diff --git a/wavesrv/pkg/sstore/dbops.go b/wavesrv/pkg/sstore/dbops.go index 658a25af..361d74a4 100644 --- a/wavesrv/pkg/sstore/dbops.go +++ b/wavesrv/pkg/sstore/dbops.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package sstore import ( @@ -10,14 +13,14 @@ import ( "sync" "time" + "github.com/google/uuid" + "github.com/jmoiron/sqlx" + "github.com/sawka/txwrap" "github.com/wavetermdev/waveterm/waveshell/pkg/base" "github.com/wavetermdev/waveterm/waveshell/pkg/packet" "github.com/wavetermdev/waveterm/waveshell/pkg/shexec" "github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" - "github.com/google/uuid" - "github.com/jmoiron/sqlx" - "github.com/sawka/txwrap" ) const HistoryCols = "h.historyid, h.ts, h.userid, h.sessionid, h.screenid, h.lineid, h.haderror, h.cmdstr, h.remoteownerid, h.remoteid, h.remotename, h.ismetacmd, h.incognito, h.linenum" @@ -1641,7 +1644,6 @@ func GetSessionStats(ctx context.Context, sessionId string) (*SessionStatsType, const ( RemoteField_Alias = "alias" // string RemoteField_ConnectMode = "connectmode" // string - RemoteField_AutoInstall = "autoinstall" // bool RemoteField_SSHKey = "sshkey" // string RemoteField_SSHPassword = "sshpassword" // string RemoteField_Color = "color" // string @@ -1667,10 +1669,6 @@ func UpdateRemote(ctx context.Context, remoteId string, editMap map[string]inter query = `UPDATE remote SET connectmode = ? WHERE remoteid = ?` tx.Exec(query, mode, remoteId) } - if autoInstall, found := editMap[RemoteField_AutoInstall]; found { - query = `UPDATE remote SET autoinstall = ? WHERE remoteid = ?` - tx.Exec(query, autoInstall, remoteId) - } if sshKey, found := editMap[RemoteField_SSHKey]; found { query = `UPDATE remote SET sshopts = json_set(sshopts, '$.sshidentity', ?) WHERE remoteid = ?` tx.Exec(query, sshKey, remoteId) diff --git a/wavesrv/pkg/sstore/fileops.go b/wavesrv/pkg/sstore/fileops.go index 57fd5292..6d42ac99 100644 --- a/wavesrv/pkg/sstore/fileops.go +++ b/wavesrv/pkg/sstore/fileops.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package sstore import ( diff --git a/wavesrv/pkg/sstore/map.go b/wavesrv/pkg/sstore/map.go index c24a5345..21329dde 100644 --- a/wavesrv/pkg/sstore/map.go +++ b/wavesrv/pkg/sstore/map.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package sstore import ( diff --git a/wavesrv/pkg/sstore/migrate.go b/wavesrv/pkg/sstore/migrate.go index 06d8d51d..afd8bfd7 100644 --- a/wavesrv/pkg/sstore/migrate.go +++ b/wavesrv/pkg/sstore/migrate.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package sstore import ( @@ -8,16 +11,16 @@ import ( "strconv" "time" - sh2db "github.com/wavetermdev/waveterm/wavesrv/db" _ "github.com/golang-migrate/migrate/v4/database/sqlite3" _ "github.com/golang-migrate/migrate/v4/source/file" "github.com/golang-migrate/migrate/v4/source/iofs" _ "github.com/mattn/go-sqlite3" + sh2db "github.com/wavetermdev/waveterm/wavesrv/db" "github.com/golang-migrate/migrate/v4" ) -const MaxMigration = 22 +const MaxMigration = 24 const MigratePrimaryScreenVersion = 9 const CmdScreenSpecialMigration = 13 const CmdLineSpecialMigration = 20 diff --git a/wavesrv/pkg/sstore/quick.go b/wavesrv/pkg/sstore/quick.go index 84aae134..ff1480d5 100644 --- a/wavesrv/pkg/sstore/quick.go +++ b/wavesrv/pkg/sstore/quick.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package sstore import ( diff --git a/wavesrv/pkg/sstore/sstore.go b/wavesrv/pkg/sstore/sstore.go index 6ff4c42d..84c2cc2f 100644 --- a/wavesrv/pkg/sstore/sstore.go +++ b/wavesrv/pkg/sstore/sstore.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package sstore import ( diff --git a/wavesrv/pkg/sstore/sstore_migrate.go b/wavesrv/pkg/sstore/sstore_migrate.go index 15036ff1..6f6d20a6 100644 --- a/wavesrv/pkg/sstore/sstore_migrate.go +++ b/wavesrv/pkg/sstore/sstore_migrate.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package sstore import ( diff --git a/wavesrv/pkg/sstore/updatebus.go b/wavesrv/pkg/sstore/updatebus.go index 72316b3a..242c1c80 100644 --- a/wavesrv/pkg/sstore/updatebus.go +++ b/wavesrv/pkg/sstore/updatebus.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package sstore import ( diff --git a/wavesrv/pkg/utilfn/linediff.go b/wavesrv/pkg/utilfn/linediff.go index 5d4a2e9e..f724441b 100644 --- a/wavesrv/pkg/utilfn/linediff.go +++ b/wavesrv/pkg/utilfn/linediff.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package utilfn import ( diff --git a/wavesrv/pkg/utilfn/utilfn.go b/wavesrv/pkg/utilfn/utilfn.go index 5b3e70b1..dbab5c29 100644 --- a/wavesrv/pkg/utilfn/utilfn.go +++ b/wavesrv/pkg/utilfn/utilfn.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package utilfn import ( diff --git a/wavesrv/pkg/utilfn/utilfn_test.go b/wavesrv/pkg/utilfn/utilfn_test.go index d5d7c81b..c6956ab9 100644 --- a/wavesrv/pkg/utilfn/utilfn_test.go +++ b/wavesrv/pkg/utilfn/utilfn_test.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package utilfn import ( diff --git a/wavesrv/pkg/wsshell/wsshell.go b/wavesrv/pkg/wsshell/wsshell.go index d6fe69e7..48031df5 100644 --- a/wavesrv/pkg/wsshell/wsshell.go +++ b/wavesrv/pkg/wsshell/wsshell.go @@ -1,3 +1,6 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + package wsshell import ( diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..070ce803 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,30 @@ +const {webDev, webProd} = require("./webpack/webpack.web.js"); +const {electronDev, electronProd} = require("./webpack/webpack.electron.js"); + +module.exports = (env) => { + if (env.prod) { + console.log("using PROD (web+electron) webpack environment"); + return [webProd, electronProd]; + } + if (env["prod:web"]) { + console.log("using PROD (web) webpack environment"); + return webProd; + } + if (env["prod:electron"]) { + console.log("using PROD (electron) webpack environment"); + return electronProd; + } + if (env.dev) { + console.log("using DEV (web+electron) webpack environment"); + return [webDev, electronDev]; + } + if (env["dev:web"]) { + console.log("using DEV (web) webpack environment"); + return webDev; + } + if (env["dev:electron"]) { + console.log("using DEV (electron) webpack environment"); + return electronDev; + } + console.log("must specify a webpack environment using --env [dev|prod]"); +}; diff --git a/webpack.dev.js b/webpack.dev.js deleted file mode 100644 index 56cd10f6..00000000 --- a/webpack.dev.js +++ /dev/null @@ -1,36 +0,0 @@ -const webpack = require("webpack"); -const merge = require("webpack-merge"); -const common = require("./webpack.common.js"); -const path = require("path"); -const CopyPlugin = require("copy-webpack-plugin"); -const VERSION = require("./version.js"); - -var merged = merge.merge(common, { - mode: "development", - output: { - path: path.resolve(__dirname, "dist-dev"), - filename: "[name].js", - }, - devtool: "source-map", - devServer: { - static: { - directory: path.join(__dirname, "static"), - }, - port: 9000, - headers: { - "Cache-Control": "no-store", - }, - }, - watchOptions: { - aggregateTimeout: 200, - }, -}); - -var definePlugin = new webpack.DefinePlugin({ - __PROMPT_DEV__: "true", - __PROMPT_VERSION__: JSON.stringify(VERSION), - __PROMPT_BUILD__: JSON.stringify("devbuild"), -}); -merged.plugins.push(definePlugin); - -module.exports = merged; diff --git a/webpack.prod.js b/webpack.prod.js deleted file mode 100644 index 1eb63d3e..00000000 --- a/webpack.prod.js +++ /dev/null @@ -1,44 +0,0 @@ -const webpack = require("webpack"); -const merge = require("webpack-merge"); -const common = require("./webpack.common.js"); -const moment = require("dayjs"); -const path = require("path"); -const VERSION = require("./version.js"); - -function makeBuildStr() { - let buildStr = moment().format("YYYYMMDD-HHmmss"); - console.log("Prompt " + VERSION + " build " + buildStr); - return buildStr; -} - -const BUILD = makeBuildStr(); - -let BundleAnalyzerPlugin = null; -if (process.env.WEBPACK_ANALYZE) { - BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; -} - -let merged = merge.merge(common, { - mode: "production", - output: { - path: path.resolve(__dirname, "dist"), - filename: "[name].js", - }, - devtool: false, - optimization: { - minimize: true, - }, -}); - -if (BundleAnalyzerPlugin != null) { - merged.plugins.push(new BundleAnalyzerPlugin()); -} -merged.plugins.push( - new webpack.DefinePlugin({ - __PROMPT_DEV__: "false", - __PROMPT_VERSION__: JSON.stringify(VERSION), - __PROMPT_BUILD__: JSON.stringify(BUILD), - }) -); - -module.exports = merged; diff --git a/webpack.share.dev.js b/webpack.share.dev.js deleted file mode 100644 index ac7aa019..00000000 --- a/webpack.share.dev.js +++ /dev/null @@ -1,44 +0,0 @@ -const webpack = require("webpack"); -const merge = require("webpack-merge"); -const common = require("./webpack.share.js"); -const path = require("path"); -const CopyPlugin = require("copy-webpack-plugin"); -const VERSION = require("./version.js"); - -var merged = merge.merge(common, { - mode: "development", - output: { - path: path.resolve(__dirname, "webshare/dist-dev"), - filename: "[name].js", - }, - devtool: "source-map", - devServer: { - static: { - directory: path.join(__dirname, "./webshare"), - }, - port: 9001, - headers: { - "Cache-Control": "no-store", - }, - devMiddleware: { - publicPath: "/dist-dev/", - }, - allowedHosts: "all", - hot: false, - liveReload: false, - }, - watchOptions: { - aggregateTimeout: 200, - }, -}); - -var definePlugin = new webpack.DefinePlugin({ - __PROMPT_DEV__: "true", - __PROMPT_VERSION__: JSON.stringify(VERSION), - __PROMPT_BUILD__: JSON.stringify("devbuild"), - __PROMPT_API_ENDPOINT__: JSON.stringify("https://ot2e112zx5.execute-api.us-west-2.amazonaws.com/dev"), - __PROMPT_WSAPI_ENDPOINT__: JSON.stringify("wss://5lfzlg5crl.execute-api.us-west-2.amazonaws.com/dev"), -}); -merged.plugins.push(definePlugin); - -module.exports = merged; diff --git a/webpack.share.js b/webpack.share.js deleted file mode 100644 index bc84ae3d..00000000 --- a/webpack.share.js +++ /dev/null @@ -1,62 +0,0 @@ -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); -const LodashModuleReplacementPlugin = require("lodash-webpack-plugin"); -const path = require("path"); - -module.exports = { - mode: "development", - entry: { - webshare: ["./src/webshare.ts", "./src/app/app.less"], - }, - output: { - path: path.resolve(__dirname, "webshare/dist"), - filename: "[name].js", - }, - module: { - rules: [ - { - test: /\.tsx?$/, - // exclude: /node_modules/, - use: { - loader: "babel-loader", - options: { - presets: [ - [ - "@babel/preset-env", - { - targets: - "defaults and not ie > 0 and not op_mini all and not op_mob > 0 and not kaios > 0 and not and_qq > 0 and not and_uc > 0 and not baidu > 0", - }, - ], - "@babel/preset-react", - "@babel/preset-typescript", - ], - plugins: [ - ["@babel/transform-runtime", { regenerator: true }], - "@babel/plugin-transform-react-jsx", - ["@babel/plugin-proposal-decorators", { legacy: true }], - ["@babel/plugin-proposal-class-properties", { loose: true }], - ["@babel/plugin-proposal-private-methods", { loose: true }], - ["@babel/plugin-proposal-private-property-in-object", { loose: true }], - "babel-plugin-jsx-control-statements", - ], - }, - }, - }, - { - test: /\.css$/, - use: ["style-loader", "css-loader"], - }, - { - test: /\.less$/, - use: [{ loader: MiniCssExtractPlugin.loader }, "css-loader", "less-loader"], - }, - ], - }, - plugins: [ - new MiniCssExtractPlugin({ filename: "[name].css", ignoreOrder: true }), - new LodashModuleReplacementPlugin(), - ], - resolve: { - extensions: [".ts", ".tsx", ".js", ".mjs", ".cjs", ".wasm", ".json", ".less", ".css"], - }, -}; diff --git a/webpack.share.prod.js b/webpack.share.prod.js deleted file mode 100644 index 62d03ee0..00000000 --- a/webpack.share.prod.js +++ /dev/null @@ -1,38 +0,0 @@ -const webpack = require("webpack"); -const merge = require("webpack-merge"); -const common = require("./webpack.share.js"); -const moment = require("dayjs"); -const path = require("path"); -const VERSION = require("./version.js"); - -function makeBuildStr() { - let buildStr = moment().format("YYYYMMDD-HHmmss"); - console.log("Prompt " + VERSION + " build " + buildStr); - return buildStr; -} - -const BUILD = makeBuildStr(); - -let merged = merge.merge(common, { - mode: "production", - output: { - path: path.resolve(__dirname, "webshare/dist"), - filename: "[name].js", - }, - devtool: false, - optimization: { - minimize: true, - }, -}); - -merged.plugins.push( - new webpack.DefinePlugin({ - __PROMPT_DEV__: "false", - __PROMPT_VERSION__: JSON.stringify(VERSION), - __PROMPT_BUILD__: JSON.stringify(BUILD), - __PROMPT_API_ENDPOINT__: JSON.stringify("https://share.getprompt.dev/api"), - __PROMPT_WSAPI_ENDPOINT__: JSON.stringify("wss://wsapi.getprompt.dev"), - }) -); - -module.exports = merged; diff --git a/webpack.electron.js b/webpack/webpack.electron.js similarity index 59% rename from webpack.electron.js rename to webpack/webpack.electron.js index 32f759eb..771fd714 100644 --- a/webpack.electron.js +++ b/webpack/webpack.electron.js @@ -1,20 +1,28 @@ +const webpack = require("webpack"); +const webpackMerge = require("webpack-merge"); const path = require("path"); +const moment = require("dayjs"); +const VERSION = require("../version.js"); const CopyPlugin = require("copy-webpack-plugin"); -module.exports = { - mode: "development", +function makeBuildStr() { + let buildStr = moment().format("YYYYMMDD-HHmmss"); + // console.log("waveterm:electron " + VERSION + " build " + buildStr); + return buildStr; +} + +const BUILD = makeBuildStr(); + +var electronCommon = { entry: { emain: ["./src/electron/emain.ts"], }, target: "electron-main", - output: { - path: path.resolve(__dirname, "dist-dev"), - filename: "[name].js", - }, externals: { fs: "require('fs')", "fs-ext": "require('fs-ext')", }, + devtool: "source-map", module: { rules: [ { @@ -48,12 +56,48 @@ module.exports = { }, ], }, - plugins: [ - new CopyPlugin({ - patterns: [{ from: "src/electron/preload.js", to: "preload.js" }], - }), - ], resolve: { extensions: [".ts", ".tsx", ".js"], }, }; + +var electronDev = webpackMerge.merge(electronCommon, { + mode: "development", + output: { + path: path.resolve(__dirname, "../dist-dev"), + filename: "[name].js", + }, + plugins: [ + new CopyPlugin({ + patterns: [{ from: "src/electron/preload.js", to: "preload.js" }], + }), + new webpack.DefinePlugin({ + __PROMPT_DEV__: "true", + __PROMPT_VERSION__: JSON.stringify(VERSION), + __PROMPT_BUILD__: JSON.stringify("devbuild"), + }), + ], +}); + +var electronProd = webpackMerge.merge(electronCommon, { + mode: "production", + output: { + path: path.resolve(__dirname, "../dist"), + filename: "[name].js", + }, + plugins: [ + new CopyPlugin({ + patterns: [{ from: "src/electron/preload.js", to: "preload.js" }], + }), + new webpack.DefinePlugin({ + __PROMPT_DEV__: "false", + __PROMPT_VERSION__: JSON.stringify(VERSION), + __PROMPT_BUILD__: JSON.stringify(BUILD), + }), + ], + optimization: { + minimize: true, + }, +}); + +module.exports = {electronDev: electronDev, electronProd: electronProd}; diff --git a/webpack.electron.prod.js b/webpack/webpack.electron.prod.js similarity index 89% rename from webpack.electron.prod.js rename to webpack/webpack.electron.prod.js index 66ea4ab9..daaea669 100644 --- a/webpack.electron.prod.js +++ b/webpack/webpack.electron.prod.js @@ -2,7 +2,7 @@ const webpack = require("webpack"); const merge = require("webpack-merge"); const common = require("./webpack.electron.js"); const moment = require("dayjs"); -const VERSION = require("./version.js"); +const VERSION = require("../version.js"); const path = require("path"); function makeBuildStr() { @@ -16,7 +16,7 @@ const BUILD = makeBuildStr(); let merged = merge.merge(common, { mode: "production", output: { - path: path.resolve(__dirname, "dist"), + path: path.resolve(__dirname, "../dist"), filename: "[name].js", }, devtool: "source-map", diff --git a/webpack.common.js b/webpack/webpack.web.js similarity index 50% rename from webpack.common.js rename to webpack/webpack.web.js index 35362feb..c6478a67 100644 --- a/webpack.common.js +++ b/webpack/webpack.web.js @@ -1,15 +1,28 @@ +const webpack = require("webpack"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const LodashModuleReplacementPlugin = require("lodash-webpack-plugin"); +const CopyPlugin = require("copy-webpack-plugin"); +const webpackMerge = require("webpack-merge"); const path = require("path"); +const moment = require("dayjs"); +const VERSION = require("../version.js"); -module.exports = { - mode: "development", +function makeBuildStr() { + let buildStr = moment().format("YYYYMMDD-HHmmss"); + // console.log("waveterm:web " + VERSION + " build " + buildStr); + return buildStr; +} + +const BUILD = makeBuildStr(); + +let BundleAnalyzerPlugin = null; +if (process.env.WEBPACK_ANALYZE) { + BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; +} + +var webCommon = { entry: { - prompt: ["./src/index.ts", "./src/app/app.less"], - }, - output: { - path: path.resolve(__dirname, "dist"), - filename: "[name].js", + waveterm: ["./src/index.ts", "./src/app/app.less"], }, module: { rules: [ @@ -52,15 +65,75 @@ module.exports = { }, { test: /\.svg$/, - use: ["@svgr/webpack", "file-loader"], + use: [{ loader: "@svgr/webpack", options: { icon: true, svgo: false } }, "file-loader"], + }, + { + test: /\.md$/, + use: "raw-loader", + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: "file-loader", }, ], }, - plugins: [ - new MiniCssExtractPlugin({ filename: "[name].css", ignoreOrder: true }), - new LodashModuleReplacementPlugin(), - ], resolve: { extensions: [".ts", ".tsx", ".js", ".mjs", ".cjs", ".wasm", ".json", ".less", ".css"], }, }; + +var webDev = webpackMerge.merge(webCommon, { + mode: "development", + output: { + path: path.resolve(__dirname, "../dist-dev"), + filename: "[name].js", + }, + devtool: "source-map", + devServer: { + static: { + directory: path.join(__dirname, "../public"), + }, + port: 9000, + headers: { + "Cache-Control": "no-store", + }, + }, + plugins: [ + new MiniCssExtractPlugin({ filename: "[name].css", ignoreOrder: true }), + new LodashModuleReplacementPlugin(), + new webpack.DefinePlugin({ + __PROMPT_DEV__: "true", + __PROMPT_VERSION__: JSON.stringify(VERSION), + __PROMPT_BUILD__: JSON.stringify("devbuild"), + }), + ], + watchOptions: { + aggregateTimeout: 200, + }, +}); + +var webProd = webpackMerge.merge(webCommon, { + mode: "production", + output: { + path: path.resolve(__dirname, "../dist"), + filename: "[name].js", + }, + devtool: false, + plugins: [ + new MiniCssExtractPlugin({ filename: "[name].css", ignoreOrder: true }), + new LodashModuleReplacementPlugin(), + new webpack.DefinePlugin({ + __PROMPT_DEV__: "false", + __PROMPT_VERSION__: JSON.stringify(VERSION), + __PROMPT_BUILD__: JSON.stringify(BUILD), + }), + ], + optimization: { + minimize: true, + }, +}); +if (BundleAnalyzerPlugin != null) { + webProd.plugins.push(new BundleAnalyzerPlugin()); +} + +module.exports = { webDev: webDev, webProd: webProd }; diff --git a/yarn.lock b/yarn.lock index 129fe9ef..77791bf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1641,6 +1641,16 @@ dependencies: defer-to-connect "^2.0.0" +"@table-nav/core@^0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@table-nav/core/-/core-0.0.7.tgz#2d7f58eb81aacf5bdd1b40a823dbef5a88792995" + integrity sha512-pCh18jHDRe3tw9sJZXfKi4cSD6VjHbn40CYdqhp5X91SIX7rakDEQAsTx6F7Fv9TUv265l+5rUDcYNaJ0N0cqQ== + +"@table-nav/react@^0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@table-nav/react/-/react-0.0.7.tgz#a97699a490e31713fccd5f624ddb62e9fdc06a82" + integrity sha512-S+DsD/qDqp50Z4dqt5tZFMWA3sRu0OOT/grMQuq/z/52jPEKJB+b9t+YSH8Ms55vCJOJ0DxuYldJpYrJLMG5ew== + "@tanstack/match-sorter-utils@^8.8.4": version "8.8.4" resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457" @@ -1956,6 +1966,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.0.tgz#53ef263e5239728b56096b0a869595135b7952d2" integrity sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q== +"@types/webpack-env@^1.18.3": + version "1.18.3" + resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.3.tgz#e81f769199a5609c751f34fcc6f6095ceac7831f" + integrity sha512-v4CH6FLBCftYGFAswDhzFLjKgucXsOkIf5Mzl8ZZhEtC6oye9whFInNPKszNB9AvX7JEZMtpXxWctih6addP+Q== + "@types/ws@^8.5.5": version "8.5.6" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.6.tgz#e9ad51f0ab79b9110c50916c9fcbddc36d373065" @@ -6417,6 +6432,14 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +raw-loader@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" + integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + rcedit@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-3.1.0.tgz#1563ec7a5663de639f94c5dc85429db1da364b3e"