Merge pull request #57 from wavetermdev/dev-0.5.0

Initial v0.5.0 merge into main
This commit is contained in:
sawka 2023-10-30 23:36:29 -07:00 committed by GitHub
commit a8836ec691
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
181 changed files with 3539 additions and 1094 deletions

2
.gitignore vendored
View File

@ -19,4 +19,4 @@ webshare/dist-dev/
temp.sql temp.sql
.idea/ .idea/
test/ test/
.vscode/

View File

@ -57,27 +57,23 @@ scripthaus run electron-rebuild
## Running WebPack ## 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 scripthaus run webpack-watch
``` ```
For the Electron App: ## Running the WaveTerm Dev Client
```
scripthaus run webpack-electron-watch
```
## Running the Prompt Dev Client Now that webpack is running (and watching for file changes) we can finally run the WaveTerm Dev Client! To start the client run:
Now that webpack is running (and watching for file changes) we can finally run the Prompt Dev Client! To start the client run:
``` ```
scripthaus run electron scripthaus run electron
``` ```
To kill the client, either exit the Electron App normally or just Ctrl-C the ```scripthaus run electron``` command. 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`.

View File

@ -81,27 +81,23 @@ scripthaus run electron-rebuild
## Running WebPack ## 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 scripthaus run webpack-watch
``` ```
For the Electron App: ## Running the WaveTerm Dev Client
```
scripthaus run webpack-electron-watch
```
## Running the Prompt Dev Client Now that webpack is running (and watching for file changes) we can finally run the WaveTerm Dev Client! To start the client run:
Now that webpack is running (and watching for file changes) we can finally run the Prompt Dev Client! To start the client run:
``` ```
scripthaus run electron scripthaus run electron
``` ```
To kill the client, either exit the Electron App normally or just Ctrl-C the ```scripthaus run electron``` command. 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`.

View File

@ -55,7 +55,7 @@ module.exports = {
"node_modules/fs-ext/**", "node_modules/fs-ext/**",
"node_modules/fsevents/**", "node_modules/fsevents/**",
], ],
icon: "public/Prompt.icns", icon: "public/waveterm.icns",
osxNotarize: { osxNotarize: {
tool: "notarytool", tool: "notarytool",
keychainProfile: "notarytool-creds", keychainProfile: "notarytool-creds",

4
go.work Normal file
View File

@ -0,0 +1,4 @@
go 1.18
use ./wavesrv
use ./waveshell

6
go.work.sum Normal file
View File

@ -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=

Binary file not shown.

215
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "Prompt", "name": "waveterm",
"version": "0.3.1", "version": "0.5.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -2289,6 +2289,15 @@
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true "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": { "emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@ -2306,6 +2315,40 @@
"strip-ansi": "^7.0.1" "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": { "strip-ansi": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
@ -2315,6 +2358,23 @@
"ansi-regex": "^6.0.1" "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": { "wrap-ansi": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@ -2325,6 +2385,60 @@
"string-width": "^5.0.1", "string-width": "^5.0.1",
"strip-ansi": "^7.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==", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"dev": true "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": { "electron-installer-common": {
"version": "0.10.3", "version": "0.10.3",
"resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz", "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": { "rcedit": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/rcedit/-/rcedit-3.1.0.tgz", "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-3.1.0.tgz",
@ -9897,17 +10039,6 @@
"strip-ansi": "^6.0.1" "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": { "string_decoder": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@ -9925,15 +10056,6 @@
"ansi-regex": "^5.0.1" "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": { "strip-bom": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@ -11050,17 +11172,6 @@
"strip-ansi": "^6.0.0" "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": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View File

@ -1,10 +1,13 @@
{ {
"name": "Prompt", "name": "waveterm",
"version": "0.4.0", "productName": "Wave",
"version": "0.5.0",
"main": "dist/emain.js", "main": "dist/emain.js",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@monaco-editor/react": "^4.5.1", "@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/match-sorter-utils": "^8.8.4",
"@tanstack/react-table": "^8.10.3", "@tanstack/react-table": "^8.10.3",
"autobind-decorator": "^2.4.0", "autobind-decorator": "^2.4.0",
@ -56,6 +59,7 @@
"@types/papaparse": "^5.3.9", "@types/papaparse": "^5.3.9",
"@types/react": "^18.0.12", "@types/react": "^18.0.12",
"@types/uuid": "9.0.0", "@types/uuid": "9.0.0",
"@types/webpack-env": "^1.18.3",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"babel-plugin-jsx-control-statements": "^4.1.2", "babel-plugin-jsx-control-statements": "^4.1.2",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
@ -69,6 +73,7 @@
"lodash-webpack-plugin": "^0.11.6", "lodash-webpack-plugin": "^0.11.6",
"mini-css-extract-plugin": "^2.6.0", "mini-css-extract-plugin": "^2.6.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"raw-loader": "^4.0.2",
"react-split-it": "^2.0.0", "react-split-it": "^2.0.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"typescript": "^4.7.3", "typescript": "^4.7.3",

View File

@ -3,9 +3,9 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<base href="../" /> <base href="../" />
<script charset="UTF-8" src="dist-dev/prompt.js"></script> <script charset="UTF-8" src="dist-dev/waveterm.js"></script>
<link rel="stylesheet" href="public/bulma-0.9.4.min.css" /> <link rel="stylesheet" href="public/bulma-0.9.4.min.css" />
<link rel="stylesheet" href="dist-dev/prompt.css" /> <link rel="stylesheet" href="dist-dev/waveterm.css" />
</head> </head>
<body> <body>
<div id="measure"></div> <div id="measure"></div>

View File

@ -3,9 +3,9 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<base href="../" /> <base href="../" />
<script charset="UTF-8" src="dist/prompt.js"></script> <script charset="UTF-8" src="dist/waveterm.js"></script>
<link rel="stylesheet" href="public/bulma-0.9.4.min.css" /> <link rel="stylesheet" href="public/bulma-0.9.4.min.css" />
<link rel="stylesheet" href="dist/prompt.css" /> <link rel="stylesheet" href="dist/waveterm.css" />
</head> </head>
<body> <body>
<div id="measure"></div> <div id="measure"></div>

BIN
public/waveterm.icns Normal file

Binary file not shown.

View File

@ -1,27 +1,21 @@
# Prompt Commands # WaveTerm Commands
```bash ```bash
# @scripthaus command webpack-watch # @scripthaus command webpack-watch
# @scripthaus cd :playbook # @scripthaus cd :playbook
node_modules/.bin/webpack --watch --config webpack.dev.js node_modules/.bin/webpack --env dev --watch
``` ```
```bash ```bash
# @scripthaus command webpack-build # @scripthaus command webpack-build
# @scripthaus cd :playbook # @scripthaus cd :playbook
node_modules/.bin/webpack --config webpack.dev.js node_modules/.bin/webpack --env dev
``` ```
```bash ```bash
# @scripthaus command webpack-electron-watch # @scripthaus command webpack-build-prod
# @scripthaus cd :playbook # @scripthaus cd :playbook
node_modules/.bin/webpack --watch --config webpack.electron.js node_modules/.bin/webpack --env prod
```
```bash
# @scripthaus command webpack-electron-build
# @scripthaus cd :playbook
node_modules/.bin/webpack --config webpack.electron.js
``` ```
```bash ```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 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 ```bash
# @scripthaus command typecheck # @scripthaus command typecheck
# @scripthaus cd :playbook # @scripthaus cd :playbook
node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/index.ts node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/index.ts src/types/custom.d.ts
```
```bash
# @scripthaus command typecheck-webshare
# @scripthaus cd :playbook
node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/webshare.ts
``` ```
```bash ```bash
@ -78,8 +42,7 @@ node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --e
rm -rf dist/ rm -rf dist/
rm -rf bin/ rm -rf bin/
rm -rf build/ rm -rf build/
node_modules/.bin/webpack --config webpack.prod.js node_modules/.bin/webpack --env prod
node_modules/.bin/webpack --config webpack.electron.prod.js
GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')" 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=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) (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 ```bash
# @scripthaus command open-electron-package # @scripthaus command open-electron-package
# @scripthaus cd :playbook # @scripthaus cd :playbook
open out/Prompt-darwin-x64/Prompt.app open out/Wave-darwin-x64/Wave.app
``` ```
```bash ```bash
# @scripthaus command create-dmg # @scripthaus command create-dmg
# @scripthaus cd :playbook # @scripthaus cd :playbook
DMG_VERSION=$(node -e 'console.log(require("./version.js"))') 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 rm *.dmg
/Users/mike/work/gopath/src/github.com/create-dmg/create-dmg/create-dmg \ /Users/mike/work/gopath/src/github.com/create-dmg/create-dmg/create-dmg \
--volname "Prompt" \ --volname "WaveTerm" \
--window-pos 200 120 \ --window-pos 200 120 \
--window-size 600 300 \ --window-size 600 300 \
--icon-size 100 \ --icon-size 100 \
--icon "Prompt.app" 200 130 \ --icon "Wave.app" 200 130 \
--hide-extension "Prompt.app" \ --hide-extension "Wave.app" \
--app-drop-link 400 125 \ --app-drop-link 400 125 \
$DMG_NAME \ $DMG_NAME \
"out/Prompt-darwin-x64/Prompt.app" "out/Wave-darwin-x64/Wave.app"
``` ```
```bash ```bash
# @scripthaus command create-dmg-m1 # @scripthaus command create-dmg-m1
# @scripthaus cd :playbook # @scripthaus cd :playbook
DMG_VERSION=$(node -e 'console.log(require("./version.js"))') 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 rm *.dmg
../../create-dmg/create-dmg/create-dmg \ /Users/sawka/work/gopath/src/github.com/create-dmg/create-dmg/create-dmg \
--volname "Prompt" \ --volname "WaveTerm" \
--window-pos 200 120 \ --window-pos 200 120 \
--window-size 600 300 \ --window-size 600 300 \
--icon-size 100 \ --icon-size 100 \
--icon "Prompt.app" 200 130 \ --icon "Wave.app" 200 130 \
--hide-extension "Prompt.app" \ --hide-extension "Wave.app" \
--app-drop-link 400 125 \ --app-drop-link 400 125 \
$DMG_NAME \ $DMG_NAME \
"out/Prompt-darwin-arm64/Prompt.app" "out/Wave-darwin-arm64/Wave.app"
``` ```
```bash ```bash
@ -181,6 +144,7 @@ go build -ldflags="$GO_LDFLAGS" -o bin/mshell-v0.3-darwin.amd64 main-waveshell.g
```bash ```bash
# @scripthaus command fullbuild-waveshell # @scripthaus command fullbuild-waveshell
set -e
cd waveshell cd waveshell
GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')" 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 go build -ldflags="$GO_LDFLAGS" -o ~/.mshell/mshell-v0.2 main-waveshell.go

View File

@ -36,6 +36,50 @@ textarea {
color: @base-color; 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 { body code {
font-family: @terminal-font; font-family: @terminal-font;
} }
@ -179,7 +223,8 @@ a.a-block {
height: 100%; height: 100%;
.history-view, .history-view,
.bookmarks-view { .bookmarks-view,
.plugins-view {
flex-grow: 1; flex-grow: 1;
display: flex; display: flex;
flex-direction: column; 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 { @keyframes loader-ring {
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);
@ -409,3 +463,87 @@ a.a-block {
opacity: 0; 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;
}
}

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -8,6 +11,8 @@ import type { ContextMenuOpts } from "../types/types";
import localizedFormat from "dayjs/plugin/localizedFormat"; import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel } from "../model/model"; import { GlobalModel } from "../model/model";
import { isBlank } from "../util/util"; import { isBlank } from "../util/util";
import { WorkspaceView } from "./workspace/workspaceview";
import { PluginsView } from "./pluginsview/pluginsview";
import { BookmarksView } from "./bookmarks/bookmarks"; import { BookmarksView } from "./bookmarks/bookmarks";
import { HistoryView } from "./history/history"; import { HistoryView } from "./history/history";
import { import {
@ -18,9 +23,9 @@ import {
} from "./common/modals/settings"; } from "./common/modals/settings";
import { RemotesModal } from "./connections/connections"; import { RemotesModal } from "./connections/connections";
import { TosModal } from "./common/modals/modals"; import { TosModal } from "./common/modals/modals";
import { WorkspaceView } from "../app/workspace/workspaceview";
import { MainSideBar } from "./sidebar/MainSideBar"; import { MainSideBar } from "./sidebar/MainSideBar";
import { DisconnectedModal, ClientStopModal, AlertModal, WelcomeModal } from "./common/modals/modals"; import { DisconnectedModal, ClientStopModal, AlertModal, WelcomeModal } from "./common/modals/modals";
import { ErrorBoundary } from "./common/error/errorboundary";
import "./app.less"; import "./app.less";
dayjs.extend(localizedFormat); dayjs.extend(localizedFormat);
@ -103,13 +108,17 @@ class App extends React.Component<{}, {}> {
if (dcWait) { if (dcWait) {
setTimeout(() => this.updateDcWait(false), 0); setTimeout(() => this.updateDcWait(false), 0);
} }
//console.log(`GlobalModel.activeMainView.get() = ${GlobalModel.activeMainView.get()}`); // @mike - if I remove this, I cant see plugins
return ( return (
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}> <div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
<div className="main-content"> <div className="main-content">
<MainSideBar /> <MainSideBar />
<WorkspaceView /> <ErrorBoundary>
<HistoryView /> <PluginsView />
<BookmarksView /> <WorkspaceView />
<HistoryView />
<BookmarksView />
</ErrorBoundary>
</div> </div>
<AlertModal /> <AlertModal />
<If condition={GlobalModel.needsTos()}> <If condition={GlobalModel.needsTos()}>

View File

@ -0,0 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrows up_down">
<g id="Vector">
<path d="M12.1297 9.69269C12.3999 10.0066 12.3645 10.4801 12.0506 10.7504L8.48447 13.8208C8.20296 14.0632 7.78645 14.063 7.50519 13.8203L3.94636 10.7499C3.63274 10.4793 3.59785 10.0057 3.86843 9.69211C4.13901 9.37849 4.6126 9.3436 4.92622 9.61418L7.99562 12.2624L11.0719 9.61368C11.3858 9.34342 11.8594 9.37879 12.1297 9.69269Z" fill="#C3C8C2"/>
<path d="M12.1297 6.31224C12.3999 5.99834 12.3645 5.52479 12.0506 5.25453L8.48447 2.18408C8.20296 1.94171 7.78645 1.94192 7.50519 2.18458L3.94636 5.25502C3.63274 5.52561 3.59785 5.9992 3.86843 6.31282C4.13901 6.62644 4.6126 6.66133 4.92622 6.39075L7.99562 3.74257L11.0719 6.39125C11.3858 6.66151 11.8594 6.62614 12.1297 6.31224Z" fill="#C3C8C2"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 846 B

View File

@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="check">
<path id="Vector" d="M10.4215 3.07563C10.6558 3.30994 10.6558 3.68984 10.4215 3.92416L4.922 9.4238C4.80947 9.53632 4.65685 9.59954 4.49772 9.59954C4.33858 9.59954 4.18597 9.53631 4.07345 9.42378L1.57086 6.92099C1.33655 6.68666 1.33657 6.30676 1.5709 6.07246C1.80522 5.83815 2.18512 5.83817 2.41942 6.07249L4.49774 8.15099L9.57293 3.07564C9.80725 2.84133 10.1871 2.84132 10.4215 3.07563Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 525 B

View File

@ -0,0 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8.375" cy="8" r="8" fill="#E54D2E"/>
</svg>

After

Width:  |  Height:  |  Size: 151 B

View File

@ -0,0 +1,5 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="github-fill">
<path id="Vector" d="M24 4C12.95 4 4 12.95 4 24C3.99773 28.1986 5.31763 32.2912 7.77243 35.6974C10.2272 39.1035 13.6923 41.6501 17.676 42.976C18.676 43.15 19.05 42.55 19.05 42.024C19.05 41.55 19.024 39.976 19.024 38.3C14 39.226 12.7 37.076 12.3 35.95C12.074 35.374 11.1 33.6 10.25 33.124C9.55 32.75 8.55 31.824 10.224 31.8C11.8 31.774 12.924 33.25 13.3 33.85C15.1 36.874 17.976 36.024 19.124 35.5C19.3 34.2 19.824 33.326 20.4 32.826C15.95 32.326 11.3 30.6 11.3 22.95C11.3 20.774 12.074 18.976 13.35 17.574C13.15 17.074 12.45 15.024 13.55 12.274C13.55 12.274 15.224 11.75 19.05 14.326C20.6781 13.8741 22.3603 13.6467 24.05 13.65C25.75 13.65 27.45 13.874 29.05 14.324C32.874 11.724 34.55 12.276 34.55 12.276C35.65 15.026 34.95 17.076 34.75 17.576C36.024 18.976 36.8 20.75 36.8 22.95C36.8 30.626 32.126 32.326 27.676 32.826C28.4 33.45 29.026 34.65 29.026 36.526C29.026 39.2 29 41.35 29 42.026C29 42.55 29.376 43.174 30.376 42.974C34.3461 41.6336 37.7958 39.082 40.2398 35.6783C42.6838 32.2746 43.9989 28.1902 44 24C44 12.95 35.05 4 24 4Z" fill="#58C142"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="globe">
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M5.51561 7.24988C5.58926 5.49464 5.91878 3.94637 6.3934 2.81642C6.66453 2.17096 6.96822 1.69729 7.26714 1.39629C7.56327 1.0981 7.81035 1.00977 8.00003 1.00977C8.1897 1.00977 8.43678 1.0981 8.73291 1.39629C9.03183 1.69729 9.33553 2.17096 9.60665 2.81642C10.0813 3.94637 10.4108 5.49464 10.4844 7.24988H5.51561ZM4.01441 7.24988H1.03973C1.31693 4.64752 3.02019 2.47128 5.35297 1.51768C5.22994 1.74443 5.1158 1.98471 5.01045 2.23552C4.44737 3.57606 4.0887 5.32893 4.01441 7.24988ZM10.6471 1.51768C10.7701 1.74443 10.8842 1.98471 10.9896 2.23552C11.5527 3.57606 11.9114 5.32893 11.9856 7.24988H14.9603C14.6831 4.64752 12.9799 2.47128 10.6471 1.51768ZM10.4842 8.74988C10.4101 10.5028 10.0808 12.049 9.60665 13.1777C9.33553 13.8232 9.03183 14.2968 8.73291 14.5978C8.43678 14.896 8.1897 14.9844 8.00003 14.9844C7.81035 14.9844 7.56327 14.896 7.26714 14.5978C6.96822 14.2968 6.66453 13.8232 6.3934 13.1777C5.91928 12.049 5.58997 10.5028 5.51585 8.74988H10.4842ZM11.9854 8.74988H14.9603C14.683 11.3537 12.978 13.5309 10.6432 14.4837C10.7677 14.2548 10.8831 14.0121 10.9896 13.7586C11.5521 12.4194 11.9107 10.6686 11.9854 8.74988ZM5.35689 14.4837C3.0221 13.5309 1.31708 11.3537 1.03973 8.74988H4.01463C4.0894 10.6686 4.44792 12.4194 5.01045 13.7586C5.11692 14.0121 5.23237 14.2548 5.35689 14.4837Z" fill="#C3C8C2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,5 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="help">
<path id="Vector" d="M23.9468 3.16714C12.682 2.96414 3.38555 11.9315 3.18255 23.1963C2.97955 34.461 11.9469 43.7575 23.2117 43.9605C34.4764 44.1635 43.7729 35.1961 43.9759 23.9314C44.1789 12.6666 35.2115 3.37014 23.9468 3.16714ZM23.7413 14.5653C22.3462 14.5401 21.1521 15.4731 20.7957 16.7612C20.5306 17.7193 19.539 18.2811 18.5809 18.0161C17.6228 17.751 17.061 16.7594 17.3261 15.8012C18.1098 12.9686 20.7289 10.9104 23.8062 10.9659C27.4508 11.0316 30.3519 14.0392 30.2863 17.6837C30.2339 20.59 28.6376 22.0717 27.4405 23.1492L27.4205 23.1672C26.2542 24.2169 25.5369 24.8629 25.3131 26.2501C25.1548 27.2315 24.2308 27.8987 23.2494 27.7406C22.2679 27.5822 21.6007 26.6583 21.759 25.6767C22.1877 23.0184 23.7599 21.6119 24.867 20.6214L25.032 20.4736C26.1224 19.492 26.6636 18.9119 26.6869 17.6189C26.7167 15.9623 25.3978 14.5951 23.7413 14.5653ZM25.8059 33.2055C25.782 34.5308 24.6883 35.5858 23.363 35.5619C22.0378 35.538 20.9828 34.4443 21.0067 33.119C21.0305 31.7937 22.1243 30.7388 23.4495 30.7627C24.7748 30.7865 25.8298 31.8802 25.8059 33.2055Z" fill="#58C142"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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<V> = mobx.IObservableValue<V>;
@mobxReact.observer
class DisconnectedModal extends React.Component<{}, {}> {
logRef: any = React.createRef();
showLog: mobx.IObservableValue<boolean> = 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 (
<div className="prompt-modal disconnected-modal modal is-active">
<div className="modal-background"></div>
<div className="modal-content">
<div className="message-header">
<div className="modal-title">Prompt Client Disconnected</div>
</div>
<If condition={this.showLog.get()}>
<div className="inner-content">
<div className="ws-log" ref={this.logRef}>
<For each="logLine" index="idx" of={GlobalModel.ws.wsLog}>
<div key={idx} className="ws-logline">
{logLine}
</div>
</For>
</div>
</div>
</If>
<footer>
<div className="footer-text-link" style={{ marginLeft: 10 }} onClick={this.handleShowLog}>
<If condition={!this.showLog.get()}>
<i className="fa-sharp fa-solid fa-plus" /> Show Log
</If>
<If condition={this.showLog.get()}>
<i className="fa-sharp fa-solid fa-minus" /> Hide Log
</If>
</div>
<div className="flex-spacer" />
<button onClick={this.tryReconnect} className="button">
<span className="icon">
<i className="fa-sharp fa-solid fa-rotate" />
</span>
<span>Try Reconnect</span>
</button>
<button onClick={this.restartServer} className="button is-danger" style={{ marginLeft: 10 }}>
<WarningIcon className="icon" />
<span>Restart Server</span>
</button>
</footer>
</div>
</div>
);
}
}
@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 (
<div className="prompt-modal client-stop-modal modal is-active">
<div className="modal-background"></div>
<div className="modal-content">
<div className="message-header">
<div className="modal-title">[prompt] {title}</div>
</div>
<div className="inner-content">
<If condition={cdata == null}>
<div>Cannot get client data.</div>
</If>
</div>
<footer>
<button onClick={this.refreshClient} className="button">
<span className="icon">
<i className="fa-sharp fa-solid fa-rotate" />
</span>
<span>Hard Refresh Client</span>
</button>
</footer>
</div>
</div>
);
}
}
@mobxReact.observer
class LoadingSpinner extends React.Component<{}, {}> {
render() {
return (
<div className="loading-spinner">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
);
}
}
@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 (
<div className="modal prompt-modal wave-modal is-active alert-modal">
<div className="modal-background" />
<div className="modal-content">
<header>
<p className="modal-title">
<WarningIcon className="icon" />
{title}
</p>
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
<XmarkIcon />
</div>
</header>
<If condition={message.markdown}>
<Markdown text={message.message} extraClassName="inner-content" />
</If>
<If condition={!message.markdown}>
<div className="inner-content content">
<p>{message.message}</p>
</div>
</If>
<footer>
<If condition={isConfirm}>
<div onClick={this.closeModal} className="button is-prompt-cancel is-outlined is-small">
Cancel
</div>
<div onClick={this.handleOK} className="button is-prompt-green is-outlined is-small">
OK
</div>
</If>
<If condition={!isConfirm}>
<div onClick={this.handleOK} className="button is-prompt-green is-small">
OK
</div>
</If>
</footer>
</div>
</div>
);
}
}
@mobxReact.observer
class WelcomeModal extends React.Component<{}, {}> {
totalPages: number = 3;
pageNum: OV<number> = 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 <i key={String(num)} className="fa-sharp fa-solid fa-circle" />;
}
return <i key={String(num)} className="fa-sharp fa-regular fa-circle" />;
}
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 (
<div className={cn("modal welcome-modal prompt-modal is-active")}>
<div className="modal-background" onClick={this.closeModal} />
<div className="modal-content">
<header>
<div className="modal-title">welcome to [prompt]</div>
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
<XmarkIcon />
</div>
</header>
<div className={cn("inner-content content", { "is-hidden": pageNum != 1 })}>
<p>
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!
</p>
</div>
<footer>
<If condition={pageNum > 1}>
<button className={cn("button is-dark prev-button is-small")} onClick={this.goPrev}>
<span className="icon is-small">
<i className="fa-sharp fa-regular fa-angle-left" />
</span>
<span>Prev</span>
</button>
</If>
<If condition={pageNum == 1}>
<div className="prev-spacer" />
</If>
<div className="flex-spacer" />
<div className="dots">{this.renderDots()}</div>
<div className="flex-spacer" />
<If condition={pageNum < this.totalPages}>
<button className="button is-dark next-button is-small" onClick={this.goNext}>
<span>Next</span>
<span className="icon is-small">
<i className="fa-sharp fa-regular fa-angle-right" />
</span>
</button>
</If>
<If condition={pageNum == this.totalPages}>
<button className="button is-dark next-button is-small" onClick={this.closeModal}>
<span>Done</span>
</button>
</If>
</footer>
</div>
</div>
);
}
}
@mobxReact.observer
class TosModal extends React.Component<{}, {}> {
@boundMethod
acceptTos(): void {
GlobalCommandRunner.clientAcceptTos();
}
render() {
return (
<div className={cn("modal tos-modal wave-modal is-active")}>
<div className="modal-background" />
<div className="modal-content">
<div className="modal-content-wrapper">
<header>
<div className="modal-title">Welcome to Wave Terminal!</div>
<div className="modal-subtitle">Lets set everything for you</div>
</header>
<div className="content">
<div className="item">
<ShieldCheck />
<div className="item-inner">
<div className="item-title">Telemetry</div>
</div>
</div>
<div className="item">
<Help />
<div className="item-inner"></div>
</div>
<div className="item">
<Github />
<div className="item-inner"></div>
</div>
{/* <p>
<a target="_blank" href={util.makeExternLink("https://www.commandline.dev/tos")}>
Full Terms of Service
</a>
</p> */}
</div>
<footer>
<div className="flex-spacer" />
<div onClick={this.acceptTos} className="button is-prompt-green is-outlined is-small">
Accept Terms of Service
</div>
</footer>
</div>
</div>
</div>
);
}
}
export { WelcomeModal, LoadingSpinner, ClientStopModal, AlertModal, DisconnectedModal, TosModal };

View File

@ -0,0 +1,8 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="shield check">
<g id="Vector">
<path d="M24.3696 6.0192C24.1241 5.98873 23.8759 5.98873 23.6304 6.0192C23.3928 6.0504 23.1432 6.1224 22.2168 6.4176L11.0208 9.9792C10.5168 10.14 10.2288 10.2336 10.0128 10.3176C9.94556 10.3425 9.88053 10.373 9.8184 10.4088C9.74112 10.4722 9.6809 10.5538 9.6432 10.6464C9.62911 10.7176 9.62108 10.7899 9.6192 10.8624C9.60207 11.2149 9.59567 11.5679 9.6 11.9208V25.5768C9.6 30.0336 11.9832 33.6264 15.0288 36.408C18.0768 39.1896 21.6048 40.9848 23.4432 41.808C23.6592 41.904 23.7456 41.9424 23.8128 41.9664C23.934 42.0088 24.066 42.0088 24.1872 41.9664C24.2544 41.9424 24.3432 41.904 24.5568 41.808C26.3952 40.9848 29.9256 39.1896 32.9712 36.408C36.0192 33.6288 38.4 30.036 38.4 25.5792V11.9232C38.4 11.3952 38.4 11.088 38.3832 10.86C38.3806 10.7874 38.3718 10.7151 38.3568 10.644C38.3194 10.5526 38.2601 10.4718 38.184 10.4088C38.1211 10.3729 38.0553 10.3423 37.9872 10.3176C37.6547 10.1954 37.3185 10.0833 36.9792 9.9816L25.7832 6.4176C24.8568 6.1224 24.6072 6.0504 24.3672 6.0192H24.3696ZM23.1888 2.448C23.7275 2.38128 24.2725 2.38128 24.8112 2.448C25.4568 2.5272 26.0736 2.7312 26.8752 2.9856L38.0712 6.5496L38.2632 6.6096C39.0216 6.8496 39.8304 7.104 40.464 7.6224C41.0136 8.0736 41.4384 8.6568 41.7024 9.3168C42.0048 10.0776 42.0024 10.9248 42 11.7216V25.5792C42 31.4352 38.8464 35.9208 35.4 39.0672C31.9488 42.2184 28.0272 44.2032 26.0256 45.096C25.6608 45.2616 25.2624 45.4464 24.7056 45.5424C24.2808 45.6144 23.7192 45.6144 23.2968 45.5424C22.7376 45.4464 22.3392 45.2616 21.9744 45.096C19.9752 44.2032 16.0512 42.2184 12.6024 39.0696C9.1536 35.9208 6 31.4352 6 25.5816V11.7216C5.9976 10.9248 5.9952 10.0776 6.2976 9.3168C6.56036 8.6563 6.98646 8.0733 7.536 7.6224C8.1696 7.104 8.976 6.8496 9.7368 6.6096L9.9288 6.5496L21.1248 2.9856C21.9288 2.7312 22.5432 2.5272 23.1888 2.448Z" fill="#58C142"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.3696 6.0192C24.1241 5.98873 23.8759 5.98873 23.6304 6.0192C23.3928 6.0504 23.1432 6.1224 22.2168 6.4176L11.0208 9.9792C10.5168 10.14 10.2288 10.2336 10.0128 10.3176C9.94556 10.3425 9.88053 10.373 9.8184 10.4088C9.74112 10.4722 9.6809 10.5538 9.6432 10.6464C9.62911 10.7176 9.62108 10.7899 9.6192 10.8624C9.60207 11.2149 9.59567 11.5679 9.6 11.9208V25.5768C9.6 30.0336 11.9832 33.6264 15.0288 36.408C18.0768 39.1896 21.6048 40.9848 23.4432 41.808C23.6592 41.904 23.7456 41.9424 23.8128 41.9664C23.934 42.0088 24.066 42.0088 24.1872 41.9664C24.2544 41.9424 24.3432 41.904 24.5568 41.808C26.3952 40.9848 29.9256 39.1896 32.9712 36.408C36.0192 33.6288 38.4 30.036 38.4 25.5792V11.9232C38.4 11.3952 38.4 11.088 38.3832 10.86C38.3806 10.7874 38.3718 10.7151 38.3568 10.644C38.3194 10.5526 38.2601 10.4718 38.184 10.4088C38.1211 10.3729 38.0553 10.3423 37.9872 10.3176C37.6547 10.1954 37.3185 10.0833 36.9792 9.9816L25.7832 6.4176C24.8568 6.1224 24.6072 6.0504 24.3672 6.0192H24.3696ZM33.436 17.8489C33.3374 17.634 33.1974 17.4407 33.024 17.28C32.8507 17.1192 32.6498 16.9942 32.4281 16.912C32.2064 16.8298 31.9708 16.7921 31.7345 16.8011C31.4983 16.81 31.2661 16.8653 31.0513 16.964C30.8364 17.0626 30.6431 17.2026 30.4824 17.376L21.7704 26.7552L17.52 22.176C17.1917 21.8417 16.7459 21.6486 16.2774 21.6378C15.809 21.627 15.3548 21.7992 15.0113 22.118C14.6679 22.4367 14.4623 22.8769 14.4382 23.3448C14.4141 23.8128 14.5735 24.2717 14.8824 24.624L20.4504 30.624C20.6189 30.8057 20.8231 30.9507 21.0503 31.0499C21.2774 31.1491 21.5226 31.2002 21.7704 31.2002C22.0182 31.2002 22.2634 31.1491 22.4905 31.0499C22.7177 30.9507 22.9219 30.8057 23.0904 30.624L33.12 19.824C33.2808 19.6507 33.4058 19.4474 33.488 19.2257C33.5702 19.004 33.6079 18.7684 33.5989 18.5321C33.59 18.2959 33.5347 18.0637 33.436 17.8489Z" fill="#58C142"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,3 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle id="Ellipse 1" cx="5" cy="5" r="4" fill="#46A758" stroke="#151715" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 600 400" style="enable-background:new 0 0 600 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_00000112609801797065440120000009078644614516335013_);}
.st2{fill:#FFFFFF;}
.st3{fill:#58C142;}
.st4{fill:url(#SVGID_00000140705471064485508570000011374545427215048096_);}
.st5{fill:url(#SVGID_00000070096993167771140800000009230862614014512804_);}
.st6{fill:url(#SVGID_00000018927797001490499930000007907141601259141028_);}
.st7{fill:url(#SVGID_00000177467729764420216790000007105154932047940258_);}
.st8{fill:url(#SVGID_00000094609616810712664470000012347852020703354001_);}
.st9{fill:url(#SVGID_00000147904120639373224840000010231811793887454594_);}
.st10{fill:url(#SVGID_00000124156599372995293460000015010576141345595828_);}
.st11{fill:url(#SVGID_00000113335101732495131180000003771446430366466471_);}
.st12{fill:url(#SVGID_00000123421819715807053460000011277887084625979316_);}
.st13{fill:url(#SVGID_00000070797766435397124570000013813146128488798621_);}
</style>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="7.3142" y1="133.3046" x2="556.0522" y2="133.3046">
<stop offset="0.1418" style="stop-color:#1F4D22"/>
<stop offset="0.8656" style="stop-color:#418D31"/>
</linearGradient>
<path class="st0" d="M190.2,184.4c-28.8,0-44.6,18.7-53.3,60.5L7.3,226.2C23.2,89.4,76.4,23.1,181.6,23.1
c77.8,0,154.1,59.1,191.6,59.1c28.8,0,44.7-20.2,53.3-60.5l129.6,18.7c-14.4,136.8-69.1,203.1-174.3,203.1
C302.6,243.5,229.1,184.4,190.2,184.4z"/>
</g>
<g>
<linearGradient id="SVGID_00000085247734223146048360000005883855174484422067_" gradientUnits="userSpaceOnUse" x1="43.9478" y1="266.6954" x2="592.6859" y2="266.6954">
<stop offset="0.2223" style="stop-color:#418D31"/>
<stop offset="0.7733" style="stop-color:#58C142"/>
</linearGradient>
<path style="fill:url(#SVGID_00000085247734223146048360000005883855174484422067_);" d="M226.8,317.8
c-28.8,0-44.6,18.7-53.3,60.5L43.9,359.6c15.8-136.8,69.1-203.1,174.3-203.1c77.8,0,154.1,59.1,191.6,59.1
c28.8,0,44.7-20.2,53.3-60.5l129.6,18.7c-14.4,136.8-69.1,203.1-174.3,203.1C339.2,376.9,265.7,317.8,226.8,317.8z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";

View File

@ -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 { .button.is-prompt-green {
background-color: #222; background-color: #222;
color: @term-white; 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-plain,
.button.is-prompt-cancel { .button.is-prompt-cancel {
background-color: #222; background-color: #222;

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; 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 (
<div className="checkbox">
<input
type="checkbox"
id={id}
checked={checked}
onChange={(e) => onChange(e.target.checked)}
aria-checked={checked}
role="checkbox"
/>
<label htmlFor={id}>
<span></span>
{label}
</label>
</div>
);
}
}
@mobxReact.observer @mobxReact.observer
class RemoteStatusLight extends React.Component<{ remote: RemoteType }, {}> { class RemoteStatusLight extends React.Component<{ remote: RemoteType }, {}> {
render() { render() {
@ -328,6 +357,7 @@ class SettingsError extends React.Component<{ errorMessage: OV<string> }, {}> {
export { export {
CmdStrCode, CmdStrCode,
Toggle, Toggle,
Checkbox,
renderCmdText, renderCmdText,
RemoteStatusLight, RemoteStatusLight,
InlineSettingsTextEdit, InlineSettingsTextEdit,

View File

@ -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<ErrorBoundaryProps, ErrorBoundaryState> {
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 (
<div className={cn("load-error-text", { "view-error": !plugin })}>
<div>{`${error?.message}`}</div>
{plugin && <div>An error occurred in the {plugin} plugin</div>}
</div>
);
}
render() {
const { hasError } = this.state;
if (hasError) {
return this.renderFallback();
}
return this.props.children;
}
}
export { ErrorBoundary };

View File

@ -158,8 +158,6 @@
display: inline; display: inline;
width: 1em; width: 1em;
height: 1em; height: 1em;
margin: 0 0.25em 0 1em;
vertical-align: middle;
} }
&.is-hidden { &.is-hidden {
display: none; 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 { .modal.welcome-modal {
footer { footer {
.prev-button { .prev-button {
@ -343,8 +444,11 @@
.tab-color-icon.color-white path { .tab-color-icon.color-white path {
fill: @tab-white; fill: @tab-white;
} }
.tab-color-icon.color-black path { .tab-color-icon.color-violet path {
fill: @tab-black; fill: @tab-violet;
}
.tab-color-icon.color-pink path {
fill: @tab-pink;
} }
.tab-colors { .tab-colors {

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -9,9 +12,14 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner } from "../../../model/model"; import { GlobalModel, GlobalCommandRunner } from "../../../model/model";
import { Markdown } from "../common"; import { Markdown } from "../common";
import * as util from "../../../util/util"; 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 XmarkIcon } from "../../assets/icons/line/xmark.svg";
import { ReactComponent as WarningIcon } from "../../assets/icons/line/triangle-exclamation.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); dayjs.extend(localizedFormat);
@ -309,48 +317,112 @@ class WelcomeModal extends React.Component<{}, {}> {
@mobxReact.observer @mobxReact.observer
class TosModal extends React.Component<{}, {}> { class TosModal extends React.Component<{}, {}> {
state = {
isChecked: false,
};
@boundMethod
handleCheckboxChange(checked: boolean): void {
this.setState({ isChecked: checked });
}
@boundMethod @boundMethod
acceptTos(): void { acceptTos(): void {
GlobalCommandRunner.clientAcceptTos(); GlobalCommandRunner.clientAcceptTos();
} }
@boundMethod
handleChangeTelemetry(val: boolean): void {
if (val) {
GlobalCommandRunner.telemetryOn(false);
} else {
GlobalCommandRunner.telemetryOff(false);
}
}
render() { render() {
let cdata: ClientDataType = GlobalModel.clientData.get();
return ( return (
<div className={cn("modal tos-modal prompt-modal is-active")}> <div className={cn("modal tos-modal wave-modal is-active")}>
<div className="modal-background" /> <div className="modal-background" />
<div className="modal-content"> <div className="modal-content">
<header> <div className="modal-content-wrapper">
<div className="modal-title">Welcome to [prompt]</div> <header>
</header> <div className="modal-title">Welcome to Wave Terminal!</div>
<div className="inner-content"> <div className="modal-subtitle">Lets set everything for you</div>
</header>
<div className="content"> <div className="content">
<p>Thank you for downloading Prompt!</p> <div className="item">
<p> <img src={shield} alt="Privacy" />
Prompt is a new terminal designed to help you save time and organize your command life. <div className="item-inner">
Prompt is currently in beta. If you'd like to give feedback, run into problems, have <div className="item-title">Telemetry</div>
questions, or need help, please join the Prompt{" "} <div className="item-text">
<a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}> We dont collect any personal info, only crash logs and IP address to make Wave
discord&nbsp;server better. If you like, you can disable telemetry now or late.
</a> </div>
. <div className="item-field">
</p> <Toggle
<p> checked={!cdata.clientopts.notelemetry}
Prompt is free to use, no email or registration required (unless you're using the cloud onChange={this.handleChangeTelemetry}
features). />
</p> <div className="item-label">Basic Telemetry</div>
<p> </div>
<a target="_blank" href={util.makeExternLink("https://www.commandline.dev/tos")}> </div>
Full Terms of Service </div>
</a> <div className="item">
</p> <img src={help} alt="Help" />
<div className="item-inner">
<div className="item-title">Help</div>
<div className="item-text">
If you need any help or you have feature request, you can join{" "}
<a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}>
our Discord
</a>
.
</div>
</div>
</div>
<div className="item">
<img src={github} alt="Github" />
<div className="item-inner">
<div className="item-title">Like Wave? Give us a star</div>
<div className="item-text">
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{" "}
<a
target="_blank"
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
>
Github Repository
</a>
.
</div>
</div>
</div>
</div> </div>
<footer>
<div>
<Checkbox
checked={this.state.isChecked}
label="I accept the Terms of Service"
id="accept-tos"
onChange={this.handleCheckboxChange}
/>
</div>
<div className="button-wrapper">
<button
onClick={this.acceptTos}
className={cn("button is-wave-green is-outlined is-small", {
"disabled-button": !this.state.isChecked,
})}
disabled={!this.state.isChecked}
>
Continue
</button>
</div>
</footer>
</div> </div>
<footer>
<div className="flex-spacer" />
<div onClick={this.acceptTos} className="button is-prompt-green is-outlined is-small">
Accept Terms of Service
</div>
</footer>
</div> </div>
</div> </div>
); );

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -7,8 +10,10 @@ import cn from "classnames";
import { GlobalModel, GlobalCommandRunner, TabColors } from "../../../model/model"; import { GlobalModel, GlobalCommandRunner, TabColors } from "../../../model/model";
import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common"; import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common";
import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types"; import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types";
import { RemotesSelector } from "../../connections/connections";
import { PluginModel } from "../../../plugins/plugins"; import { PluginModel } from "../../../plugins/plugins";
import * as util from "../../../util/util"; import * as util from "../../../util/util";
import { commandRtnHandler } from "../../../util/util";
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg"; import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
import { ReactComponent as XmarkIcon } from "../../assets/icons/line/xmark.svg"; import { ReactComponent as XmarkIcon } from "../../assets/icons/line/xmark.svg";
import { ReactComponent as AngleDownIcon } from "../../assets/icons/history/angle-down.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(); `.trim();
const SessionDeleteMessage = ` 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(); `.trim();
const WebShareConfirmMarkdown = ` const WebShareConfirmMarkdown = `
@ -44,19 +49,8 @@ const WebStopShareConfirmMarkdown = `
Are you sure you want to stop web-sharing this screen? Are you sure you want to stop web-sharing this screen?
`.trim(); `.trim();
function commandRtnHandler(prtn: Promise<CommandRtnType>, errorMessage: OV<string>) {
prtn.then((crtn) => {
if (crtn.success) {
return;
}
mobx.action(() => {
errorMessage.set(crtn.error);
})();
});
}
@mobxReact.observer @mobxReact.observer
class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string }, {}> { class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string; inline?: boolean }, {}> {
shareCopied: OV<boolean> = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" }); shareCopied: OV<boolean> = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" });
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" }); errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
@ -201,30 +195,38 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
} }
render() { render() {
let { sessionId, screenId } = this.props; let { sessionId, screenId, inline } = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId); let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) { if (screen == null) {
return null; return null;
} }
let color: string = null; let color: string = null;
return ( return (
<div className={cn("modal screen-settings-modal settings-modal prompt-modal is-active")}> <div
<div className="modal-background" /> className={
<div className="modal-content"> inline
<If condition={this.shareCopied.get()}> ? "screen-settings-inline"
<div className="copied-indicator" /> : cn("modal screen-settings-modal settings-modal prompt-modal is-active")
</If> }
<header> >
<div className="modal-title">screen settings ({screen.name.get()})</div> {!inline && <div className="modal-background" />}
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}> <div className={inline ? "inline-content" : "modal-content"}>
<XmarkIcon /> {this.shareCopied.get() && <div className="copied-indicator" />}
</div> {!inline && (
</header> <header>
<div className="modal-title">screen settings ({screen.name.get()})</div>
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
<XmarkIcon />
</div>
</header>
)}
<div className="inner-content"> <div className="inner-content">
<div className="settings-field"> {!inline && (
<div className="settings-label">Screen Id</div> <div className="settings-field">
<div className="settings-input">{screen.screenId}</div> <div className="settings-label">Screen Id</div>
</div> <div className="settings-input">{screen.screenId}</div>
</div>
)}
<div className="settings-field"> <div className="settings-field">
<div className="settings-label">Name</div> <div className="settings-label">Name</div>
<div className="settings-input"> <div className="settings-input">
@ -259,40 +261,47 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
</div> </div>
</div> </div>
</div> </div>
<div className="settings-field"> {!inline && (
<div className="settings-label"> <div className="settings-field">
<div>Archived</div> <div className="settings-label">
<InfoMessage width={400}> <div>Archived</div>
Archive will hide the screen tab. Commands and output will be retained in history. <InfoMessage width={400}>
</InfoMessage> Archive will hide the screen tab. Commands and output will be retained in
</div> history.
<div className="settings-input"> </InfoMessage>
<Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived} /> </div>
</div> <div className="settings-input">
</div> <Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived} />
<div className="settings-field">
<div className="settings-label">
<div>Actions</div>
<InfoMessage width={400}>
Delete will remove the screen, removing all commands and output from history.
</InfoMessage>
</div>
<div className="settings-input">
<div
onClick={this.handleDeleteScreen}
className="button is-prompt-danger is-outlined is-small"
>
Delete Screen
</div> </div>
</div> </div>
</div> )}
{!inline && (
<div className="settings-field">
<div className="settings-label">
<div>Actions</div>
<InfoMessage width={400}>
Delete will remove the screen, removing all commands and output from history.
</InfoMessage>
</div>
<div className="settings-input">
<div
onClick={this.handleDeleteScreen}
className="button is-prompt-danger is-outlined is-small"
>
Delete Screen
</div>
</div>
</div>
)}
<SettingsError errorMessage={this.errorMessage} /> <SettingsError errorMessage={this.errorMessage} />
</div> </div>
<footer> {!inline && (
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small"> <footer>
Close <div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
</div> Close
</footer> </div>
</footer>
)}
</div> </div>
</div> </div>
); );
@ -402,7 +411,7 @@ class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
<div className="settings-label"> <div className="settings-label">
<div>Archived</div> <div>Archived</div>
<InfoMessage width={400}> <InfoMessage width={400}>
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. retained in history.
</InfoMessage> </InfoMessage>
</div> </div>
@ -414,7 +423,7 @@ class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
<div className="settings-label"> <div className="settings-label">
<div>Actions</div> <div>Actions</div>
<InfoMessage width={400}> <InfoMessage width={400}>
Delete will remove the session, removing all commands and output from history. Delete will remove the workspace, removing all commands and output from history.
</InfoMessage> </InfoMessage>
</div> </div>
<div className="settings-input"> <div className="settings-input">
@ -422,7 +431,7 @@ class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
onClick={this.handleDeleteSession} onClick={this.handleDeleteSession}
className="button is-prompt-danger is-outlined is-small" className="button is-prompt-danger is-outlined is-small"
> >
Delete Session Delete Workspace
</div> </div>
</div> </div>
</div> </div>
@ -625,7 +634,7 @@ class ClientSettingsModal extends React.Component<{}, {}> {
renderFontSizeDropdown(): any { renderFontSizeDropdown(): any {
let availableFontSizes = [8, 9, 10, 11, 12, 13, 14, 15]; let availableFontSizes = [8, 9, 10, 11, 12, 13, 14, 15];
let fsize: number = 0; let fsize: number = 0;
let curSize = GlobalModel.termFontSize.get() let curSize = GlobalModel.termFontSize.get();
return ( return (
<div className={cn("dropdown", "font-size-dropdown", { "is-active": this.fontSizeDropdownActive.get() })}> <div className={cn("dropdown", "font-size-dropdown", { "is-active": this.fontSizeDropdownActive.get() })}>
<div className="dropdown-trigger"> <div className="dropdown-trigger">

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -30,17 +33,6 @@ type RendererComponentType = {
new (props: RendererComponentProps): React.Component<RendererComponentProps, {}>; new (props: RendererComponentProps): React.Component<RendererComponentProps, {}>;
}; };
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 { function makeFullRemoteRef(ownerName: string, remoteRef: string, name: string): string {
if (isBlank(ownerName) && isBlank(name)) { if (isBlank(ownerName) && isBlank(name)) {
return remoteRef; return remoteRef;
@ -64,6 +56,17 @@ function getRemoteStr(rptr: RemotePtrType): string {
return fullRef; 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 { function replaceHomePath(path: string, homeDir: string): string {
if (path == homeDir) { if (path == homeDir) {
return "~"; return "~";
@ -153,4 +156,4 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
} }
} }
export { Prompt }; export { Prompt, getRemoteStr };

View File

@ -1,7 +1,7 @@
@base-color: #eceeec; @base-color: #eceeec;
@base-background: rgba(21, 23, 21, 1); @base-background: rgba(21, 23, 21, 1);
@base-background-transparent: rgba(21, 23, 21, 0.7); @base-background-transparent: rgba(21, 23, 21, 0.7);
@base-background-dev: rgba(21, 23, 21, 0.7); @base-background-dev: rgba(57, 0, 78, 0.7);
@base-border: rgba(241, 246, 243, 0.08); @base-border: rgba(241, 246, 243, 0.08);
@background-session: rgba(13, 13, 13, 0.85); @background-session: rgba(13, 13, 13, 0.85);
@background-session-components: rgba(48, 49, 48, 0.6); @background-session-components: rgba(48, 49, 48, 0.6);
@ -18,6 +18,18 @@
@warning-yellow: #ffa500; @warning-yellow: #ffa500;
@textarea-background: #2a2a2a; @textarea-background: #2a2a2a;
@text-primary: #fff;
@text-secondary: #C3C8C2;
@text-caption: #8b918a;
@status-outline: #151715;
@dropdown-menu: rgba(21, 23, 21, 1);
@status-connected: #46A758;
@status-connecting: #f5d90a;
@status-error: #e54d2e;
@status-disconnected: #c3c8c2;
@term-black: #000000; @term-black: #000000;
@term-red: #cc0000; @term-red: #cc0000;
@term-green: #4e9a06; @term-green: #4e9a06;
@ -35,15 +47,16 @@
@term-bright-cyan: #34e2e2; @term-bright-cyan: #34e2e2;
@term-bright-white: #ffffff; @term-bright-white: #ffffff;
@tab-black: rgb(50, 50, 50); @tab-red: #e54d2e;
@tab-red: rgb(205, 49, 49); @tab-green: #58c142;
@tab-green: rgb(0, 128, 0); @tab-orange: #ef713b;
@tab-orange: rgb(255, 199, 6); @tab-violet: #8b46d0;
@tab-yellow: rgb(229, 229, 16); @tab-cyan: #3abab6;
@tab-blue: rgb(0, 71, 171); @tab-magenta: #fc3651;
@tab-magenta: rgb(188, 63, 188); @tab-pink: #e05677;
@tab-cyan: rgb(17, 168, 205); @tab-blue: #5460cf;
@tab-white: rgb(249, 249, 249); @tab-yellow: #e0b956;
@tab-white: #ffffff;
@tab-black-text: #333; @tab-black-text: #333;
@tab-white-text: #d7d7d7; @tab-white-text: #d7d7d7;
@ -56,6 +69,9 @@
@fixed-font: "Martian Mono", sans-serif; @fixed-font: "Martian Mono", sans-serif;
@terminal-font: "JetBrains Mono", sans-serif; @terminal-font: "JetBrains Mono", sans-serif;
@text-s1-font: "Martian Mono", sans-serif;
// to match Github // to match Github
@markdown-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial,sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; @markdown-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji";
@markdown-highlight: rgb(35, 35, 35); @markdown-highlight: rgb(35, 35, 35);

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
/** /**
* The file contains barebones of styling to appy themes to Prompt. * The file contains barebones of styling to appy themes to Prompt.
* @TODO: Find a way to change the theme system-wide. atm, we are captruing colors in main.less * @TODO: Find a way to change the theme system-wide. atm, we are captruing colors in main.less

View File

@ -67,6 +67,7 @@
.remote-status-light { .remote-status-light {
width: 2em; width: 2em;
margin-top: 0.7em; margin-top: 0.7em;
margin-right: 0.7em;
font-size: 0.8em; font-size: 0.8em;
} }
@ -76,6 +77,7 @@
.remote-name-primary { .remote-name-primary {
font-weight: bold; font-weight: bold;
max-width: 10em; max-width: 10em;
margin-right: 1em;
} }
.remote-name-secondary { .remote-name-secondary {
@ -267,3 +269,66 @@
} }
} }
} }
.remotes-inline {
.icon {
width: 1em;
height: 1em;
fill: @base-color;
margin: 0 0 0 1em !important;
vertical-align: middle;
}
.dropdown {
margin-top: 1em;
.button {
color: @base-color;
border: none !important;
padding: 0 1em 0 0.2em;
&:hover,
&:focus {
border: none !important;
box-shadow: none;
}
.remote-name {
vertical-align: bottom;
.remote-status {
top: -2px;
left: 4px;
vertical-align: middle;
font-size: 0.85em;
}
}
}
.dropdown-content {
background: @background-session-components-solid;
}
.dropdown-item {
min-width: max-content;
}
&.is-active:hover {
box-shadow: none;
}
}
.remote-status-light {
display: inline;
margin-right: 0.5em;
}
.remote-name {
display: inline;
flex-grow: 1;
vertical-align: super;
.remote-name-primary {
color: @base-color;
max-width: inherit;
margin-right: 1em;
display: inline;
}
.remote-name-secondary {
color: @disabled-color;
max-width: inherit;
display: inline;
}
}
}

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -150,7 +153,6 @@ class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdi
tempManualMode: OV<boolean>; tempManualMode: OV<boolean>;
tempPassword: OV<string>; tempPassword: OV<string>;
tempKeyFile: OV<string>; tempKeyFile: OV<string>;
tempAutoInstall: OV<boolean>;
errorStr: OV<string>; errorStr: OV<string>;
constructor(props: any) { 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.tempConnectMode = mobx.observable.box("auto", { name: "CreateRemote-connectMode" });
this.tempKeyFile = mobx.observable.box("", { name: "CreateRemote-keystr" }); this.tempKeyFile = mobx.observable.box("", { name: "CreateRemote-keystr" });
this.tempPassword = mobx.observable.box("", { name: "CreateRemote-password" }); 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" }); 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["password"] = "";
} }
kwargs["connectmode"] = this.tempConnectMode.get(); kwargs["connectmode"] = this.tempConnectMode.get();
kwargs["autoinstall"] = this.tempAutoInstall.get() ? "1" : "0";
kwargs["visual"] = "1"; kwargs["visual"] = "1";
kwargs["submit"] = "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 @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() { render() {
let { model, remoteEdit } = this.props; let { model, remoteEdit } = this.props;
let authMode = this.tempAuthMode.get(); let authMode = this.tempAuthMode.get();
@ -413,19 +428,6 @@ class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdi
</div> </div>
</div> </div>
</div> </div>
<div className="settings-field" style={{ marginTop: 10 }}>
<div className="settings-label">
<div>Auto Install</div>
<div className="flex-spacer" />
<InfoMessage width={350}>
If selected, will try to auto-install the mshell client if it is not installed or out of
date.
</InfoMessage>
</div>
<div className="settings-input">
<Toggle checked={this.tempAutoInstall.get()} onChange={this.handleChangeAutoInstall} />
</div>
</div>
<If condition={!util.isBlank(this.getErrorStr())}> <If condition={!util.isBlank(this.getErrorStr())}>
<div className="settings-field settings-error">Error: {this.getErrorStr()}</div> <div className="settings-field settings-error">Error: {this.getErrorStr()}</div>
</If> </If>
@ -459,7 +461,6 @@ class EditRemoteSettings extends React.Component<
tempManualMode: OV<boolean>; tempManualMode: OV<boolean>;
tempPassword: OV<string>; tempPassword: OV<string>;
tempKeyFile: OV<string>; tempKeyFile: OV<string>;
tempAutoInstall: OV<boolean>;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
@ -471,7 +472,6 @@ class EditRemoteSettings extends React.Component<
this.tempPassword = mobx.observable.box(remoteEdit.haspassword ? PasswordUnchangedSentinel : "", { this.tempPassword = mobx.observable.box(remoteEdit.haspassword ? PasswordUnchangedSentinel : "", {
name: "EditRemoteSettings-password", name: "EditRemoteSettings-password",
}); });
this.tempAutoInstall = mobx.observable.box(!!remote.autoinstall, { name: "EditRemoteSettings-autoinstall" });
} }
componentDidUpdate() { componentDidUpdate() {
@ -527,13 +527,6 @@ class EditRemoteSettings extends React.Component<
})(); })();
} }
@boundMethod
handleChangeAutoInstall(val: boolean): void {
mobx.action(() => {
this.tempAutoInstall.set(val);
})();
}
@boundMethod @boundMethod
canResetPw(): boolean { canResetPw(): boolean {
let { remoteEdit } = this.props; let { remoteEdit } = this.props;
@ -584,9 +577,6 @@ class EditRemoteSettings extends React.Component<
if (!util.isStrEq(this.tempConnectMode.get(), remote.connectmode)) { if (!util.isStrEq(this.tempConnectMode.get(), remote.connectmode)) {
kwargs["connectmode"] = this.tempConnectMode.get(); 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) { if (Object.keys(kwargs).length == 0) {
return; return;
} }
@ -733,19 +723,6 @@ class EditRemoteSettings extends React.Component<
</div> </div>
</div> </div>
</div> </div>
<div className="settings-field" style={{ marginTop: 10 }}>
<div className="settings-label">
<div>Auto Install</div>
<div className="flex-spacer" />
<InfoMessage width={350}>
If selected, will try to auto-install the mshell client if it is not installed or out of
date.
</InfoMessage>
</div>
<div className="settings-input">
<Toggle checked={this.tempAutoInstall.get()} onChange={this.handleChangeAutoInstall} />
</div>
</div>
<div className="settings-field mt-3"> <div className="settings-field mt-3">
<div className="settings-label">Actions</div> <div className="settings-label">Actions</div>
<div className="settings-input"> <div className="settings-input">
@ -866,9 +843,6 @@ class RemoteDetailView extends React.Component<{ model: RemotesModalModel; remot
if (statusStr == null) { if (statusStr == null) {
return null; return null;
} }
if (remote.autoinstall) {
statusStr = statusStr + " (autoinstall)";
}
return ( return (
<div key="install-status" className="settings-field"> <div key="install-status" className="settings-field">
<div className="settings-label"> Install Status</div> <div className="settings-label"> Install Status</div>
@ -1102,7 +1076,7 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> {
renderRemoteMenuItem(remote: RemoteType, selectedId: string): any { renderRemoteMenuItem(remote: RemoteType, selectedId: string): any {
return ( return (
<div <div
key={remote.remoteid} key={remote.remotecanonicalname}
onClick={() => this.selectRemote(remote.remoteid)} onClick={() => this.selectRemote(remote.remoteid)}
className={cn("remote-menu-item", { "is-selected": remote.remoteid == selectedId })} 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 isAuthEditMode = model.isAuthEditMode();
let selectedRemote = GlobalModel.getRemote(selectedRemoteId); let selectedRemote = GlobalModel.getRemote(selectedRemoteId);
let remoteEdit = model.remoteEdit.get(); let remoteEdit = model.remoteEdit.get();
let onlyAddNewRemote = model.onlyAddNewRemote.get();
return ( return (
<div className={cn("modal remotes-modal settings-modal prompt-modal is-active")}> <div className={cn("modal remotes-modal settings-modal prompt-modal is-active")}>
<div className="modal-background" /> <div className="modal-background" />
@ -1161,19 +1136,21 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> {
</div> </div>
</header> </header>
<div className="inner-content"> <div className="inner-content">
<div className="remotes-menu"> <If condition={!onlyAddNewRemote}>
{this.renderAddRemoteMenuItem()} <div className="remotes-menu">
<For each="remote" of={allRemotes}> {this.renderAddRemoteMenuItem()}
{this.renderRemoteMenuItem(remote, selectedRemoteId)} <For each="remote" of={allRemotes}>
</For> {this.renderRemoteMenuItem(remote, selectedRemoteId)}
</div> </For>
</div>{" "}
</If>
<If condition={selectedRemote == null}> <If condition={selectedRemote == null}>
<If condition={remoteEdit != null}> <If condition={remoteEdit != null}>
<CreateRemote model={model} remoteEdit={remoteEdit} /> <CreateRemote model={model} remoteEdit={remoteEdit} />
</If> </If>
<If condition={remoteEdit == null}>{this.renderEmptyDetail()}</If> <If condition={remoteEdit == null}>{this.renderEmptyDetail()}</If>
</If> </If>
<If condition={selectedRemote != null}> <If condition={selectedRemote != null && !onlyAddNewRemote}>
<If condition={!isAuthEditMode}> <If condition={!isAuthEditMode}>
<RemoteDetailView <RemoteDetailView
key={"remotedetail-" + selectedRemoteId} key={"remotedetail-" + selectedRemoteId}
@ -1197,4 +1174,95 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> {
} }
} }
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 (
<div
key={remote.remoteid}
onClick={() => this.selectRemote(remote.remoteid, remote.remotecanonicalname)}
className={cn("dropdown-item remote-menu-item hoverEffect", {
"is-selected": remote.remoteid == selectedId,
})}
>
<div className="remote-status-light">
<RemoteStatusLight remote={remote} />
</div>
<If condition={util.isBlank(remote.remotealias)}>
<div className="remote-name">
<div className="remote-name-primary">{remote.remotecanonicalname}</div>
</div>
</If>
<If condition={!util.isBlank(remote.remotealias)}>
<div className="remote-name">
<div className="remote-name-primary">{remote.remotealias}</div>
<div className="remote-name-secondary">{remote.remotecanonicalname}</div>
</div>
</If>
</div>
);
}
render() {
const allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice());
const remote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
const selectedRemoteDiv = (
<div className="remote-name">
<div className="remote-status-light">
<RemoteStatusLight remote={remote} />
</div>
<div className="remote-name-primary">{remote.remotealias}</div>
<div className="remote-name-secondary">{remote.remotecanonicalname}</div>
</div>
);
return (
<div className={"remotes-inline"}>
<div className="remotes-menu">
<div className={`dropdown ${this.state.isOpen ? "is-active" : ""}`}>
<div className="dropdown-trigger">
<button className="button" onClick={() => this.setState({ isOpen: !this.state.isOpen })}>
{selectedRemoteDiv}
<AngleDownIcon className="icon" />
</button>
</div>
<div className="dropdown-menu" id="dropdown-menu3" role="menu">
<div className="dropdown-content">
{allRemotes
.filter(({ remoteid }) => remoteid !== remote.remoteid)
.map((remote) => this.renderRemoteMenuItem(remote, remote.remoteid))}
<div onClick={this.clickAddRemote} className=".dropdown-item hoverEffect">
<AddIcon className="icon" /> Add SSH Connection
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export { RemotesModal, RemotesSelector };

View File

@ -323,7 +323,7 @@
margin-right: 1em; margin-right: 1em;
} }
td.session { td.workspace {
flex: 0 0 auto; flex: 0 0 auto;
flex-basis: 120px; flex-basis: 120px;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -373,7 +376,7 @@ class HistoryView extends React.Component<{}, {}> {
<div onClick={this.toggleSessionDropdown}> <div onClick={this.toggleSessionDropdown}>
<span className="label"> <span className="label">
{hvm.searchSessionId.get() == null {hvm.searchSessionId.get() == null
? "Limit Session" ? "Limit Workspace"
: formatSessionName(snames, hvm.searchSessionId.get())} : formatSessionName(snames, hvm.searchSessionId.get())}
</span> </span>
<AngleDownIcon className="icon" /> <AngleDownIcon className="icon" />
@ -385,7 +388,7 @@ class HistoryView extends React.Component<{}, {}> {
key="all" key="all"
className="dropdown-item" className="dropdown-item"
> >
(all sessions) (all workspaces)
</div> </div>
<For each="sessionId" of={sessionIds}> <For each="sessionId" of={sessionIds}>
<div <div
@ -535,7 +538,7 @@ class HistoryView extends React.Component<{}, {}> {
<FavoritesIcon className="icon" /> <FavoritesIcon className="icon" />
</td> </td>
<td className="ts">{getHistoryViewTs(nowDate, item.ts)}</td> <td className="ts">{getHistoryViewTs(nowDate, item.ts)}</td>
<td className="session">{formatSSName(snames, scrnames, item)}</td> <td className="workspace">{formatSSName(snames, scrnames, item)}</td>
<td className="remote">{formatRemoteName(rnames, item.remote)}</td> <td className="remote">{formatRemoteName(rnames, item.remote)}</td>
<td className="cmdstr" onClick={() => this.activateItem(item.historyid)}> <td className="cmdstr" onClick={() => this.activateItem(item.historyid)}>
<CmdStrCode <CmdStrCode

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -35,6 +38,7 @@ import { isBlank } from "../../util/util";
import { PluginModel } from "../../plugins/plugins"; import { PluginModel } from "../../plugins/plugins";
import { Prompt } from "../common/prompt/prompt"; import { Prompt } from "../common/prompt/prompt";
import * as lineutil from "./lineutil"; import * as lineutil from "./lineutil";
import { ErrorBoundary } from "../../app/common/error/errorboundary";
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg"; import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
import { ReactComponent as CommentIcon } from "../assets/icons/line/comment.svg"; import { ReactComponent as CommentIcon } from "../assets/icons/line/comment.svg";
@ -591,7 +595,6 @@ class LineCmd extends React.Component<
let isRunning = cmd.isRunning(); let isRunning = cmd.isRunning();
let isExpanded = this.isCmdExpanded.get(); let isExpanded = this.isCmdExpanded.get();
let rsdiff = this.rtnStateDiff.get(); let rsdiff = this.rtnStateDiff.get();
// console.log("render", "#" + line.linenum, termHeight, usedRows, cmd.getStatus(), (this.rtnStateDiff.get() != null), (!cmd.isRunning() ? "cmd-done" : "running"));
let mainDivCn = cn( let mainDivCn = cn(
"line", "line",
"line-cmd", "line-cmd",
@ -661,39 +664,41 @@ class LineCmd extends React.Component<
</div> </div>
</div> </div>
<If condition={!this.isMinimised.get()}> <If condition={!this.isMinimised.get()}>
<If condition={rendererPlugin == null && !isNoneRenderer}> <ErrorBoundary plugin={rendererPlugin?.name} lineContext={lineutil.getRendererContext(line)}>
<TerminalRenderer <If condition={rendererPlugin == null && !isNoneRenderer}>
screen={screen} <TerminalRenderer
line={line} screen={screen}
width={width} line={line}
staticRender={staticRender} width={width}
visible={visible} staticRender={staticRender}
onHeightChange={this.handleHeightChange} visible={visible}
collapsed={false} onHeightChange={this.handleHeightChange}
/> collapsed={false}
</If> />
<If condition={rendererPlugin != null && rendererPlugin.rendererType == "simple"}> </If>
<SimpleBlobRenderer <If condition={rendererPlugin != null && rendererPlugin.rendererType == "simple"}>
rendererContainer={screen} <SimpleBlobRenderer
lineId={line.lineid} rendererContainer={screen}
plugin={rendererPlugin} lineId={line.lineid}
onHeightChange={this.handleHeightChange} plugin={rendererPlugin}
initParams={this.makeRendererModelInitializeParams()} onHeightChange={this.handleHeightChange}
scrollToBringIntoViewport={this.scrollToBringIntoViewport} initParams={this.makeRendererModelInitializeParams()}
isSelected={isSelected} scrollToBringIntoViewport={this.scrollToBringIntoViewport}
shouldFocus={shouldCmdFocus} isSelected={isSelected}
/> shouldFocus={shouldCmdFocus}
</If> />
<If condition={rendererPlugin != null && rendererPlugin.rendererType == "full"}> </If>
<IncrementalRenderer <If condition={rendererPlugin != null && rendererPlugin.rendererType == "full"}>
rendererContainer={screen} <IncrementalRenderer
lineId={line.lineid} rendererContainer={screen}
plugin={rendererPlugin} lineId={line.lineid}
onHeightChange={this.handleHeightChange} plugin={rendererPlugin}
initParams={this.makeRendererModelInitializeParams()} onHeightChange={this.handleHeightChange}
isSelected={isSelected} initParams={this.makeRendererModelInitializeParams()}
/> isSelected={isSelected}
</If> />
</If>
</ErrorBoundary>
<If condition={cmd.getRtnState()}> <If condition={cmd.getRtnState()}>
<div <div
key="rtnstate" key="rtnstate"
@ -738,6 +743,7 @@ class Line extends React.Component<
overrideCollapsed: OV<boolean>; overrideCollapsed: OV<boolean>;
renderMode: RenderModeType; renderMode: RenderModeType;
noSelect?: boolean; noSelect?: boolean;
topBorder: boolean;
}, },
{} {}
> { > {

View File

@ -467,7 +467,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: auto; overflow: auto;
padding: 10px 0; padding: 0 0 10px 0;
flex-grow: 1; flex-grow: 1;
position: relative; position: relative;

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import dayjs from "dayjs"; import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat"; import localizedFormat from "dayjs/plugin/localizedFormat";
import { isBlank, getDateStr } from "../../util/util"; import { isBlank, getDateStr } from "../../util/util";

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";

34
src/app/magiclayout.ts Normal file
View File

@ -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 };

View File

@ -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;
}
}
}
}

View File

@ -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 <Comp/>;
}
render() {
if (GlobalModel.activeMainView.get() !== "plugins") {
return <></>;
}
const { pluginsModel } = GlobalModel;
console.log(`rendering details for ${pluginsModel.selectedPlugin.get().name}`);
const PluginList = () => (
<div className="plugins-list">
{PluginModel.allPlugins().map((plugin, i) => (
<div
key={i}
className={`plugin-summary hoverEffect ${
plugin.name === pluginsModel.selectedPlugin.get().name ? "selected" : ""
}`}
onClick={() => pluginsModel.setSelectedPlugin(plugin)}
>
<div className="plugin-summary-header">
<div className="plugin-summary-icon">{this.renderPluginIcon(plugin)}</div>
<div className="plugin-summary-info">
<div className="plugin-summary-title">{plugin.title}</div>
<div className="plugin-summary-vendor">{plugin.vendor}</div>
</div>
</div>
<div className="plugin-summary-body">{plugin.summary}</div>
</div>
))}
</div>
);
const PluginDetails = () => {
const plugin = pluginsModel.selectedPlugin.get();
return (
<div className="plugins-details">
<div className="plugin-summary-header">
<div className="plugin-summary-icon">{this.renderPluginIcon(plugin)}</div>
<div className="plugin-summary-info">
<div className="plugin-summary-title">{plugin.title}</div>
<div className="plugin-summary-vendor">{plugin.vendor}</div>
</div>
</div>
<div className="plugin-summary-body">{plugin.summary}</div>
{plugin.screenshots && plugin.screenshots.length > 0 && (
<div className="plugin-screenshots-container">
<div className="plugin-label">{"Screenshots"}</div>
<div className="plugin-screenshots">
{plugin.screenshots.map((path, index) => (
<img key={index} src={path} alt={`Screenshot ${index}`} />
))}
</div>
</div>
)}
{plugin.readme && (
<div className="plugin-readme">
<div className="plugin-label">{"Readme"}</div>
<Markdown text={plugin.readme} />
</div>
)}
</div>
);
};
return (
<div className="plugins-view">
<div className="header">
<div className="plugins-title">Apps</div>
<div className="close-button hoverEffect" title="Close (Escape)" onClick={this.closeView}>
<XmarkIcon className={"icon"} />
</div>
</div>
<div className="body">
<PluginList />
<PluginDetails />
</div>
</div>
);
}
}
export { PluginsView };

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -74,16 +77,18 @@ class MainSideBar extends React.Component<{}, {}> {
} }
@boundMethod @boundMethod
handleAddRemote(): void { handlePluginsClick(): void {
GlobalCommandRunner.openCreateRemote(); if (GlobalModel.activeMainView.get() == "plugins") {
GlobalModel.showSessionView();
return;
}
GlobalModel.pluginsModel.showPluginsView();
} }
@boundMethod @boundMethod
handleHistoryClick(): void { handleHistoryClick(): void {
if (GlobalModel.activeMainView.get() == "history") { if (GlobalModel.activeMainView.get() == "history") {
mobx.action(() => { GlobalModel.showSessionView();
GlobalModel.activeMainView.set("session");
})();
return; return;
} }
GlobalModel.historyViewModel.reSearch(); GlobalModel.historyViewModel.reSearch();
@ -152,14 +157,12 @@ class MainSideBar extends React.Component<{}, {}> {
} }
return sessionList.map((session, index) => { return sessionList.map((session, index) => {
const isActive = GlobalModel.activeMainView.get() == "session" && activeSessionId == session.sessionId; const isActive = GlobalModel.activeMainView.get() == "session" && activeSessionId == session.sessionId;
/** @TODO: Handle archived sessions and talk to Mike about session settings */
return ( return (
<div <div
key={index} key={index}
className={`item hoverEffect ${isActive ? "active" : ""}`} className={`item hoverEffect ${isActive ? "active" : ""}`}
onClick={() => this.handleSessionClick(session.sessionId)} onClick={() => this.handleSessionClick(session.sessionId)}
> >
<span className="hotkey">^</span>
<span className="index">{index + 1}</span> <span className="index">{index + 1}</span>
<span className="truncate sessionName">{session.name.get()}</span> <span className="truncate sessionName">{session.name.get()}</span>
<ActionsIcon <ActionsIcon
@ -197,17 +200,17 @@ class MainSideBar extends React.Component<{}, {}> {
let mainView = GlobalModel.activeMainView.get(); let mainView = GlobalModel.activeMainView.get();
return ( return (
<div className={cn("main-sidebar", { collapsed: isCollapsed }, { "is-dev": GlobalModel.isDev })}> <div className={cn("main-sidebar", { collapsed: isCollapsed }, { "is-dev": GlobalModel.isDev })}>
<div className="title-bar-drag"/> <div className="title-bar-drag" />
<div className="arrow-container hoverEffect" onClick={this.toggleCollapsed}> <div className="arrow-container hoverEffect" onClick={this.toggleCollapsed}>
<LeftChevronIcon className="icon" /> <LeftChevronIcon className="icon" />
</div> </div>
<div className="contents"> <div className="contents">
<div className="top"> <div className="top">
{/*<div className="item disabled"> <div className="item hoverEffect" onClick={this.handlePluginsClick}>
<AppsIcon className="icon" /> <AppsIcon className="icon" />
Apps Apps
<span className="hotkey">&#x2318;A</span> <span className="hotkey">&#x2318;A</span>
</div>*/} </div>
<div className="item hoverEffect" onClick={this.handleHistoryClick}> <div className="item hoverEffect" onClick={this.handleHistoryClick}>
<HistoryIcon className="icon" /> <HistoryIcon className="icon" />
History History
@ -224,7 +227,7 @@ class MainSideBar extends React.Component<{}, {}> {
</div> </div>
</div> </div>
<div className="separator" /> <div className="separator" />
<div className="item"> <div className="item workspaces-item">
<WorkspacesIcon className="icon" /> <WorkspacesIcon className="icon" />
Workspaces Workspaces
<div className="add_workspace hoverEffect" onClick={this.handleNewSession}> <div className="add_workspace hoverEffect" onClick={this.handleNewSession}>

View File

@ -7,6 +7,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
font-size: 12.5px;
line-height: 20px;
.title-bar-drag { .title-bar-drag {
-webkit-app-region: drag; -webkit-app-region: drag;
@ -32,7 +34,7 @@
.arrow-container { .arrow-container {
position: absolute; position: absolute;
top: 2em; top: 1em;
right: 2em; right: 2em;
width: 1.5em; width: 1.5em;
height: 1.5em; height: 1.5em;
@ -45,7 +47,7 @@
} }
.top { .top {
margin-top: 4em; margin-top: 32px;
} }
.separator { .separator {
@ -54,9 +56,13 @@
background-color: @base-border; background-color: @base-border;
} }
.item.workspaces-item {
margin-bottom: -4px;
}
.middle { .middle {
max-height: calc(100vh - 32em); max-height: calc(100vh - 32em);
padding: 8px 6px; padding: 4px 6px 8px 6px;
border-bottom: 1px solid @base-border; border-bottom: 1px solid @base-border;
.item { .item {
&.active { &.active {
@ -89,30 +95,27 @@
} }
.item { .item {
padding: 6px; padding: 5px;
margin: 0 6px; margin: 0 6px;
border-radius: 4px; border-radius: 4px;
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
transition: opacity 0.1s ease-in-out, visibility 0.1s step-end; transition: opacity 0.1s ease-in-out, visibility 0.1s step-end;
.sessionName { .sessionName {
width: 11rem; width: 12rem;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
} }
.icon { .icon {
margin: 0 0.5em 2px 0.5em; margin: -2px 8px 0px 4px;
width: 1em; width: 16px;
height: 1em; height: 16px;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
border-radius: 50%; border-radius: 50%;
}
&.discord { .actions.icon {
width: 1.2em; margin-left: 8px;
height: 1.2em;
margin: 0 0.4em 2px 0.4em;
}
} }
.hotkey { .hotkey {
float: right; float: right;

View File

@ -6,10 +6,11 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: absolute; position: absolute;
bottom: 16px; bottom: 12px;
right: 16px; right: 12px;
width: calc(100% - 28px); width: calc(100% - 24px);
padding: 12px; padding: 12px;
padding-bottom: 6px;
z-index: 100; z-index: 100;
background: @background-session-components; 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), box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5), 0px 3px 8px 0px rgba(0, 0, 0, 0.35),

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -64,6 +67,11 @@ class CmdInput extends React.Component<{}, {}> {
GlobalModel.inputModel.giveFocus(); GlobalModel.inputModel.giveFocus();
} }
@boundMethod
cmdInputClick(): void {
GlobalModel.inputModel.giveFocus();
}
@boundMethod @boundMethod
clickHistoryHint(e: any): void { clickHistoryHint(e: any): void {
e.preventDefault(); e.preventDefault();
@ -106,7 +114,11 @@ class CmdInput extends React.Component<{}, {}> {
let inputMode: string = inputModel.inputMode.get(); let inputMode: string = inputModel.inputMode.get();
let textAreaInputKey = screen == null ? "null" : screen.screenId; let textAreaInputKey = screen == null ? "null" : screen.screenId;
return ( return (
<div ref={this.cmdInputRef} className={cn("cmd-input", { "has-info": infoShow }, { active: focusVal })}> <div
ref={this.cmdInputRef}
className={cn("cmd-input", { "has-info": infoShow }, { active: focusVal })}
onClick={this.cmdInputClick}
>
<div key="minmax" onClick={this.onInfoToggle} className="input-minmax-control"> <div key="minmax" onClick={this.onInfoToggle} className="input-minmax-control">
<If condition={infoShow || historyShow}> <If condition={infoShow || historyShow}>
<i className="fa-sharp fa-solid fa-chevron-down" /> <i className="fa-sharp fa-solid fa-chevron-down" />
@ -153,10 +165,10 @@ class CmdInput extends React.Component<{}, {}> {
</div> </div>
</If> </If>
<TextAreaInput key={textAreaInputKey} onHeightChange={this.handleInnerHeightUpdate} /> <TextAreaInput key={textAreaInputKey} onHeightChange={this.handleInnerHeightUpdate} />
<div className="control cmd-exec" onClick={inputModel.uiSubmitCommand}> <div className="control cmd-exec">
{/**<div onClick={inputModel.toggleExpandInput} className="hint-item color-white"> {/**<div onClick={inputModel.toggleExpandInput} className="hint-item color-white">
{inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")}) {inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")})
</div>**/} </div>**/}
{!focusVal && ( {!focusVal && (
<div onClick={this.clickFocusInputHint} className="cmd-btn hoverEffect"> <div onClick={this.clickFocusInputHint} className="cmd-btn hoverEffect">
focus input ({renderCmdText("I")}) focus input ({renderCmdText("I")})
@ -168,6 +180,7 @@ class CmdInput extends React.Component<{}, {}> {
</div> </div>
)} )}
<ExecIcon <ExecIcon
onClick={inputModel.uiSubmitCommand}
className={`icon ${inputModel.getCurLine().trim() === "" ? "disabled" : "hoverEffect"}`} className={`icon ${inputModel.getCurLine().trim() === "" ? "disabled" : "hoverEffect"}`}
/> />
</div> </div>

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import { If, For } from "tsx-control-statements/components"; import { If, For } from "tsx-control-statements/components";
@ -16,6 +19,9 @@ class InfoMsg extends React.Component<{}, {}> {
if (s.startsWith("^/")) { if (s.startsWith("^/")) {
return s.substr(1); return s.substr(1);
} }
if (s.startsWith("^")) {
return s.substr(1);
}
let slashIdx = s.lastIndexOf("/"); let slashIdx = s.lastIndexOf("/");
if (slashIdx == s.length - 1) { if (slashIdx == s.length - 1) {
slashIdx = s.lastIndexOf("/", slashIdx - 1); slashIdx = s.lastIndexOf("/", slashIdx - 1);

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";

View File

@ -11,7 +11,7 @@
flex-direction: column; flex-direction: column;
position: absolute; position: absolute;
width: 100%; width: 100%;
height: calc(100% - 3em); height: calc(100% - 0.5rem);
overflow-x: hidden; overflow-x: hidden;
.rendermode-tag { .rendermode-tag {
@ -78,25 +78,209 @@
} }
} }
} }
}
.window-empty { .screen-settings-inline {
display: flex; padding: 2em;
align-items: center; .settings-field {
justify-content: center; display: block;
width: 100%; padding: 1.5em 1em;
padding: 10px; margin-top: 0;
height: 100%; line-height: 2.5em;
color: @term-white; border-top: 1px solid @base-border;
&:first-child {
code { border-top: none;
color: @prompt-green; }
background-color: transparent; }
font-family: @fixed-font; }
} }
&.should-fade { .newtab-container {
opacity: 1; margin: 16px;
animation: fade-in 2.5s;
.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;
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;
.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;
}
&:hover {
background-color: rgba(241, 246, 243, 0.08);
}
} }
} }
} }

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -7,11 +10,23 @@ import { If, For } from "tsx-control-statements/components";
import cn from "classnames"; import cn from "classnames";
import { debounce } from "throttle-debounce"; import { debounce } from "throttle-debounce";
import dayjs from "dayjs"; 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 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 { GlobalModel, ScreenLines, Screen } from "../../../model/model";
import { Line } from "../../line/linecomps"; import { Line } from "../../line/linecomps";
import { LinesView } from "../../line/linesview"; 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 "./screenview.less";
import "./tabs.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<boolean> = mobx.observable.box(false, { name: "NewTabSettings-connDropdownActive" });
errorMessage: OV<string> = 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 (
<div className={cn("dropdown", "conn-dropdown", { "is-active": this.connDropdownActive.get() })}>
<div className="dropdown-trigger" onClick={this.toggleConnDropdown}>
<div className="conn-dd-trigger">
<div className="lefticon">
<GlobeIcon className="globe-icon"/>
<StatusCircleIcon className={cn("status-icon", "status-" + curRemote.status)}/>
</div>
<div className="conntext">
<If condition={util.isBlank(curRemote.remotealias)}>
<div className="text-standard conntext-solo">
{curRemote.remotecanonicalname}
</div>
</If>
<If condition={!util.isBlank(curRemote.remotealias)}>
<div className="text-secondary conntext-1">
{curRemote.remotealias}
</div>
<div className="text-caption conntext-2">
{curRemote.remotecanonicalname}
</div>
</If>
</div>
<div className="dd-control">
<ArrowsUpDownIcon className="icon"/>
</div>
</div>
</div>
<div className="dropdown-menu" role="menu">
<div className="dropdown-content conn-dd-menu">
<For each="remote" of={allRemotes}>
<div className="dropdown-item" key={remote.remoteid} onClick={() => this.selectRemote(remote.remotecanonicalname)}>
<div className="status-div">
<CircleIcon className={cn("status-icon", "status-" + remote.status)}/>
</div>
<If condition={util.isBlank(remote.remotealias)}>
<div className="text-standard">{remote.remotecanonicalname}</div>
</If>
<If condition={!util.isBlank(remote.remotealias)}>
<div className="text-standard">{remote.remotealias}</div>
<div className="text-caption">{remote.remotecanonicalname}</div>
</If>
</div>
</For>
<div className="dropdown-item" onClick={this.clickNewConnection}>
<div className="add-div">
<AddIcon className="add-icon"/>
</div>
<div className="text-standard">New Connection</div>
</div>
</div>
</div>
</div>
);
}
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 (
<div className="newtab-container">
<div className="newtab-section conn-section">
<div className="text-s1">
You're connected to [{getRemoteStr(rptr)}]. Do you want to change it?
</div>
<div>
{this.renderConnDropdown()}
</div>
</div>
<div className="newtab-spacer"/>
<div className="newtab-section settings-field">
<div className="text-s1">
Name
</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder="name"
text={screen.name.get() ?? "(none)"}
value={screen.name.get() ?? ""}
onChange={this.inlineUpdateName}
maxLength={50}
showIcon={true}
/>
</div>
</div>
<div className="newtab-spacer"/>
<div className="newtab-section">
<div className="text-s1">
Select the color
</div>
<div className="control-iconlist">
<For each="color" of={TabColors}>
<div className="icondiv" key={color} title={color} onClick={() => this.selectTabColor(color)}>
<EllipseIcon className={cn("icon", "color-" + color)}/>
<If condition={color == curColor}>
<Check12Icon className="check-icon"/>
</If>
</div>
</For>
</div>
</div>
</div>
);
}
}
// screen is not null // screen is not null
@mobxReact.observer @mobxReact.observer
class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
@ -178,11 +359,7 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
if (cdata == null) { if (cdata == null) {
return this.renderError("loading client data", true); 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 isActive = screen.isActive();
let selectedLine = screen.getSelectedLine();
let lines = win.getNonArchivedLines(); let lines = win.getNonArchivedLines();
let renderMode = this.renderMode.get(); let renderMode = this.renderMode.get();
return ( return (
@ -201,6 +378,9 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
</If> </If>
</div> </div>
</div> </div>
<If condition={lines.length == 0}>
<NewTabSettings screen={screen}/>
</If>
<If condition={screen.isWebShared()}> <If condition={screen.isWebShared()}>
<div key="share-tag" className="share-tag"> <div key="share-tag" className="share-tag">
<If condition={this.shareCopied.get()}> <If condition={this.shareCopied.get()}>
@ -237,13 +417,6 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
lineFactory={this.buildLineComponent} lineFactory={this.buildLineComponent}
/> />
</If> </If>
<If condition={lines.length == 0}>
<div key="window-empty" className="window-empty">
<div>
<code>[workspace="{session.name.get()}" screen="{screen.name.get()}"]</code>
</div>
</div>
</If>
</div> </div>
); );
} }

View File

@ -1,8 +1,12 @@
@import "../../../app/common/themes/themes.less"; @import "../../../app/common/themes/themes.less";
#main .screen-tabs .screen-tab { #main .screen-tabs .screen-tab {
border-radius: 12px 0px 0px 0px;
border-top: 1px solid transparent; border-top: 1px solid transparent;
font-size: 12.5px;
&:first-child {
border-radius: 12px 0px 0px 0px;
}
&.color-default { &.color-default {
svg.left-icon path { svg.left-icon path {
@ -58,9 +62,15 @@
} }
} }
&.color-black { &.color-violet {
svg.left-icon path { 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 { .new-screen {
flex-shrink: 0;
margin-left: 1em; margin-left: 1em;
margin-right: 1em;
cursor: pointer; cursor: pointer;
.icon { .icon {
height: 2rem; height: 2rem;

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -191,11 +194,6 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
<AddIcon className="icon hoverEffect" /> <AddIcon className="icon hoverEffect" />
</div> </div>
</div> </div>
{/**<div className="cmd-hints">
<div className="hint-item color-green">move left {renderCmdText("[")}</div>
<div className="hint-item color-green">move right {renderCmdText("]")}</div>
<div className="hint-item color-green">new tab {renderCmdText("T")}</div>
</div>*/}
</div> </div>
); );
} }

View File

@ -12,8 +12,8 @@
background: @background-session; background: @background-session;
border: 1px solid @base-border; border: 1px solid @base-border;
border-radius: 8px; border-radius: 8px;
margin-bottom: 1em;
transition: width 0.2s ease; transition: width 0.2s ease;
margin-bottom: 0.5em;
} }
.collapsed + .session-view { .collapsed + .session-view {
max-width: calc(100% - 6.7em); max-width: calc(100% - 6.7em);

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -8,6 +11,8 @@ import { GlobalModel } from "../../model/model";
import { CmdInput } from "./cmdinput/cmdinput"; import { CmdInput } from "./cmdinput/cmdinput";
import { ScreenView } from "./screen/screenview"; import { ScreenView } from "./screen/screenview";
import { ScreenTabs } from "./screen/tabs"; import { ScreenTabs } from "./screen/tabs";
import { ErrorBoundary } from "../../app/common/error/errorboundary";
import { MagicLayout } from "../magiclayout";
import "./workspace.less"; import "./workspace.less";
dayjs.extend(localizedFormat); dayjs.extend(localizedFormat);
@ -25,15 +30,19 @@ class WorkspaceView extends React.Component<{}, {}> {
let activeScreen = session.getActiveScreen(); let activeScreen = session.getActiveScreen();
let cmdInputHeight = model.inputModel.cmdInputHeight.get(); let cmdInputHeight = model.inputModel.cmdInputHeight.get();
if (cmdInputHeight == 0) { 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"; let isHidden = GlobalModel.activeMainView.get() != "session";
return ( return (
<div className={cn("session-view", { "is-hidden": isHidden })} data-sessionid={session.sessionId}> <div className={cn("session-view", { "is-hidden": isHidden })} data-sessionid={session.sessionId}>
<ScreenTabs session={session} /> <ScreenTabs session={session} />
<ScreenView screen={activeScreen} /> <ErrorBoundary>
<div style={{ height: cmdInputHeight }}></div> <ScreenView screen={activeScreen} />
<CmdInput /> <div className="cmdinput-height-placeholder" style={{ height: cmdInputHeight }}></div>
<CmdInput />
</ErrorBoundary>
</div> </div>
); );
} }

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as electron from "electron"; import * as electron from "electron";
import * as path from "path"; import * as path from "path";
import * as fs from "fs"; import * as fs from "fs";
@ -40,7 +43,7 @@ let loggerConfig = {
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.printf((info) => `${info.timestamp} ${info.message}`) 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) { if (isDev) {
loggerConfig.transports.push(new winston.transports.Console()); loggerConfig.transports.push(new winston.transports.Console());
@ -56,7 +59,7 @@ function log(...msg) {
console.log = log; console.log = log;
console.log( console.log(
sprintf( sprintf(
"prompt-app starting, PROMPT_HOME=%s, apppath=%s arch=%s/%s", "waveterm-app starting, PROMPT_HOME=%s, apppath=%s arch=%s/%s",
scHome, scHome,
getAppBasePath(), getAppBasePath(),
unamePlatform, unamePlatform,
@ -67,7 +70,7 @@ if (isDev) {
console.log("prompt-app PROMPT_DEV set"); console.log("prompt-app PROMPT_DEV set");
} }
let app = electron.app; let app = electron.app;
app.setName(isDev ? "Prompt (Dev)" : "Prompt"); app.setName(isDev ? "Wave (Dev)" : "Wave");
let waveSrvProc = null; let waveSrvProc = null;
let waveSrvShouldRestart = false; let waveSrvShouldRestart = false;
@ -336,7 +339,7 @@ function mainResizeHandler(e, win) {
return; return;
} }
let bounds = win.getBounds(); 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 winSize = { width: bounds.width, height: bounds.height, top: bounds.y, left: bounds.x };
let url = getBaseHostPort() + "/api/set-winsize"; let url = getBaseHostPort() + "/api/set-winsize";
let fetchHeaders = getFetchHeaders(); let fetchHeaders = getFetchHeaders();
@ -603,7 +606,7 @@ function runActiveTimer() {
(async () => { (async () => {
let instanceLock = app.requestSingleInstanceLock(); let instanceLock = app.requestSingleInstanceLock();
if (!instanceLock) { 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(); app.quit();
return; return;
} }

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as mobx from "mobx"; import * as mobx from "mobx";
import { sprintf } from "sprintf-js"; import { sprintf } from "sprintf-js";
import { boundMethod } from "autobind-decorator"; import { boundMethod } from "autobind-decorator";
@ -12,6 +15,7 @@ import {
isModKeyPress, isModKeyPress,
} from "../util/util"; } from "../util/util";
import { TermWrap } from "../plugins/terminal/term"; import { TermWrap } from "../plugins/terminal/term";
import { PluginModel } from "../plugins/plugins";
import type { import type {
SessionDataType, SessionDataType,
LineType, LineType,
@ -72,6 +76,8 @@ import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat"; import localizedFormat from "dayjs/plugin/localizedFormat";
import customParseFormat from "dayjs/plugin/customParseFormat"; import customParseFormat from "dayjs/plugin/customParseFormat";
import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil"; import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil";
import { sortAndFilterRemotes } from "../util/util";
import { MagicLayout } from "../app/magiclayout";
dayjs.extend(customParseFormat); dayjs.extend(customParseFormat);
dayjs.extend(localizedFormat); dayjs.extend(localizedFormat);
@ -88,7 +94,7 @@ const MinFontSize = 8;
const MaxFontSize = 15; const MaxFontSize = 15;
const InputChunkSize = 500; const InputChunkSize = 500;
const RemoteColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"]; 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 // @ts-ignore
const VERSION = __PROMPT_VERSION__; const VERSION = __PROMPT_VERSION__;
@ -640,10 +646,11 @@ class Screen {
let height = termHeightFromRows(25, GlobalModel.termFontSize.get()); let height = termHeightFromRows(25, GlobalModel.termFontSize.get());
return { width, height }; return { width, height };
} }
// TODO calculate these sizes more deliberately
let winSize = this.lastScreenSize; let winSize = this.lastScreenSize;
let width = boundInt(winSize.width - 50, 100, 5000); let minSize = MagicLayout.ScreenMinContentSize;
let height = boundInt(winSize.height - 120, 100, 5000); 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 }; return { width, height };
} }
@ -1977,7 +1984,7 @@ class HistoryViewModel {
return; return;
} }
let prtn = GlobalModel.showAlert({ 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, confirm: true,
}); });
prtn.then((result) => { prtn.then((result) => {
@ -2417,13 +2424,45 @@ class BookmarksModel {
return; return;
} }
} }
return; }
class PluginsModel {
selectedPlugin: OV<RendererPluginType> = 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 { class RemotesModalModel {
openState: OV<boolean> = mobx.observable.box(false, { openState: OV<boolean> = mobx.observable.box(false, {
name: "RemotesModalModel-isOpen", name: "RemotesModalModel-isOpen",
}); });
onlyAddNewRemote: OV<boolean> = mobx.observable.box(false, {
name: "RemotesModalModel-onlyAddNewRemote",
});
selectedRemoteId: OV<string> = mobx.observable.box(null, { selectedRemoteId: OV<string> = mobx.observable.box(null, {
name: "RemotesModalModel-selectedRemoteId", name: "RemotesModalModel-selectedRemoteId",
}); });
@ -2465,8 +2504,9 @@ class RemotesModalModel {
})(); })();
} }
openModalForEdit(redit: RemoteEditType): void { openModalForEdit(redit: RemoteEditType, onlyAddNewRemote: boolean): void {
mobx.action(() => { mobx.action(() => {
this.onlyAddNewRemote.set(onlyAddNewRemote);
this.openState.set(true); this.openState.set(true);
this.selectedRemoteId.set(redit.remoteid); this.selectedRemoteId.set(redit.remoteid);
this.remoteEdit.set(redit); this.remoteEdit.set(redit);
@ -2495,6 +2535,11 @@ class RemotesModalModel {
cancelEditAuth(): void { cancelEditAuth(): void {
mobx.action(() => { mobx.action(() => {
this.remoteEdit.set(null); this.remoteEdit.set(null);
if (this.onlyAddNewRemote.get()) {
this.onlyAddNewRemote.set(false);
this.openState.set(false);
return;
}
if (this.selectedRemoteId.get() == null) { if (this.selectedRemoteId.get() == null) {
this.openModal(); this.openModal();
} }
@ -2517,6 +2562,7 @@ class RemotesModalModel {
this.openState.set(false); this.openState.set(false);
this.selectedRemoteId.set(null); this.selectedRemoteId.set(null);
this.remoteEdit.set(null); this.remoteEdit.set(null);
this.onlyAddNewRemote.set(false);
})(); })();
setTimeout(() => GlobalModel.refocus(), 10); setTimeout(() => GlobalModel.refocus(), 10);
} }
@ -2642,7 +2688,7 @@ class Model {
authKey: string; authKey: string;
isDev: boolean; isDev: boolean;
platform: string; 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", name: "activeMainView",
}); });
termFontSize: CV<number>; termFontSize: CV<number>;
@ -2668,6 +2714,7 @@ class Model {
remotesModalModel: RemotesModalModel; remotesModalModel: RemotesModalModel;
inputModel: InputModel; inputModel: InputModel;
pluginsModel: PluginsModel;
bookmarksModel: BookmarksModel; bookmarksModel: BookmarksModel;
historyViewModel: HistoryViewModel; historyViewModel: HistoryViewModel;
clientData: OV<ClientDataType> = mobx.observable.box(null, { clientData: OV<ClientDataType> = mobx.observable.box(null, {
@ -2686,6 +2733,7 @@ class Model {
); );
this.ws.reconnect(); this.ws.reconnect();
this.inputModel = new InputModel(); this.inputModel = new InputModel();
this.pluginsModel = new PluginsModel();
this.bookmarksModel = new BookmarksModel(); this.bookmarksModel = new BookmarksModel();
this.historyViewModel = new HistoryViewModel(); this.historyViewModel = new HistoryViewModel();
this.remotesModalModel = new RemotesModalModel(); this.remotesModalModel = new RemotesModalModel();
@ -3187,7 +3235,9 @@ class Model {
this.updateRemotes(update.remotes); this.updateRemotes(update.remotes);
} }
if ("mainview" in update) { 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); this.bookmarksModel.showBookmarksView(update.bookmarks, update.selectedbookmark);
} else if (update.mainview == "session") { } else if (update.mainview == "session") {
this.activeMainView.set("session"); this.activeMainView.set("session");
@ -3211,7 +3261,7 @@ class Model {
if (rview.remoteshowall) { if (rview.remoteshowall) {
this.remotesModalModel.openModal(); this.remotesModalModel.openModal();
} else if (rview.remoteedit != null) { } else if (rview.remoteedit != null) {
this.remotesModalModel.openModalForEdit(rview.remoteedit); this.remotesModalModel.openModalForEdit(rview.remoteedit, false);
} else if (rview.ptyremoteid) { } else if (rview.ptyremoteid) {
this.remotesModalModel.openModal(rview.ptyremoteid); this.remotesModalModel.openModal(rview.ptyremoteid);
} }
@ -3448,7 +3498,15 @@ class Model {
uicontext: this.getUIContext(), uicontext: this.getUIContext(),
interactive: interactive, 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); return this.submitCommandPacket(pk, interactive);
} }
@ -3462,7 +3520,7 @@ class Model {
interactive: interactive, interactive: interactive,
rawstr: cmdStr, rawstr: cmdStr,
}; };
if (!addToHistory) { if (!addToHistory && pk.kwargs) {
pk.kwargs["nohist"] = "1"; pk.kwargs["nohist"] = "1";
} }
return this.submitCommandPacket(pk, interactive); return this.submitCommandPacket(pk, interactive);
@ -3618,7 +3676,7 @@ class Model {
return remote.remotecanonicalname; return remote.remotecanonicalname;
} }
readRemoteFile(screenId: string, lineId: string, path: string): Promise<File> { readRemoteFile(screenId: string, lineId: string, path: string): Promise<T.ExtFile> {
let urlParams = { let urlParams = {
screenid: screenId, screenid: screenId,
lineid: lineId, lineid: lineId,
@ -3653,7 +3711,7 @@ class Model {
let isWriteable = (fileInfo.perm & 0o222) > 0; // checks for unix permission "w" bits let isWriteable = (fileInfo.perm & 0o222) > 0; // checks for unix permission "w" bits
(file as any).readOnly = !isWriteable; (file as any).readOnly = !isWriteable;
(file as any).notFound = !!fileInfo.notfound; (file as any).notFound = !!fileInfo.notfound;
return file; return file as T.ExtFile;
} else { } else {
let textError: string = blobOrText; let textError: string = blobOrText;
if (textError == null || textError.length == 0) { if (textError == null || textError.length == 0) {
@ -3817,16 +3875,24 @@ class CommandRunner {
GlobalModel.submitCommand("remote", "installcancel", null, { nohist: "1", remote: remoteid }, true); GlobalModel.submitCommand("remote", "installcancel", null, { nohist: "1", remote: remoteid }, true);
} }
createRemote(cname: string, kwargsArg: Record<string, string>) { createRemote(cname: string, kwargsArg: Record<string, string>, interactive: boolean): Promise<CommandRtnType> {
let kwargs = Object.assign({}, kwargsArg); let kwargs = Object.assign({}, kwargsArg);
kwargs["nohist"] = "1"; kwargs["nohist"] = "1";
GlobalModel.submitCommand("remote", "new", [cname], kwargs, true); return GlobalModel.submitCommand("remote", "new", [cname], kwargs, interactive);
} }
openCreateRemote(): void { openCreateRemote(): void {
GlobalModel.submitCommand("remote", "new", null, { nohist: "1", visual: "1" }, true); GlobalModel.submitCommand("remote", "new", null, { nohist: "1", visual: "1" }, true);
} }
screenSetRemote(remoteArg: string, nohist: boolean, interactive: boolean): Promise<CommandRtnType> {
let kwargs = {};
if (nohist) {
kwargs["nohist"] = "1";
}
return GlobalModel.submitCommand("connect", null, [remoteArg], kwargs, interactive);
}
editRemote(remoteid: string, kwargsArg: Record<string, string>): void { editRemote(remoteid: string, kwargsArg: Record<string, string>): void {
let kwargs = Object.assign({}, kwargsArg); let kwargs = Object.assign({}, kwargsArg);
kwargs["nohist"] = "1"; kwargs["nohist"] = "1";

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as mobx from "mobx"; import * as mobx from "mobx";
import { sprintf } from "sprintf-js"; import { sprintf } from "sprintf-js";
import { boundMethod } from "autobind-decorator"; import { boundMethod } from "autobind-decorator";

View File

@ -1,5 +1,8 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; 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 Editor from "@monaco-editor/react";
import { Markdown } from "../../app/common/common"; import { Markdown } from "../../app/common/common";
import { GlobalModel, GlobalCommandRunner } from "../../model/model"; import { GlobalModel, GlobalCommandRunner } from "../../model/model";
@ -18,20 +21,20 @@ declare var monaco: any;
class SourceCodeRenderer extends React.Component< class SourceCodeRenderer extends React.Component<
{ {
data: Blob; data: T.ExtBlob;
cmdstr: string; cmdstr: string;
cwd: string; cwd: string;
readOnly: boolean; readOnly: boolean;
notFound: boolean; notFound: boolean;
exitcode: number; exitcode: number;
context: RendererContext; context: T.RendererContext;
opts: RendererOpts; opts: T.RendererOpts;
savedHeight: number; savedHeight: number;
scrollToBringIntoViewport: () => void; scrollToBringIntoViewport: () => void;
lineState: LineStateType; lineState: T.LineStateType;
isSelected: boolean; isSelected: boolean;
shouldFocus: boolean; shouldFocus: boolean;
rendererApi: RendererModelContainerApi; rendererApi: T.RendererModelContainerApi;
}, },
{ {
code: string; code: string;

12
src/plugins/code/icon.svg Normal file
View File

@ -0,0 +1,12 @@
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="url(#paint0_linear_629_37176)"/>
<path d="M12.7568 13.803C13.0621 13.5231 13.0828 13.0487 12.8028 12.7434C12.5229 12.438 12.0485 12.4174 11.7432 12.6973L8.74319 15.4475C8.58822 15.5896 8.49999 15.7901 8.5 16.0004C8.50001 16.2106 8.58825 16.4112 8.74322 16.5532L11.7432 19.3031C12.0486 19.5829 12.523 19.5623 12.8029 19.2569C13.0828 18.9516 13.0621 18.4772 12.7568 18.1973L10.3599 16.0003L12.7568 13.803Z" fill="white"/>
<path d="M17.7301 11.9199C17.8249 11.5167 17.5749 11.113 17.1717 11.0181C16.7685 10.9233 16.3647 11.1733 16.2699 11.5765L14.2699 20.0808C14.1751 20.484 14.4251 20.8877 14.8283 20.9826C15.2315 21.0774 15.6353 20.8274 15.7301 20.4242L17.7301 11.9199Z" fill="white"/>
<path d="M20.2568 12.6973C19.9515 12.4174 19.4771 12.438 19.1972 12.7434C18.9172 13.0487 18.9379 13.5231 19.2432 13.803L21.6401 16.0003L19.2432 18.1973C18.9379 18.4772 18.9172 18.9516 19.1971 19.2569C19.477 19.5623 19.9514 19.5829 20.2568 19.3031L23.2568 16.5532C23.4118 16.4112 23.5 16.2106 23.5 16.0004C23.5 15.7901 23.4118 15.5896 23.2568 15.4475L20.2568 12.6973Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_629_37176" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#68D4EC"/>
<stop offset="1" stop-color="#005B70"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,5 @@
{
"title": "Code Viewer",
"vendor": "Wave",
"summary": "View and Edit source code with syntax highlightng and code completion."
}

View File

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -14,6 +17,7 @@ import type {
TermContextUnion, TermContextUnion,
RendererContainerType, RendererContainerType,
} from "../../types/types"; } from "../../types/types";
import * as T from "../../types/types";
import { debounce, throttle } from "throttle-debounce"; import { debounce, throttle } from "throttle-debounce";
import * as util from "../../util/util"; import * as util from "../../util/util";
import { GlobalModel } from "../../model/model"; import { GlobalModel } from "../../model/model";
@ -34,7 +38,7 @@ class SimpleBlobRendererModel {
lineState: LineStateType; lineState: LineStateType;
ptyData: PtyDataType; ptyData: PtyDataType;
ptyDataSource: (termContext: TermContextUnion) => Promise<PtyDataType>; ptyDataSource: (termContext: TermContextUnion) => Promise<PtyDataType>;
dataBlob: Blob; dataBlob: T.ExtBlob;
readOnly: boolean; readOnly: boolean;
notFound: boolean; notFound: boolean;
@ -143,7 +147,9 @@ class SimpleBlobRendererModel {
} }
rtnp.then((ptydata) => { rtnp.then((ptydata) => {
this.ptyData = 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(() => { mobx.action(() => {
this.loading.set(false); this.loading.set(false);
this.loadError.set(null); this.loadError.set(null);

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as mobx from "mobx"; import * as mobx from "mobx";
import { incObs } from "../../util/util"; import { incObs } from "../../util/util";

View File

@ -1,6 +1,8 @@
@import "../../app/common/themes/themes.less"; @import "../../app/common/themes/themes.less";
.csv-renderer { .csv-renderer {
opacity: 0; /* Start with an opacity of 0, meaning it's invisible */
.ellipsis() { .ellipsis() {
display: block; display: block;
white-space: nowrap; white-space: nowrap;
@ -19,8 +21,9 @@
user-select: none; user-select: none;
} }
.global-search-render { table.probe {
margin-bottom: 10px; position: absolute;
visibility: hidden;
} }
table { table {
@ -29,7 +32,7 @@
border: 1px solid @scrollbar-thumb; border: 1px solid @scrollbar-thumb;
thead { thead {
position:relative; position: relative;
display: block; display: block;
width: 100%; width: 100%;
overflow-y: scroll; overflow-y: scroll;
@ -38,18 +41,18 @@
border-bottom: 1px solid @scrollbar-thumb; border-bottom: 1px solid @scrollbar-thumb;
th { th {
color:@term-white; color: @term-white;
border: 1px solid @scrollbar-thumb; border-right: 1px solid @scrollbar-thumb;
border-bottom: none; border-bottom: none;
padding: 2px 10px 4px 10px; padding: 2px 10px;
flex-basis:100%; flex-basis: 100%;
flex-grow:2; flex-grow: 2;
display: block; display: block;
text-align:left; text-align: left;
position: relative; position: relative;
.inner { .inner {
text-align:left; text-align: left;
padding-right: 15px; padding-right: 15px;
position: relative; position: relative;
.ellipsis(); .ellipsis();
@ -68,25 +71,29 @@
tbody { tbody {
display: block; display: block;
position:relative; position: relative;
overflow-y: scroll; overflow-y: scroll;
overscroll-behavior: contain; overscroll-behavior: contain;
} }
tr { tr {
width: 100%; width: 100%;
display:flex; display: flex;
td { td {
border-right: 1px solid @scrollbar-thumb; border-right: 1px solid @scrollbar-thumb;
border-left: 1px solid @scrollbar-thumb; border-left: 1px solid @scrollbar-thumb;
padding: 2px 10px; padding: 3px 10px;
flex-basis:100%; flex-basis: 100%;
flex-grow:2; flex-grow: 2;
display: block; display: block;
text-align:left; text-align: left;
.ellipsis(); .ellipsis();
} }
} }
} }
} }
.csv-renderer.show {
opacity: 1; /* When loaded class is added, set the opacity to 1, making it visible */
}

View File

@ -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 React, { FC, useEffect, useState, useRef, useMemo } from "react";
import { RendererContext, RendererOpts, LineStateType, RendererModelContainerApi } from "../../types/types"; import { RendererContext, RendererOpts, LineStateType, RendererModelContainerApi } from "../../types/types";
import * as T from "../../types/types";
import { GlobalModel } from "../../model/model"; import { GlobalModel } from "../../model/model";
import Papa from 'papaparse'; import Papa from "papaparse";
import { import {
createColumnHelper, createColumnHelper,
flexRender, flexRender,
useReactTable, useReactTable,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel,
getSortedRowModel, getSortedRowModel,
FilterFn, FilterFn,
} from '@tanstack/react-table' } from "@tanstack/react-table";
import { import { useTableNav } from "@table-nav/react";
rankItem, import SortUpIcon from "./img/sort-up-solid.svg";
} from '@tanstack/match-sorter-utils' import SortDownIcon from "./img/sort-down-solid.svg";
import SortUpIcon from './img/sort-up-solid.svg'; import cn from "classnames";
import SortDownIcon from './img/sort-down-solid.svg';
import "./csv.less"; 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 = { type CSVRow = {
[key: string]: string | number; [key: string]: string | number;
}; };
const fuzzyFilter: FilterFn<any> = (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 { interface Props {
data: Blob; data: T.ExtBlob;
cmdstr: string;
cwd: string;
readOnly: boolean; readOnly: boolean;
notFound: boolean; context: T.RendererContext;
exitcode: number; opts: T.RendererOpts;
context: RendererContext;
opts: RendererOpts;
savedHeight: number; savedHeight: number;
scrollToBringIntoViewport: () => void; lineState: T.LineStateType;
lineState: LineStateType;
isSelected: boolean;
shouldFocus: boolean; shouldFocus: boolean;
rendererApi: RendererModelContainerApi; rendererApi: T.RendererModelContainerApi;
scrollToBringIntoViewport: () => void;
} }
interface State { interface State {
content: string | null; content: string | null;
message: { status: string; text: string } | null;
isPreviewerAvailable: boolean;
showReadonly: boolean; showReadonly: boolean;
totalHeight: number; tbodyHeight: number;
} }
const columnHelper = createColumnHelper<any>(); const columnHelper = createColumnHelper<any>();
const CSVRenderer: FC<Props> = (props: Props) => { const CSVRenderer: FC<Props> = (props: Props) => {
const { data, opts, lineState, context, shouldFocus, rendererApi, savedHeight } = props;
const { height: maxHeight } = opts.maxSize;
const csvCacheRef = useRef(new Map<string, string>()); const csvCacheRef = useRef(new Map<string, string>());
const rowRef = useRef<(HTMLTableRowElement | null)[]>([]); const rowRef = useRef<(HTMLTableRowElement | null)[]>([]);
const headerRef = useRef<HTMLTableRowElement | null>(null); const headerRef = useRef<HTMLTableRowElement | null>(null);
const probeRef = useRef<HTMLTableRowElement | null>(null);
const tbodyRef = useRef<HTMLTableSectionElement | null>(null);
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
content: null, content: null,
message: null,
isPreviewerAvailable: false,
showReadonly: true, showReadonly: true,
totalHeight: 0, tbodyHeight: 0,
}); });
const [globalFilter, setGlobalFilter] = useState('')
const [isFileTooLarge, setIsFileTooLarge] = useState<boolean>(false); const [isFileTooLarge, setIsFileTooLarge] = useState<boolean>(false);
const [tableLoaded, setTableLoaded] = useState(false);
const { listeners } = useTableNav();
const filePath = props.lineState["prompt:file"]; const filePath = lineState["prompt:file"];
const { screenId, lineId } = props.context; const { screenId, lineId } = context;
const cacheKey = `${screenId}-${lineId}-${filePath}`; const cacheKey = `${screenId}-${lineId}-${filePath}`;
// Parse the CSV data // Parse the CSV data
@ -89,7 +75,7 @@ const CSVRenderer: FC<Props> = (props: Props) => {
// Trim the content and then check for headers based on the first row's content. // Trim the content and then check for headers based on the first row's content.
const trimmedContent = state.content.trim(); 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 // This checks if the first row starts with a letter or a quote
const hasHeaders = !!firstRow.match(/^[a-zA-Z"]/); const hasHeaders = !!firstRow.match(/^[a-zA-Z"]/);
@ -98,9 +84,9 @@ const CSVRenderer: FC<Props> = (props: Props) => {
// Check for non-header CSVs // Check for non-header CSVs
if (!hasHeaders && Array.isArray(results.data) && Array.isArray(results.data[0])) { 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}`); 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 = {}; const newRow: CSVRow = {};
row.forEach((value, index) => { row.forEach((value, index) => {
newRow[headers[index]] = value; newRow[headers[index]] = value;
@ -109,10 +95,10 @@ const CSVRenderer: FC<Props> = (props: Props) => {
}); });
} }
return results.data.map(row => { return results.data.map((row) => {
return Object.fromEntries( return Object.fromEntries(
Object.entries(row as CSVRow).map(([key, value]) => { Object.entries(row as CSVRow).map(([key, value]) => {
if (typeof value === 'string') { if (typeof value === "string") {
const numberValue = parseFloat(value); const numberValue = parseFloat(value);
if (!isNaN(numberValue) && String(numberValue) === value) { if (!isNaN(numberValue) && String(numberValue) === value) {
return [key, numberValue]; return [key, numberValue];
@ -130,10 +116,10 @@ const CSVRenderer: FC<Props> = (props: Props) => {
return []; return [];
} }
const headers = Object.keys(parsedData[0]); const headers = Object.keys(parsedData[0]);
return headers.map(header => return headers.map((header) =>
columnHelper.accessor(header, { columnHelper.accessor(header, {
header: () => header, header: () => header,
cell: info => info.renderValue(), cell: (info) => info.renderValue(),
}) })
); );
}, [parsedData]); }, [parsedData]);
@ -144,61 +130,55 @@ const CSVRenderer: FC<Props> = (props: Props) => {
setState((prevState) => ({ ...prevState, content })); setState((prevState) => ({ ...prevState, content }));
} else { } else {
// Check if the file size exceeds 10MB // 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); setIsFileTooLarge(true);
return; return;
} }
props.data.text().then((content: string) => { data.text().then((content: string) => {
setState((prevState) => ({ ...prevState, content })); setState((prevState) => ({ ...prevState, content }));
csvCacheRef.current.set(cacheKey, content); csvCacheRef.current.set(cacheKey, content);
}); });
} }
}, []); }, []);
// Effect to compute height after rendering
useEffect(() => { useEffect(() => {
if (headerRef.current && rowRef.current && rowRef.current[0]) { if (probeRef.current && headerRef.current && parsedData.length) {
const rowHeight = rowRef.current[0]?.offsetHeight ?? 0; // Using optional chaining const rowHeight = probeRef.current.offsetHeight;
const totalHeight = rowHeight * parsedData.length; const fullTBodyHeight = rowHeight * parsedData.length;
const th = Math.min(totalHeight, props.opts.maxSize.height); 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]);
// Makes sure rows are rendered before setting the renderer as loaded
useEffect(() => {
let timer: any;
const getMessage = () => ( if (rowRef.current.length === parsedData.length) {
<div style={{ position: "absolute", bottom: "-3px", left: "14px" }}> timer = setTimeout(() => {
<div setTableLoaded(true);
className="message" }, 50); // Delay a bit to make sure the rows are rendered
style={{ }
fontSize: GlobalModel.termFontSize.get(),
background: `${state.message?.status === "error" ? "red" : "#4e9a06"}`,
}}
>
{state.message?.text}
</div>
</div>
);
const { exitcode } = props; return () => clearTimeout(timer);
const { content, message } = state; }, [rowRef, parsedData]);
useEffect(() => {
if (shouldFocus) {
rendererApi.onFocusChanged(true);
}
}, [shouldFocus]);
const table = useReactTable({ const table = useReactTable({
manualPagination: true, manualPagination: true,
data: parsedData, data: parsedData,
columns, columns,
filterFns: {
fuzzy: fuzzyFilter,
},
state: {
globalFilter,
},
globalFilterFn: fuzzyFilter,
onGlobalFilterChange: setGlobalFilter,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
}); });
@ -210,61 +190,74 @@ const CSVRenderer: FC<Props> = (props: Props) => {
); );
} }
if (content == null) return <div className="csv-renderer" style={{ height: props.savedHeight }} />;
return ( return (
<div className="csv-renderer"> <div
<table> className={cn("csv-renderer", { show: tableLoaded })}
style={{ height: tableLoaded ? "auto" : savedHeight }}
>
<table className="probe">
<tbody>
<tr ref={probeRef}>
<td>dummy data</td>
</tr>
</tbody>
</table>
<table {...listeners}>
<thead> <thead>
{table.getHeaderGroups().map(headerGroup => ( {table.getHeaderGroups().map((headerGroup, index) => (
<tr key={headerGroup.id} ref={headerRef}> <tr key={headerGroup.id} ref={headerRef} id={headerGroup.id} tabIndex={index}>
{headerGroup.headers.map(header => ( {headerGroup.headers.map((header, index) => (
<th <th
key={header.id} key={header.id}
colSpan={header.colSpan} colSpan={header.colSpan}
id={header.id}
tabIndex={index}
style={{ width: header.getSize() }} style={{ width: header.getSize() }}
> >
{header.isPlaceholder {header.isPlaceholder ? null : (
? null <div
: ( {...{
<div className: header.column.getCanSort()
{...{ ? "inner cursor-pointer select-none"
className: header.column.getCanSort() : "",
? 'inner cursor-pointer select-none' onClick: header.column.getToggleSortingHandler(),
: '', }}
onClick: header.column.getToggleSortingHandler(), >
}} {flexRender(header.column.columnDef.header, header.getContext())}
> {header.column.getIsSorted() === "asc" ? (
{flexRender( <img
header.column.columnDef.header, src={SortUpIcon}
header.getContext() className="sort-icon sort-up-icon"
)} alt="Ascending"
{ />
header.column.getIsSorted() === 'asc' ? <img src={SortUpIcon} className="sort-icon sort-up-icon" alt="Ascending" /> : ) : header.column.getIsSorted() === "desc" ? (
header.column.getIsSorted() === 'desc' ? <img src={SortDownIcon} className="sort-icon sort-down-icon" alt="Descending" /> : null <img
} src={SortDownIcon}
</div> className="sort-icon sort-down-icon"
)} alt="Descending"
/>
) : null}
</div>
)}
</th> </th>
))} ))}
</tr> </tr>
))} ))}
</thead> </thead>
<tbody style={{"height": `${state.totalHeight}px`}}> <tbody style={{ height: `${state.tbodyHeight}px` }} ref={tbodyRef}>
{table.getRowModel().rows.map((row, index) => ( {table.getRowModel().rows.map((row, index) => (
<tr key={row.id} ref={el => rowRef.current[index] = el}> <tr key={row.id} ref={(el) => (rowRef.current[index] = el)} id={row.id} tabIndex={index}>
{row.getVisibleCells().map(cell => ( {row.getVisibleCells().map((cell) => (
<td key={cell.id}> <td key={cell.id} id={cell.id} tabIndex={index}>
{flexRender(cell.column.columnDef.cell, cell.getContext())} {flexRender(cell.column.columnDef.cell, cell.getContext())}
</td> </td>
))} ))}
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
{message && getMessage()}
</div> </div>
); );
} };
export { CSVRenderer }; export { CSVRenderer };

10
src/plugins/csv/icon.svg Normal file
View File

@ -0,0 +1,10 @@
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="url(#paint0_linear_629_37175)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.281 10H13.72C13.046 10 12.495 10 12.048 10.037C11.585 10.074 11.166 10.155 10.776 10.354C10.1646 10.6656 9.66756 11.1626 9.356 11.774C9.156 12.165 9.076 12.584 9.038 13.047C9 13.494 9 14.045 9 14.719V17.28C9 17.954 9 18.504 9.037 18.952C9.074 19.415 9.155 19.834 9.354 20.225C9.66556 20.8364 10.1626 21.3334 10.774 21.645C11.165 21.844 11.584 21.925 12.047 21.962C12.494 21.999 13.045 21.999 13.719 21.999H18.281C18.955 21.999 19.505 21.999 19.953 21.962C20.416 21.925 20.835 21.844 21.226 21.645C21.8374 21.3334 22.3344 20.8364 22.646 20.225C22.845 19.834 22.926 19.415 22.963 18.952C23 18.505 23 17.954 23 17.28V14.72C23 14.046 23 13.495 22.963 13.048C22.926 12.585 22.845 12.166 22.646 11.776C22.3344 11.1646 21.8374 10.6676 21.226 10.356C20.835 10.156 20.416 10.076 19.953 10.038C19.506 10 18.955 10 18.281 10ZM11.456 11.69C11.6 11.617 11.799 11.562 12.169 11.532C12.549 11.501 13.038 11.5 13.75 11.5H18.25C18.962 11.5 19.452 11.5 19.83 11.532C20.201 11.562 20.4 11.617 20.544 11.691C20.874 11.859 21.142 12.126 21.309 12.456C21.383 12.6 21.439 12.799 21.469 13.169C21.477 13.271 21.483 13.38 21.487 13.5H10.513C10.517 13.38 10.523 13.27 10.532 13.17C10.562 12.799 10.617 12.6 10.691 12.456C10.8588 12.1266 11.1266 11.8578 11.456 11.69ZM10.5 15V17.25C10.5 17.962 10.5 18.452 10.532 18.83C10.562 19.201 10.617 19.4 10.691 19.544C10.859 19.874 11.126 20.142 11.456 20.309C11.6 20.383 11.799 20.439 12.169 20.469C12.398 20.487 12.666 20.495 13 20.498V15H10.5ZM14.5 20.5H17.5V15H14.5V20.5ZM19 20.498C19.334 20.495 19.602 20.488 19.83 20.468C20.201 20.438 20.4 20.383 20.544 20.309C20.8732 20.1415 21.141 19.874 21.309 19.545C21.383 19.4 21.439 19.201 21.469 18.831C21.499 18.451 21.5 17.962 21.5 17.25V15H19V20.498Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_629_37175" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#8B46D0"/>
<stop offset="1" stop-color="#42156F"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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 :)"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

View File

@ -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<React.InputHTMLAttributes<HTMLInputElement>, '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 <div className="search-renderer">
<input {...props} value={value} onChange={e => setValue(e.target.value)} />
</div>;
};
export default DebouncedInput

View File

@ -0,0 +1,11 @@
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="url(#paint0_linear_629_37177)"/>
<path d="M15.3174 8.99805H16.6826C17.6351 8.99804 18.3956 8.99804 19.0099 9.04822C19.6399 9.0997 20.1818 9.20768 20.6795 9.46127C21.4791 9.86873 22.1293 10.5189 22.5368 11.3186C22.7904 11.8163 22.8984 12.3582 22.9498 12.9882C23 13.6024 23 14.363 23 15.3154L23 16.6849C23 17.6374 23 18.3979 22.9499 19.0122C22.8984 19.6422 22.7904 20.1841 22.5368 20.6818C22.1293 21.4815 21.4792 22.1316 20.6795 22.5391C20.1818 22.7927 19.6399 22.9007 19.0099 22.9521C18.3956 23.0023 17.6351 23.0023 16.6827 23.0023H15.3174C14.3649 23.0023 13.6044 23.0023 12.9901 22.9521C12.3601 22.9007 11.8182 22.7927 11.3205 22.5391C10.5209 22.1316 9.87068 21.4815 9.46322 20.6818C9.20963 20.1841 9.10165 19.6422 9.05018 19.0122C8.99999 18.3979 8.99999 17.6374 9 16.6849V15.3154C8.99999 14.363 8.99999 13.6024 9.05018 12.9882C9.10165 12.3582 9.20963 11.8163 9.46322 11.3186C9.87068 10.5189 10.5209 9.86873 11.3205 9.46127C11.8182 9.20768 12.3601 9.0997 12.9901 9.04822C13.6044 8.99804 14.3649 8.99804 15.3174 8.99805ZM13.1123 10.5432C12.575 10.5871 12.2525 10.6699 12.0015 10.7978C11.4841 11.0614 11.0634 11.4821 10.7997 11.9996C10.6718 12.2506 10.5891 12.5731 10.5452 13.1103C10.5006 13.6563 10.5 14.3556 10.5 15.348V16.6523C10.5 17.2122 10.5002 17.6788 10.5084 18.0771L11.7825 16.8029C12.487 16.0984 13.6367 16.1233 14.31 16.8578L15.1117 17.7325L17.571 15.2732C18.4497 14.3945 19.8743 14.3945 20.753 15.2732L21.5 16.0202L21.5 15.348C21.5 14.3556 21.4994 13.6563 21.4548 13.1103C21.4109 12.5731 21.3282 12.2506 21.2003 11.9996C20.9366 11.4821 20.5159 11.0614 19.9985 10.7978C19.7475 10.6699 19.425 10.5871 18.8877 10.5432C18.3417 10.4986 17.6425 10.498 16.65 10.498H15.35C14.3575 10.498 13.6583 10.4986 13.1123 10.5432ZM21.4905 18.132L19.6923 16.3339C19.3994 16.041 18.9245 16.041 18.6316 16.3339L15.9878 18.9777C15.4845 19.481 14.6633 19.4631 14.1824 18.9385L13.2042 17.8714C13.1081 17.7665 12.9438 17.7629 12.8432 17.8636L10.7692 19.9376C10.7791 19.9592 10.7893 19.9802 10.7997 20.0008C11.0634 20.5182 11.4841 20.9389 12.0015 21.2026C12.2525 21.3305 12.575 21.4132 13.1123 21.4571C13.6583 21.5017 14.3575 21.5023 15.35 21.5023H16.65C17.6425 21.5023 18.3417 21.5017 18.8877 21.4571C19.425 21.4132 19.7475 21.3305 19.9985 21.2026C20.516 20.9389 20.9366 20.5182 21.2003 20.0008C21.3282 19.7498 21.4109 19.4273 21.4548 18.89C21.4731 18.6663 21.484 18.4168 21.4905 18.132Z" fill="white"/>
<path d="M14.5009 13.2574C14.5009 13.9491 13.9402 14.5098 13.2485 14.5098C12.5568 14.5098 11.9961 13.9491 11.9961 13.2574C11.9961 12.5658 12.5568 12.0051 13.2485 12.0051C13.9402 12.0051 14.5009 12.5658 14.5009 13.2574Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_629_37177" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9839F"/>
<stop offset="1" stop-color="#B50A33"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,33 +1,19 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobx from "mobx"; import * as mobx from "mobx";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import { RendererContext, RendererOpts } from "../../types/types"; import * as T from "../../types/types";
import "./image.less"; import "./image.less";
type OV<V> = mobx.IObservableValue<V>; type OV<V> = mobx.IObservableValue<V>;
type CV<V> = mobx.IComputedValue<V>; type CV<V> = mobx.IComputedValue<V>;
// 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 @mobxReact.observer
class SimpleImageRenderer extends React.Component< 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; objUrl: string = null;

View File

@ -0,0 +1,5 @@
{
"title": "ImageViewer",
"vendor": "Wave",
"summary": "View Images inline in the terminal."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

View File

@ -0,0 +1,11 @@
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="url(#paint0_linear_629_37174)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2186 9.49805H18.7814C19.4548 9.49804 20.0055 9.49803 20.4531 9.5346C20.9163 9.57245 21.3347 9.65318 21.7255 9.85227C22.337 10.1639 22.8342 10.661 23.1458 11.2726C23.3449 11.6633 23.4256 12.0817 23.4634 12.545C23.5 12.9925 23.5 13.5432 23.5 14.2166L23.5 17.7837C23.5 18.4571 23.5 19.0078 23.4635 19.4554C23.4256 19.9186 23.3449 20.337 23.1458 20.7278C22.8342 21.3393 22.337 21.8365 21.7255 22.1481C21.3348 22.3472 20.9164 22.4279 20.4531 22.4658C20.0055 22.5023 19.4548 22.5023 18.7815 22.5023H13.2186C12.5452 22.5023 11.9945 22.5023 11.5469 22.4658C11.0837 22.4279 10.6653 22.3472 10.2745 22.1481C9.663 21.8365 9.16582 21.3393 8.85423 20.7278C8.65514 20.337 8.5744 19.9187 8.53655 19.4554C8.49998 19.0078 8.49999 18.4571 8.5 17.7837V14.2166C8.49999 13.5433 8.49998 12.9925 8.53655 12.545C8.5744 12.0817 8.65514 11.6633 8.85423 11.2726C9.16582 10.6611 9.663 10.1639 10.2745 9.85228C10.6653 9.65318 11.0837 9.57245 11.5469 9.5346C11.9945 9.49803 12.5452 9.49804 13.2186 9.49805ZM11.6691 11.0296C11.2986 11.0599 11.0996 11.1154 10.9555 11.1888C10.6262 11.3566 10.3585 11.6243 10.1907 11.9536C10.1173 12.0976 10.0618 12.2966 10.0316 12.6671C10.0006 13.0464 10 13.5356 10 14.248V17.7523C10 18.4648 10.0006 18.954 10.0316 19.3332C10.0618 19.7037 10.1173 19.9028 10.1907 20.0468C10.3585 20.3761 10.6262 20.6438 10.9555 20.8116C11.0996 20.885 11.2986 20.9405 11.6691 20.9708C12.0483 21.0017 12.5376 21.0023 13.25 21.0023H18.75C19.4625 21.0023 19.9517 21.0017 20.331 20.9708C20.7015 20.9405 20.9005 20.885 21.0445 20.8116C21.3738 20.6438 21.6415 20.3761 21.8093 20.0468C21.8827 19.9028 21.9382 19.7037 21.9685 19.3332C21.9994 18.954 22 18.4647 22 17.7523L22 14.248C22 13.5356 21.9994 13.0464 21.9684 12.6671C21.9382 12.2966 21.8827 12.0976 21.8093 11.9536C21.6415 11.6243 21.3738 11.3566 21.0445 11.1888C20.9004 11.1154 20.7014 11.0599 20.3309 11.0296C19.9517 10.9986 19.4624 10.998 18.75 10.998H13.25C12.5376 10.998 12.0483 10.9986 11.6691 11.0296Z" fill="white"/>
<path d="M12.2381 12.7496H14.4497L15.971 16.457H16.0477L17.569 12.7496H19.7807V19.2951H18.042V15.511H17.9909L16.5335 19.244H15.4852L14.0278 15.4854H13.9767V19.2951H12.2381V12.7496Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_629_37174" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#5460CF"/>
<stop offset="1" stop-color="#0011B0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,7 +1,10 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobx from "mobx"; import * as mobx from "mobx";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import { RendererContext, RendererOpts } from "../../types/types"; import * as T from "../../types/types";
import { sprintf } from "sprintf-js"; import { sprintf } from "sprintf-js";
import { Markdown } from "../../app/common/common"; import { Markdown } from "../../app/common/common";
@ -13,7 +16,7 @@ const MaxMarkdownSize = 200000;
@mobxReact.observer @mobxReact.observer
class SimpleMarkdownRenderer extends React.Component< 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<string> = mobx.observable.box(null, { name: "markdownText" }); markdownText: OV<string> = mobx.observable.box(null, { name: "markdownText" });

View File

@ -0,0 +1,5 @@
{
"title": "Markdown",
"vendor": "Wave",
"summary": "Markdown is a lightweight markup language for creating formatted text using a plain-text editor."
}

View File

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<rect width="32" height="32" rx="8" fill="url(#paint0_linear_629_37176)"/>
<style>svg{fill:#ffffff}</style>
<g transform="scale(0.03) translate(220,300)">
<path d="M233.4 112c-32.9 0-64.4 13.1-87.7 36.3c-28.5 28.5-74.2 29.8-104.3 3.1l-1.5-1.3c-2.7-2.4-5.8-4.1-9-5c-1.3-.4-2.7-.7-4.1-.9c-1.1-.1-2.2-.2-3.3-.2c-3.6 .1-7 .9-10.1 2.4c-2.2 1-4.2 2.4-6 4.2C2.9 154.9 0 161.1 0 168v34C0 311.4 88.6 400 198 400c42.8 0 84.5-13.9 118.8-39.6L320 358l3.2 2.4c34.3 25.7 76 39.6 118.8 39.6c109.4 0 198-88.6 198-198V168c0-6.9-2.9-13.1-7.5-17.4c-4.2-4-9.8-6.5-16.1-6.6c-.4 0-.9 0-1.3 0c-2.4 .1-4.8 .5-7.1 1.3c-2.9 1-5.6 2.6-8 4.7l-1.5 1.3c-30.1 26.7-75.8 25.4-104.3-3.1C471 125.1 439.5 112 406.6 112h-1.2c-29.7 0-58.5 10.4-81.3 29.5l-4 3.3-4-3.3c-22.8-19-51.6-29.5-81.3-29.5h-1.2zm358.3 98.7C587.3 289.5 521.9 352 442 352c-32.5 0-64-10.5-90-30l-17.6-13.2c-8.5-6.4-20.3-6.4-28.8 0L288 322c-26 19.5-57.5 30-90 30c-79.9 0-145.3-62.5-149.8-141.3c44.3 16.7 96 7 131.4-28.4C193.9 168 213.3 160 233.4 160h1.2c18.5 0 36.4 6.5 50.6 18.3l19.3 16.1c8.9 7.4 21.8 7.4 30.7 0l19.3-16.1c14.2-11.8 32.1-18.3 50.6-18.3h1.2c20.2 0 39.5 8 53.8 22.3c35.4 35.4 87.1 45.1 131.4 28.4z"/>
</g>
<linearGradient id="paint0_linear_629_37176" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#ec9d68"/>
<stop offset="1" stop-color="#ff3002"/>
</linearGradient>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,5 @@
{
"title": "Mustache",
"vendor": "Wave",
"summary": "Mustache templates help create html files."
}

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobx from "mobx"; import * as mobx from "mobx";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
@ -14,7 +17,7 @@ type OV<V> = mobx.IObservableValue<V>;
@mobxReact.observer @mobxReact.observer
class SimpleMustacheRenderer extends React.Component< 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<boolean> = mobx.observable.box(true, { name: "templateLoading" }); templateLoading: OV<boolean> = mobx.observable.box(true, { name: "templateLoading" });

View File

@ -0,0 +1,12 @@
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="url(#paint0_linear_629_37176)"/>
<path d="M12.7568 13.803C13.0621 13.5231 13.0828 13.0487 12.8028 12.7434C12.5229 12.438 12.0485 12.4174 11.7432 12.6973L8.74319 15.4475C8.58822 15.5896 8.49999 15.7901 8.5 16.0004C8.50001 16.2106 8.58825 16.4112 8.74322 16.5532L11.7432 19.3031C12.0486 19.5829 12.523 19.5623 12.8029 19.2569C13.0828 18.9516 13.0621 18.4772 12.7568 18.1973L10.3599 16.0003L12.7568 13.803Z" fill="white"/>
<path d="M17.7301 11.9199C17.8249 11.5167 17.5749 11.113 17.1717 11.0181C16.7685 10.9233 16.3647 11.1733 16.2699 11.5765L14.2699 20.0808C14.1751 20.484 14.4251 20.8877 14.8283 20.9826C15.2315 21.0774 15.6353 20.8274 15.7301 20.4242L17.7301 11.9199Z" fill="white"/>
<path d="M20.2568 12.6973C19.9515 12.4174 19.4771 12.438 19.1972 12.7434C18.9172 13.0487 18.9379 13.5231 19.2432 13.803L21.6401 16.0003L19.2432 18.1973C18.9379 18.4772 18.9172 18.9516 19.1971 19.2569C19.477 19.5623 19.9514 19.5829 20.2568 19.3031L23.2568 16.5532C23.4118 16.4112 23.5 16.2106 23.5 16.0004C23.5 15.7901 23.4118 15.5896 23.2568 15.4475L20.2568 12.6973Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_629_37176" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#68D4EC"/>
<stop offset="1" stop-color="#005B70"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,5 @@
{
"title": "OpenAI",
"vendor": "Wave",
"summary": "OpenAI plugin allows chatting with OpenAI APIs."
}

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobx from "mobx"; import * as mobx from "mobx";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { RendererPluginType } from "../types/types"; import { RendererPluginType } from "../types/types";
import { SimpleImageRenderer } from "./image/image"; import { SimpleImageRenderer } from "./image/image";
import { SimpleMarkdownRenderer } from "./markdown/markdown"; import { SimpleMarkdownRenderer } from "./markdown/markdown";
@ -8,89 +11,156 @@ import { OpenAIRenderer, OpenAIRendererModel } from "./openai/openai";
import { isBlank } from "../util/util"; import { isBlank } from "../util/util";
import { sprintf } from "sprintf-js"; import { sprintf } from "sprintf-js";
const ImagePlugin: RendererPluginType = { // TODO: @mike - I did refactoring with the though that I can move config out of this plugins.ts file to a
name: "image", // plugins.json file. This way, adding a new plugin would reuire adding an entry to the json config. At a later
rendererType: "simple", // stage, a plugin can become a self-contained-bundle, which would have my_plugin.json into it. it will be easy to
heightType: "pixels", // merge this my_plugin.json into the big plugins.json. I got stuck while defining 'simpleComponent: SimpleImageRenderer'
dataType: "blob", // in a json definition (something like Java.Reflection can be used to compose a class from its name. will try later)
collapseType: "hide", const PluginConfigs: RendererPluginType[] = [
globalCss: null, {
mimeTypes: ["image/*"], name: "markdown",
simpleComponent: SimpleImageRenderer, rendererType: "simple",
}; heightType: "pixels",
dataType: "blob",
const MarkdownPlugin: RendererPluginType = { collapseType: "hide",
name: "markdown", globalCss: null,
rendererType: "simple", mimeTypes: ["text/markdown"],
heightType: "pixels", simpleComponent: SimpleMarkdownRenderer,
dataType: "blob", },
collapseType: "hide", {
globalCss: null, name: "mustache",
mimeTypes: ["text/markdown"], rendererType: "simple",
simpleComponent: SimpleMarkdownRenderer, heightType: "pixels",
}; dataType: "blob",
collapseType: "hide",
const MustachePlugin: RendererPluginType = { globalCss: null,
name: "mustache", mimeTypes: ["text/plain"],
rendererType: "simple", simpleComponent: SimpleMustacheRenderer,
heightType: "pixels", },
dataType: "blob", {
collapseType: "hide", name: "code",
globalCss: null, rendererType: "simple",
mimeTypes: ["text/plain"], heightType: "pixels",
simpleComponent: SimpleMustacheRenderer, dataType: "blob",
}; collapseType: "hide",
globalCss: null,
const CodePlugin: RendererPluginType = { mimeTypes: ["text/plain"],
name: "code", simpleComponent: SourceCodeRenderer,
rendererType: "simple", },
heightType: "pixels", {
dataType: "blob", name: "openai",
collapseType: "hide", rendererType: "full",
globalCss: null, heightType: "pixels",
mimeTypes: ["text/plain"], dataType: "model",
simpleComponent: SourceCodeRenderer, collapseType: "remove",
}; hidePrompt: true,
globalCss: null,
const OpenAIPlugin: RendererPluginType = { mimeTypes: ["application/json"],
name: "openai", fullComponent: OpenAIRenderer,
rendererType: "full", modelCtor: () => new OpenAIRendererModel(),
heightType: "pixels", },
dataType: "model", {
collapseType: "remove", name: "csv",
hidePrompt: true, rendererType: "simple",
globalCss: null, heightType: "pixels",
mimeTypes: ["application/json"], dataType: "blob",
fullComponent: OpenAIRenderer, collapseType: "hide",
modelCtor: () => new OpenAIRendererModel(), globalCss: null,
}; mimeTypes: ["text/csv"],
simpleComponent: CSVRenderer,
const CSVPlugin: RendererPluginType = { },
name: "csv", {
rendererType: "simple", name: "image",
heightType: "pixels", rendererType: "simple",
dataType: "blob", heightType: "pixels",
collapseType: "hide", dataType: "blob",
globalCss: null, collapseType: "hide",
mimeTypes: ["text/csv"], globalCss: null,
simpleComponent: CSVRenderer, mimeTypes: ["image/*"],
}; simpleComponent: SimpleImageRenderer,
},
];
class PluginModelClass { class PluginModelClass {
rendererPlugins: RendererPluginType[] = []; rendererPlugins: RendererPluginType[] = [];
registerRendererPlugin(plugin: RendererPluginType) { constructor(pluginConfigs: RendererPluginType[]) {
if (isBlank(plugin.name)) { this.rendererPlugins = pluginConfigs.map((plugin: RendererPluginType): RendererPluginType => {
throw new Error("invalid plugin, no name"); 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") { plugin.screenshots = imagePaths.map((path) => path.default);
throw new Error(sprintf("invalid plugin, name '%s' is reserved", plugin.name)); }
}
let existingPlugin = this.getRendererPluginByName(plugin.name); // use dynamic import to attach the icon etc. ensure that the 'name' matches the dir the plugin is in
if (existingPlugin != null) { async loadPluginResources(plugin) {
throw new Error(sprintf("plugin with name %s already registered", plugin.name)); this.attachScreenshots(plugin);
} // attach other resources
this.rendererPlugins.push(plugin); 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 { getRendererPluginByName(name: string): RendererPluginType {
@ -102,17 +172,15 @@ class PluginModelClass {
} }
return null; return null;
} }
allPlugins() {
return this.rendererPlugins;
}
} }
let PluginModel: PluginModelClass = null; let PluginModel: PluginModelClass = null;
if ((window as any).PluginModel == null) { if ((window as any).PluginModel == null) {
PluginModel = new PluginModelClass(); PluginModel = new PluginModelClass(PluginConfigs);
PluginModel.registerRendererPlugin(ImagePlugin);
PluginModel.registerRendererPlugin(MarkdownPlugin);
PluginModel.registerRendererPlugin(CodePlugin);
PluginModel.registerRendererPlugin(OpenAIPlugin);
PluginModel.registerRendererPlugin(MustachePlugin);
PluginModel.registerRendererPlugin(CSVPlugin);
(window as any).PluginModel = PluginModel; (window as any).PluginModel = PluginModel;
} }

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as mobx from "mobx"; import * as mobx from "mobx";
import { Terminal } from "xterm"; import { Terminal } from "xterm";
import { sprintf } from "sprintf-js"; import { sprintf } from "sprintf-js";
@ -185,6 +188,10 @@ class TermWrap {
let termNumLines = termBuf.lines.length; let termNumLines = termBuf.lines.length;
let termYPos = termBuf.y; let termYPos = termBuf.y;
if (termNumLines > term.rows) { 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; return term.rows;
} }
let usedRows = this.isRunning ? 1 : 0; let usedRows = this.isRunning ? 1 : 0;

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobxReact from "mobx-react"; import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -150,8 +153,7 @@ class TerminalRenderer extends React.Component<
.get(); .get();
let cmd = screen.getCmd(line); // will not be null let cmd = screen.getCmd(line); // will not be null
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); 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());
let termHeight = termHeightFromRows(usedRows, GlobalModel.termFontSize.get()) + 2;
if (usedRows === 0) { if (usedRows === 0) {
termHeight = 0; termHeight = 0;
} }

6
src/types/custom.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare module '*.svg' {
import * as React from 'react';
export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement> & { title?: string }>;
const src: string;
export default src;
}

3
src/types/svg.d.ts vendored
View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
declare module "*.svg" { declare module "*.svg" {
import React from "react"; import React from "react";
export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement> & { title?: string }>; export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement> & { title?: string }>;

View File

@ -1,3 +1,6 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react"; import * as React from "react";
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -378,6 +381,12 @@ type RendererPluginType = {
modelCtor?: () => RendererModel; modelCtor?: () => RendererModel;
simpleComponent?: SimpleBlobRendererComponent; simpleComponent?: SimpleBlobRendererComponent;
fullComponent?: FullRendererComponent; fullComponent?: FullRendererComponent;
readme?: string;
screenshots?: any[];
vendor?: string;
summary?: string;
title?: string;
iconComp?: React.Component<{}, {}>;
}; };
type RendererModelContainerApi = { type RendererModelContainerApi = {
@ -409,7 +418,7 @@ type RendererModel = {
}; };
type SimpleBlobRendererComponent = React.ComponentType<{ type SimpleBlobRendererComponent = React.ComponentType<{
data: Blob; data: ExtBlob;
readOnly?: boolean; readOnly?: boolean;
notFound?: boolean; notFound?: boolean;
isSelected?: boolean; isSelected?: boolean;
@ -621,6 +630,14 @@ type FileInfoType = {
notfound: boolean; notfound: boolean;
}; };
type ExtBlob = Blob & {
notFound: boolean;
};
type ExtFile = File & {
notFound: boolean;
};
export type { export type {
SessionDataType, SessionDataType,
LineStateType, LineStateType,
@ -690,4 +707,6 @@ export type {
CommandRtnType, CommandRtnType,
OpenAIPacketType, OpenAIPacketType,
FileInfoType, FileInfoType,
ExtBlob,
ExtFile,
}; };

View File

@ -1,4 +1,8 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { boundInt } from "./util"; import { boundInt } from "./util";
import { MagicLayout } from "../app/magiclayout";
const MinTermCols = 10; const MinTermCols = 10;
const MaxTermCols = 1024; const MaxTermCols = 1024;
@ -59,14 +63,14 @@ function measureText(
function windowWidthToCols(width: number, fontSize: number): number { function windowWidthToCols(width: number, fontSize: number): number {
let dr = getMonoFontSize(fontSize); 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); cols = boundInt(cols, MinTermCols, MaxTermCols);
return cols; return cols;
} }
function windowHeightToRows(height: number, fontSize: number): number { function windowHeightToRows(height: number, fontSize: number): number {
let dr = getMonoFontSize(fontSize); 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) { if (rows <= 0) {
rows = 1; rows = 1;
} }
@ -75,12 +79,13 @@ function windowHeightToRows(height: number, fontSize: number): number {
function termWidthFromCols(cols: number, fontSize: number): number { function termWidthFromCols(cols: number, fontSize: number): number {
let dr = getMonoFontSize(fontSize); 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 { function termHeightFromRows(rows: number, fontSize: number): number {
let dr = getMonoFontSize(fontSize); 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 }; export { measureText, getMonoFontSize, windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows };

View File

@ -1,8 +1,13 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as mobx from "mobx"; import * as mobx from "mobx";
import { sprintf } from "sprintf-js"; import { sprintf } from "sprintf-js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat"; import localizedFormat from "dayjs/plugin/localizedFormat";
import type { RemoteType } from "../types/types"; import type { RemoteType, CommandRtnType } from "../types/types";
type OV<V> = mobx.IObservableValue<V>;
dayjs.extend(localizedFormat); 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]}%)`; 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<CommandRtnType>, errorMessage: OV<string>) {
prtn.then((crtn) => {
if (crtn.success) {
return;
}
mobx.action(() => {
errorMessage.set(crtn.error);
})();
});
}
export { export {
handleJsonFetchResponse, handleJsonFetchResponse,
base64ToArray, base64ToArray,
@ -421,4 +437,5 @@ export {
openLink, openLink,
generateBackgroundWithGradient, generateBackgroundWithGradient,
getColorRGB, getColorRGB,
commandRtnHandler,
}; };

View File

@ -1,2 +1,2 @@
const VERSION = "v0.4.0"; const VERSION = "v0.5.0";
module.exports = VERSION; module.exports = VERSION;

Some files were not shown because too many files have changed in this diff Show More