Compare commits

..

48 Commits

Author SHA1 Message Date
sawka 19e856a3be bump version to 0.5.3 2023-12-21 12:31:54 -08:00
sawka 8246f8118e bump to v0.5.3 to fix #174 2023-12-21 12:30:57 -08:00
Red J Adaya 75d1a435ae fix missing session ID (#178) 2023-12-19 16:18:44 -08:00
Mike Sawka 426f9546e5 fix connection edit modal (#170)
* fix connection edit modal

* apply prettier
2023-12-18 23:28:49 -08:00
sawka d410e5d07f add keys to force new components whenever workspaces changes 2023-12-18 21:47:01 -08:00
sawka e66f34c9b5 upgrade go crypto library 2023-12-18 20:45:00 -08:00
sawka 58e971276e minor fixes 2023-12-18 20:41:15 -08:00
Cole Lashley 2d08037782 Fixed open ai renderer not scrolling correctly (#165)
* added scrolling to open ai renderer css

* fixed max width

* small fixes - added max width - added padding - fixed flex issue
2023-12-18 17:20:13 -08:00
Evan Simkowitz 226699732d Run release check in the background when toggling the setting (#169)
* Run release check in the background when toggling the setting

* change var name

* use new err variable
2023-12-18 17:19:11 -08:00
Evan Simkowitz 03b1e410e7 Small tweak to the checkbox toggle slider (#168) 2023-12-18 16:41:35 -08:00
Mike Sawka 38c784bf8e sidebar render delay of 0.5s, and a 0.5s transition on main window width (#167)
* sidebar render delay of 0.5s, and a 0.5s transition on main window width

* fix width calculation and how we apply the buffer width for sidebar

* also add the model.ts change
2023-12-18 16:40:26 -08:00
Sylvie Crowe 40af311e5b remove handleKeyDown for NumberField (#166)
handleKeyDown was a method that filtered out specific keypresses if they
weren't specific keycodes. This did not play well with all keyboards
which made it impossible to enter port numbers. furthermore,
handleInputChange should already filter out non-numeric characters, so
the filtering still works properly with this removal.
2023-12-18 16:03:51 -08:00
Evan Simkowitz 112fda912f Modify "Check for Updates" setting name (#164) 2023-12-18 15:28:35 -08:00
sawka f2eb383cc3 add special case for conda activate/deactivate as well to rtnstate detection 2023-12-18 15:07:13 -08:00
sawka 8d71f5538c update rtnstate detection, support blocks, and binary expressions 2023-12-18 15:02:55 -08:00
sawka 7a37fdb942 create new contexts for goroutine calls 2023-12-18 14:41:53 -08:00
sawka 80f7162691 dont scroll main view when selecting line in sidebar 2023-12-18 14:11:04 -08:00
Mike Sawka a639d72e30 Packetparser Ignore Spurious Invalid Input at Beginning of Stream (#140)
* take a stab at fixing #99. ignore invalid output before we see a real packet.  the complication here was ensuring we always output a real packet in every flow so we don't actually lose valid errors.

* add ping packets to prime the parser (when in ignoreUntilValid mode)
2023-12-18 12:42:40 -08:00
sawka ce252d479b fix id issue -- must use a unique id for tabs so we don't get collisions on real elements in the CSS 2023-12-18 00:16:57 -08:00
sawka d8d19ea035 bump to version v0.5.2 2023-12-17 23:58:09 -08:00
Mike Sawka 21ab82e2e2 Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout

* fix more golang warnings

* sidebar open/close

* add ability to set width of split

* sidebar add and remove, set width, etc.

* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there

* add isSidebarOpen() method

* fix resize jump -- must set width in error handler as well (before window is loaded)

* sidebar UI touchups and help

* more sidebar progress, render more like regular lines, just in the right column

* merge

* move migration to 26

* simplify sidebar types

* checkpoint

* proxy things through parent screen object for sidebar

* checkpoint, add/remove from sidebar

* work on add/remove icons for sidebar

* fix height calculation, remove close button

* bring back close button when no line is selected

* add sidebar flag to run command to run new command output in sidebar

* implement 'sidebar' kwarg in eval.  this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)

* prettier

* minor fixes

* working on resizing.  must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)

* fix sidebar terminal command resizing

* add sidebar header (toggles for half/partial width and close).  add hotkey to open/close sidebar (Cmd-Ctrl-S).  more robust calculation for sidebar width. add width validation.  minimum sidebar width is 200px.  other fixes, etc.
2023-12-17 23:46:53 -08:00
sawka 781ebe8154 add checking for latestversion (avoid potential error in semver.compareLoose) 2023-12-16 11:36:38 -08:00
sawka ad225ed28a fix some ts errors in emain.ts 2023-12-16 10:59:30 -08:00
Mike Sawka 633cb8fbd0 fix openai packets to be PacketType. better compatibility with backend (#154)
also pass clientid to openapi cloud service
2023-12-15 22:43:59 -08:00
Cole Lashley 4ccd62f12a Add support for proxying open AI chat completion through cloud (#148)
* wrote client code for communicating with lambda cloud

* Added timeout functionality, added check for telemetry enabled for clouod completion, added capability to unset token, other small fixes

* removed stale prints and comments, readded non stream completion for now

* changed json encode to json marshal, also testing my new commit author

* added no telemetry error message and removed check for model in cloud completion

* added defer conn.close() to doOpenAIStreamCompletion, so websocket is always closed

* made a constant for the long telemetry error message

* added endpoint getter, made errors better

* updated scripthaus file to include dev ws endpoint

* added error check for open ai errors

* changed bool condition for better readability

* update some error messages (use error message from server if returned)

* dont blow up the whole response if the server times out.  just write a timeout message

* render streaming errors with a new prompt in openai.tsx (show content and error).  render cmd status 'error' with red x as well.  show exitcode in tooltip of 'x'

* set hadError for errors.  update timeout error to work with new frontend code

* bump client timeout to 5 minutes (longer than server timeout)

---------

Co-authored-by: sawka
2023-12-15 22:20:03 -08:00
Evan Simkowitz b733724c7d Add sidebar banner when new release is available (#147)
* Server impl

* add update check setting

* add commands

* fix capitalization of commands

* apply suggestions

* add migration and fix backend bugs

* Add sidebar banner

* remove installedversion, add 5s timeout

* add icon, capture and log errors from release check

* missing return nil

* remove highlight

* remove commented less

* do not fail releasecheckoncommand if release check operation fails

* remove debug condition

* fix update on auto check, move banner display logic into frontend

* remove unnecessary import

* simplify null check

* clean up the invoking of the releasechecker
2023-12-15 17:43:54 -08:00
Red J Adaya 4ff5dcf1e0 fix mouse scroll (#151) 2023-12-15 16:15:45 -08:00
Red J Adaya f9d0e63d0c delete screen immediately when there are no lines in it (#152) 2023-12-15 15:40:34 -08:00
Red J Adaya ea9cb37de1 drag and drop support for tabs (#146)
* back-end implementation. initial ui implementation

* rough implementation of drag and drop

* wrap with AnimatePresence

* persist screen order

* cleanup

* return all screens when updating indices

* remove y axis anitation

* remove debugging code

* chain filtering and sorting logic

* remove unused var

* fix issue where tabs shift to left on hover

* fix tabElem scroll into view regression

* clear scrollIntoViewTimeout when component unmounts

* remove borken style prop

* completely remove animation related props

* scroll into view only when there's a new tab

* minor comment fix

* resolvePosInt() to resolve index

* move tab width const to magiclayout.ts

* move back scroll into view code as it was before

* clear timout

* refactor setTimeout

* remov debugging codes

* format cmdrunner.go

* move clearTimeout

* remove wheel event listener
2023-12-14 21:50:47 -08:00
Mike Sawka 8200a312b9 On MacOS allow UserShell to override /bin/bash (#137)
* switch emain to using the bash from PATH, not defaulting to /bin/bash.  not convinced this fixes #132 yet because the PATH set for MacOS applications is not set from .bash_profile or .zprofile.  those seem to be set with launchctl setenv.

* move MacUserShell() func to shexec.  use dscl UserShell output as bash path on MacOS when present.  fixes #132
2023-12-14 21:45:27 -08:00
Evan Simkowitz 3b65e9941a Clean up potentially unsafe external links (#139)
* Clean up potentially unsafe external links

* fix broken imports

* fix error in conditional

* fix one more typo
2023-12-13 17:43:30 -08:00
Cole Lashley 535fd0d7d9 added openaibaseurl parameter to client:set (#141) 2023-12-13 14:03:22 -08:00
Evan Simkowitz 96980b2f83 Merge pull request #138 from wavetermdev/evan/codescan-fixes
Fix potential overflow in promptenc arithmetic
2023-12-12 21:01:09 -08:00
sawka b6c880c6ff add 'cmd' parameter to /api/run-command for debugging purposes (easier filtering and visual inspection of run-command calls) 2023-12-12 20:37:00 -08:00
Evan Simkowitz 1feffc8a11 address comment, clean up tests 2023-12-12 20:36:16 -08:00
sawka f442236679 fix performance issue where cmdinput was causing additional loading of screenlines 2023-12-12 20:36:11 -08:00
Evan Simkowitz 414937911e remove nolint 2023-12-12 17:40:05 -08:00
Evan Simkowitz 5d39815dea refactor a bit 2023-12-12 17:39:19 -08:00
Evan Simkowitz 96f636c2da Fix potential overflow in promptenc arithmetic 2023-12-12 16:51:19 -08:00
Evan Simkowitz 112d002c2a Merge pull request #136 from wavetermdev/esimkowitz/codeql
Configuring CodeQL scanning workflow
2023-12-12 15:53:24 -08:00
Mike Sawka 4adcf6d92e pass flexrows through to packet.TermOpts so it gets back to the FE correctly. this fixes wterm overrides and github issue #116 (#134) 2023-12-12 14:24:09 -08:00
Sylvie Crowe e79dcaf910 fix ability to log into ssh with key and password (#130)
* fix ability to log into ssh with key and password

A previous refactor to the ssh password system broke the ability to use
key+password to log in. This change handles key+password as a special
case allowing it to be handled separately.

* clean up unnecessary print

My last change left in a debug print that wasn't necessary in the code.
This has been removed.
2023-12-12 14:17:53 -08:00
Evan Simkowitz e6186cd694 Fixing linting errors in unused keygen function (#135)
* Fixing missing parameter error in unused keygen function

* applying error formatting linter suggestions

* fixing more linting suggestions
2023-12-12 14:17:03 -08:00
Evan Simkowitz 58551d6a2a Configuring CodeQL scanning workflow
Configuring the default CodeQL scanning workflow. This will automatically scan our code for known vulnerabilities.
2023-12-12 10:17:55 -08:00
Mike Sawka 2b8adfe40e add homebrew instructions to readme 2023-12-10 13:33:06 -08:00
sawka ce4b6d413c fix remaining warnings in workspace 2023-12-08 13:39:13 -08:00
Red J Adaya 8a938744f7 migrate modals to new modals system (#124)
* migrate screen settings modal to new modals system

* use screen member var in the methods

* migrate session settings modal to new modal system

* use Modal component in session settings modal

* migrate line settings modal to new modals system

* use Modal component in line settings modal

* migrate client settings modal to new modals framework

* set alert modal width to 500px

* remove screen settings modal after deletion

* use Dropdown component for connnections dropdown

* use Dropdown component for connections dropdown in new tab flow

* replace InfoMessage with Tooltip

* use Dropdown for fontsize dropdown

* use Dropdown for renderer dropdown

* fix dropdown width issue on new tab container

* fix class names concatenation

* fix dropdown width issue in screen settings modal
2023-12-07 22:51:46 -08:00
Sylvie Crowe 87bf3f7a65 allow @ symbol in user for ssh connections (#129)
* allow @ symbol in user for ssh connections

Previously, the @ symbol was only used as a way to either:
- separate the user from the host
- separate "sudo" from the user

This change expands this to allow the username part of
sudo@username@host or username@host to contain any number of @ symbols
in addition to the ones previously allowed. Host is not allowed to
contain an @ symbol as per the usual definition.

* clean up regex changes

Moved the dash in the regex pattern to the end to make it explicitly
clear that it isn't part of a range.

Removed the hostNameRe regex as it is unused.
2023-12-07 18:33:16 -08:00
65 changed files with 2902 additions and 941 deletions
+81
View File
@@ -0,0 +1,81 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '36 5 * * 5'
jobs:
analyze:
name: Analyze
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners
# Consider using larger runners for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go', 'javascript-typescript' ]
# CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ]
# Use only 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
+5
View File
@@ -25,6 +25,11 @@ Wave Terminal works with MacOS and Linux (preliminary).
Install Wave Terminal from: [www.waveterm.dev/download](https://www.waveterm.dev/download)
Also available as a homebrew cask for MacOS:
```
brew install --cask wave
```
## Links
* Homepage — https://www.waveterm.dev
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# assumes we have Wave-darwin-x64-[version].zip and Wave-darwin-arm64-[version].zip in current directory
VERSION=0.5.1
VERSION=0.5.3
rm -rf temp
mkdir temp
mkdir temp/x64
+1
View File
@@ -1,3 +1,4 @@
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
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=
+3 -1
View File
@@ -2,7 +2,7 @@
"name": "waveterm",
"author": "Command Line Inc",
"productName": "Wave",
"version": "0.5.1",
"version": "0.5.3",
"main": "dist/emain.js",
"license": "Apache-2.0",
"dependencies": {
@@ -11,11 +11,13 @@
"@table-nav/react": "^0.0.7",
"@tanstack/match-sorter-utils": "^8.8.4",
"@tanstack/react-table": "^8.10.3",
"@types/semver": "^7.5.6",
"autobind-decorator": "^2.4.0",
"classnames": "^2.3.1",
"dayjs": "^1.11.3",
"dompurify": "^3.0.2",
"electron-squirrel-startup": "^1.0.0",
"framer-motion": "^10.16.16",
"mobx": "6.12",
"mobx-react": "^7.5.0",
"monaco-editor": "^0.44.0",
+1 -1
View File
@@ -27,7 +27,7 @@ node_modules/.bin/electron-rebuild
```bash
# @scripthaus command electron
# @scripthaus cd :playbook
WAVETERM_DEV=1 PCLOUD_ENDPOINT="https://ot2e112zx5.execute-api.us-west-2.amazonaws.com/dev" node_modules/.bin/electron dist-dev/emain.js
WAVETERM_DEV=1 PCLOUD_ENDPOINT="https://ot2e112zx5.execute-api.us-west-2.amazonaws.com/dev" PCLOUD_WS_ENDPOINT="wss://5lfzlg5crl.execute-api.us-west-2.amazonaws.com/dev/" node_modules/.bin/electron dist-dev/emain.js
```
```bash
-19
View File
@@ -74,9 +74,6 @@ class App extends React.Component<{}, {}> {
}
render() {
let screenSettingsModal = GlobalModel.screenSettingsModal.get();
let sessionSettingsModal = GlobalModel.sessionSettingsModal.get();
let lineSettingsModal = GlobalModel.lineSettingsModal.get();
let clientSettingsModal = GlobalModel.clientSettingsModal.get();
let remotesModel = GlobalModel.remotesModel;
let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get();
@@ -121,22 +118,6 @@ class App extends React.Component<{}, {}> {
</ErrorBoundary>
</div>
<ModalsProvider />
<If condition={screenSettingsModal != null}>
<ScreenSettingsModal
key={screenSettingsModal.sessionId + ":" + screenSettingsModal.screenId}
sessionId={screenSettingsModal.sessionId}
screenId={screenSettingsModal.screenId}
/>
</If>
<If condition={sessionSettingsModal != null}>
<SessionSettingsModal key={sessionSettingsModal} sessionId={sessionSettingsModal} />
</If>
<If condition={lineSettingsModal != null}>
<LineSettingsModal key={String(lineSettingsModal)} linenum={lineSettingsModal} />
</If>
<If condition={clientSettingsModal}>
<ClientSettingsModal />
</If>
</div>
);
}
+8
View File
@@ -3,3 +3,11 @@ export const CREATE_REMOTE = "createRemote";
export const VIEW_REMOTE = "viewRemote";
export const EDIT_REMOTE = "editRemote";
export const ALERT = "alert";
export const SCREEN_SETTINGS = "screenSettings";
export const SESSION_SETTINGS = "sessionSettings";
export const LINE_SETTINGS = "lineSettings";
export const CLIENT_SETTINGS = "clientSettings";
export const LineContainer_Main = "main";
export const LineContainer_History = "history";
export const LineContainer_Sidebar = "sidebar";
+18 -9
View File
@@ -134,7 +134,7 @@
position: relative;
display: inline-block;
width: 40px;
height: 23px;
height: 22px;
input {
opacity: 0;
@@ -160,8 +160,8 @@
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
left: 2px;
bottom: 2px;
background-color: @term-white;
transition: 0.5s;
border-radius: 50%;
@@ -610,7 +610,7 @@
position: relative;
background-color: transparent;
height: 44px;
min-width: 412px;
min-width: 150px;
width: 100%;
border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15));
border-radius: 6px;
@@ -618,6 +618,10 @@
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.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
&.no-label {
height: 34px;
}
&-label {
position: absolute;
left: 16px;
@@ -841,7 +845,7 @@
justify-content: center;
i {
font-size: 16px;
font-size: 13px;
}
}
@@ -865,11 +869,11 @@
background-color: #444;
border-radius: 5px;
overflow: hidden;
max-width: 300px;
width: 300px;
i {
display: inline;
font-size: 16px;
font-size: 13px;
fill: @base-color;
padding-top: 0.2em;
}
@@ -1114,6 +1118,11 @@
line-height: 20px;
border-bottom: 1px solid rgba(250, 250, 250, 0.1);
.wave-modal-title {
color: #eceeec;
font-size: 15px;
}
button {
i {
font-size: 18px;
@@ -1132,8 +1141,8 @@
width: 100%;
padding: 0 20px 20px;
button:first-child {
margin-right: 8px;
button:last-child {
margin-left: 8px;
}
}
}
+27 -45
View File
@@ -150,6 +150,7 @@ interface TooltipProps {
message: React.ReactNode;
icon?: React.ReactNode; // Optional icon property
children: React.ReactNode;
className?: string;
}
interface TooltipState {
@@ -185,8 +186,8 @@ class Tooltip extends React.Component<TooltipProps, TooltipState> {
if (iconElement) {
const rect = iconElement.getBoundingClientRect();
return {
top: `${rect.bottom + window.scrollY - 29.5}px`,
left: `${rect.left + window.scrollX + rect.width / 2 - 19}px`,
top: `${rect.bottom + window.scrollY - 29}px`,
left: `${rect.left + window.scrollX + rect.width / 2 - 17.5}px`,
};
}
return {};
@@ -199,7 +200,7 @@ class Tooltip extends React.Component<TooltipProps, TooltipState> {
const style = this.calculatePosition();
return ReactDOM.createPortal(
<div className="wave-tooltip" style={style}>
<div className={cn("wave-tooltip", this.props.className)} style={style}>
{this.props.icon && <div className="wave-tooltip-icon">{this.props.icon}</div>}
<div className="wave-tooltip-message">{this.props.message}</div>
</div>,
@@ -387,7 +388,7 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
// Method to handle blur at the component level
@boundMethod
handleComponentBlur() {
if (this.inputRef.current && this.inputRef.current.contains(document.activeElement)) {
if (this.inputRef.current?.contains(document.activeElement)) {
this.inputRef.current.blur();
}
}
@@ -440,7 +441,7 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
const { focused, internalValue, error } = this.state;
// Decide if the input should behave as controlled or uncontrolled
const inputValue = value !== undefined ? value : internalValue;
const inputValue = value ?? internalValue;
return (
<div
@@ -507,34 +508,10 @@ class NumberField extends TextField {
}
}
@boundMethod
handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
// Allow backspace, delete, tab, escape, and enter
if (
[46, 8, 9, 27, 13].includes(event.keyCode) ||
// Allow: Ctrl+A, Ctrl+C, Ctrl+X
((event.keyCode === 65 || event.keyCode === 67 || event.keyCode === 88) && event.ctrlKey === true) ||
// Allow: home, end, left, right
(event.keyCode >= 35 && event.keyCode <= 39)
) {
return; // let it happen, don't do anything
}
// Ensure that it is a number and stop the keypress
if (
(event.shiftKey || event.keyCode < 48 || event.keyCode > 57) &&
(event.keyCode < 96 || event.keyCode > 105)
) {
event.preventDefault();
}
}
render() {
// Use the render method from TextField but add the onKeyDown handler
const renderedTextField = super.render();
return React.cloneElement(renderedTextField, {
onKeyDown: this.handleKeyDown,
});
return React.cloneElement(renderedTextField);
}
}
@@ -572,7 +549,7 @@ class PasswordField extends TextField {
render() {
const { decoration, className, placeholder, maxLength, label } = this.props;
const { focused, internalValue, error, passwordVisible } = this.state;
const inputValue = this.props.value !== undefined ? this.props.value : internalValue;
const inputValue = this.props.value ?? internalValue;
// The input should always receive the real value
const inputProps = {
@@ -797,7 +774,7 @@ class InfoMessage extends React.Component<{ width: number; children: React.React
function LinkRenderer(props: any): any {
let newUrl = "https://extern?" + encodeURIComponent(props.href);
return (
<a href={newUrl} target="_blank">
<a href={newUrl} target="_blank" rel={"noopener"}>
{props.children}
</a>
);
@@ -864,7 +841,7 @@ interface DropdownDecorationProps {
}
interface DropdownProps {
label: string;
label?: string;
options: { value: string; label: string }[];
value?: string;
className?: string;
@@ -1031,7 +1008,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
const { label, options, value, placeholder, decoration, className, required } = this.props;
const { isOpen, internalValue, highlightedIndex, isTouched } = this.state;
const currentValue = value !== undefined ? value : internalValue;
const currentValue = value ?? internalValue;
const selectedOptionLabel =
options.find((option) => option.value === currentValue)?.label || placeholder || internalValue;
@@ -1051,7 +1028,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
{options.map((option, index) => (
<div
key={option.value}
className={cn("wave-dropdown-item", {
className={cn("wave-dropdown-item unselectable", {
"wave-dropdown-item-highlighted": index === highlightedIndex,
})}
onClick={(e) => this.handleSelect(option.value, e)}
@@ -1068,8 +1045,9 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
return (
<div
className={cn(`wave-dropdown ${className || ""}`, {
className={cn("wave-dropdown", className, {
"wave-dropdown-error": isError,
"no-label": !label,
})}
ref={this.wrapperRef}
tabIndex={0}
@@ -1078,15 +1056,19 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
onFocus={this.handleFocus}
>
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
<If condition={label}>
<div
className={cn("wave-dropdown-label unselectable", {
float: shouldLabelFloat,
"offset-left": decoration?.startDecoration,
})}
>
{label}
</div>
</If>
<div
className={cn("wave-dropdown-label", {
float: shouldLabelFloat,
"offset-left": decoration?.startDecoration,
})}
className={cn("wave-dropdown-display unselectable", { "offset-left": decoration?.startDecoration })}
>
{label}
</div>
<div className={cn("wave-dropdown-display", { "offset-left": decoration?.startDecoration })}>
{selectedOptionLabel}
</div>
<div className={cn("wave-dropdown-arrow", { "wave-dropdown-arrow-rotate": isOpen })}>
@@ -1106,7 +1088,7 @@ interface ModalHeaderProps {
const ModalHeader: React.FC<ModalHeaderProps> = ({ onClose, title }) => (
<div className="wave-modal-header">
{<div>{title}</div>}
{<div className="wave-modal-title">{title}</div>}
<IconButton theme="secondary" variant="ghost" onClick={onClose}>
<i className="fa-sharp fa-solid fa-xmark"></i>
</IconButton>
@@ -1159,7 +1141,7 @@ class Modal extends React.Component<ModalProps> {
}
render() {
return ReactDOM.createPortal(this.renderModal(), document.getElementById("app") as HTMLElement);
return ReactDOM.createPortal(this.renderModal(), document.getElementById("app") );
}
}
+100 -2
View File
@@ -396,13 +396,109 @@
gap: 12px;
align-self: stretch;
width: 100%;
> div {
width: 100%;
}
}
}
}
.screen-settings-modal {
width: 640px;
min-height: 329px;
.wave-modal-content {
gap: 24px;
.wave-modal-body {
display: flex;
padding: 0px 20px;
flex-direction: column;
align-items: flex-start;
gap: 4px;
align-self: stretch;
width: 100%;
.screen-settings-dropdown {
width: 412px;
.lefticon {
position: absolute;
top: 50%;
left: 16px;
transform: translateY(-50%);
.globe-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.status-icon {
position: absolute;
left: 7px;
top: 8px;
}
}
}
.archived-label,
.actions-label {
div:first-child {
margin-right: 5px;
}
div:last-child i {
font-size: 13px;
}
}
}
}
}
.screen-settings-tooltip .wave-tooltip-icon {
i {
font-size: 13px;
}
}
.session-settings-modal {
width: 640px;
.wave-modal-content {
gap: 24px;
.wave-modal-body {
display: flex;
padding: 0px 20px;
flex-direction: column;
align-items: flex-start;
gap: 4px;
align-self: stretch;
width: 100%;
}
}
}
.line-settings-modal {
width: 640px;
.wave-modal-content {
gap: 24px;
.wave-modal-body {
display: flex;
padding: 0px 20px;
flex-direction: column;
align-items: flex-start;
gap: 4px;
align-self: stretch;
width: 100%;
}
}
}
.client-settings-modal {
width: 640px;
.wave-modal-content {
gap: 24px;
@@ -470,6 +566,8 @@
}
.alert-modal {
width: 500px;
.wave-modal-content {
.wave-modal-body {
padding: 40px 20px;
@@ -647,7 +745,7 @@
.client-settings-modal {
.settings-field {
.settings-label {
width: 150px;
width: 157px;
}
.dropdown-menu {
min-width: 7.25em;
+82 -77
View File
@@ -11,12 +11,22 @@ import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../model/model";
import * as T from "../../../types/types";
import { Markdown } from "../common";
import {
Markdown,
Toggle,
Modal,
TextField,
NumberField,
InputDecoration,
Dropdown,
PasswordField,
Tooltip,
Button,
Status,
} from "../common";
import * as util from "../../../util/util";
import * as textmeasure from "../../../util/textmeasure";
import { Toggle, Modal } from "../common";
import { ClientDataType } from "../../../types/types";
import { TextField, NumberField, InputDecoration, Dropdown, PasswordField, Tooltip, Button, Status } from "../common";
import { ReactComponent as WarningIcon } from "../../assets/icons/line/triangle-exclamation.svg";
import shield from "../../assets/icons/shield_check.svg";
@@ -39,18 +49,18 @@ const PasswordUnchangedSentinel = "--unchanged--";
@mobxReact.observer
class ModalsProvider extends React.Component {
renderModals() {
const modals = GlobalModel.modalsModel.activeModals;
render() {
let store = GlobalModel.modalsModel.store.slice();
if (GlobalModel.needsTos()) {
return <TosModal />;
}
return modals.map((ModalComponent, index) => <ModalComponent key={index} />);
}
render() {
return <>{this.renderModals()}</>;
let rtn: JSX.Element[] = [];
for (let i = 0; i < store.length; i++) {
let entry = store[i];
let Comp = entry.component;
rtn.push(<Comp key={entry.uniqueKey} />);
}
return <>{rtn}</>;
}
}
@@ -279,7 +289,11 @@ class TosModal extends React.Component<{}, {}> {
</div>
</div>
<div className="item">
<a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}>
<a
target="_blank"
href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}
rel={"noopener"}
>
<img src={help} alt="Help" />
</a>
<div className="item-inner">
@@ -288,7 +302,11 @@ class TosModal extends React.Component<{}, {}> {
Get help, submit feature requests, report bugs, or just chat with fellow
terminal enthusiasts.
<br />
<a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}>
<a
target="_blank"
href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}
rel={"noopener"}
>
Join the Wave&nbsp;Discord&nbsp;Channel
</a>
</div>
@@ -298,6 +316,7 @@ class TosModal extends React.Component<{}, {}> {
<a
target="_blank"
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
rel={"noopener"}
>
<img src={github} alt="Github" />
</a>
@@ -309,6 +328,7 @@ class TosModal extends React.Component<{}, {}> {
<a
target="_blank"
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
rel={"noopener"}
>
Github&nbsp;(wavetermdev/waveterm)
</a>
@@ -416,6 +436,7 @@ class AboutModal extends React.Component<{}, {}> {
<a
className="wave-button wave-button-link color-standard"
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
rel={"noopener"}
target="_blank"
>
<i className="fa-brands fa-github"></i>
@@ -424,6 +445,7 @@ class AboutModal extends React.Component<{}, {}> {
<a
className="wave-button wave-button-link color-standard"
href={util.makeExternLink("https://www.waveterm.dev/")}
rel={"noopener"}
target="_blank"
>
<i className="fa-sharp fa-light fa-globe"></i>
@@ -432,6 +454,7 @@ class AboutModal extends React.Component<{}, {}> {
<a
className="wave-button wave-button-link color-standard"
href={util.makeExternLink("https://github.com/wavetermdev/waveterm/blob/main/LICENSE")}
rel={"noopener"}
target="_blank"
>
<i className="fa-sharp fa-light fa-book-blank"></i>
@@ -751,7 +774,6 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
label="Connect Mode"
options={[
{ value: "startup", label: "startup" },
{ value: "key", label: "key" },
{ value: "auto", label: "auto" },
{ value: "manual", label: "manual" },
]}
@@ -844,7 +866,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
@boundMethod
openEditModal(): void {
GlobalModel.remotesModel.openEditModal();
GlobalModel.remotesModel.startEditAuth();
}
@boundMethod
@@ -957,7 +979,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
</Button>
);
if (remote.local) {
installNowButton = <></>;
installNowButton = <></>;
updateAuthButton = <></>;
cancelInstallButton = <></>;
}
@@ -1113,74 +1135,47 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
@mobxReact.observer
class EditRemoteConnModal extends React.Component<{}, {}> {
internalTempAlias: OV<string>;
internalTempKeyFile: OV<string>;
internalTempPassword: OV<string>;
tempAlias: OV<string>;
tempKeyFile: OV<string>;
tempPassword: OV<string>;
tempConnectMode: OV<string>;
tempAuthMode: OV<string>;
model: RemotesModel;
constructor(props: { remotesModel?: RemotesModel }) {
super(props);
this.model = GlobalModel.remotesModel;
this.internalTempAlias = mobx.observable.box(null, { name: "EditRemoteSettings-internalTempAlias" });
this.internalTempKeyFile = mobx.observable.box(null, { name: "EditRemoteSettings-internalTempKeyFile" });
this.internalTempPassword = mobx.observable.box(null, { name: "EditRemoteSettings-internalTempPassword" });
this.tempAlias = mobx.observable.box(null, { name: "EditRemoteSettings-tempAlias" });
this.tempAuthMode = mobx.observable.box(null, { name: "EditRemoteSettings-tempAuthMode" });
this.tempKeyFile = mobx.observable.box(null, { name: "EditRemoteSettings-tempKeyFile" });
this.tempPassword = mobx.observable.box(null, { name: "EditRemoteSettings-tempPassword" });
this.tempConnectMode = mobx.observable.box(null, { name: "EditRemoteSettings-tempConnectMode" });
}
@mobx.computed
get selectedRemoteId() {
return this.model.selectedRemoteId.get();
}
@mobx.computed
get selectedRemote(): T.RemoteType {
return GlobalModel.getRemote(this.selectedRemoteId);
}
@mobx.computed
get remoteEdit(): T.RemoteEditType {
return this.model.remoteEdit.get();
}
@mobx.computed
get isAuthEditMode(): boolean {
return this.model.isAuthEditMode();
}
@mobx.computed
get tempAuthMode(): mobx.IObservableValue<string> {
return mobx.observable.box(this.selectedRemote?.authtype, {
name: "EditRemoteConnModal-authMode",
});
}
@mobx.computed
get tempConnectMode(): mobx.IObservableValue<string> {
return mobx.observable.box(this.selectedRemote?.connectmode, {
name: "EditRemoteConnModal-connectMode",
});
}
@mobx.computed
get tempAlias(): mobx.IObservableValue<string> {
return mobx.observable.box(this.internalTempAlias.get() || this.selectedRemote.remotealias, {
name: "EditRemoteConnModal-alias",
});
}
@mobx.computed
get tempKeyFile(): mobx.IObservableValue<string> {
return mobx.observable.box(this.internalTempKeyFile.get() || this.remoteEdit?.keystr, {
name: "EditRemoteConnModal-keystr",
});
}
@mobx.computed
get tempPassword(): mobx.IObservableValue<string> {
const oldPassword = this.remoteEdit?.haspassword ? PasswordUnchangedSentinel : "";
const newPassword = this.internalTempPassword.get() || oldPassword;
return mobx.observable.box(newPassword, {
name: "EditRemoteConnModal-password",
});
componentDidMount(): void {
mobx.action(() => {
this.tempAlias.set(this.selectedRemote?.remotealias);
this.tempKeyFile.set(this.remoteEdit?.keystr);
this.tempPassword.set(this.remoteEdit?.haspassword ? PasswordUnchangedSentinel : "");
this.tempConnectMode.set(this.selectedRemote?.connectmode);
this.tempAuthMode.set(this.selectedRemote?.authtype);
})();
}
componentDidUpdate() {
@@ -1192,21 +1187,35 @@ class EditRemoteConnModal extends React.Component<{}, {}> {
@boundMethod
handleChangeKeyFile(value: string): void {
mobx.action(() => {
this.internalTempKeyFile.set(value);
this.tempKeyFile.set(value);
})();
}
@boundMethod
handleChangePassword(value: string): void {
mobx.action(() => {
this.internalTempPassword.set(value);
this.tempPassword.set(value);
})();
}
@boundMethod
handleChangeAlias(value: string): void {
mobx.action(() => {
this.internalTempAlias.set(value);
this.tempAlias.set(value);
})();
}
@boundMethod
handleChangeAuthMode(value: string): void {
mobx.action(() => {
this.tempAuthMode.set(value);
})();
}
@boundMethod
handleChangeConnectMode(value: string): void {
mobx.action(() => {
this.tempConnectMode.set(value);
})();
}
@@ -1236,10 +1245,13 @@ class EditRemoteConnModal extends React.Component<{}, {}> {
submitRemote(): void {
let authMode = this.tempAuthMode.get();
let kwargs: Record<string, string> = {};
if (!util.isStrEq(this.tempKeyFile.get(), this.remoteEdit?.keystr)) {
if (authMode == "key" || authMode == "key+password") {
if (authMode == "key" || authMode == "key+password") {
let keyStrEq = util.isStrEq(this.tempKeyFile.get(), this.remoteEdit?.keystr);
if (!keyStrEq) {
kwargs["key"] = this.tempKeyFile.get();
} else {
}
} else {
if (!util.isBlank(this.tempKeyFile.get())) {
kwargs["key"] = "";
}
}
@@ -1289,11 +1301,9 @@ class EditRemoteConnModal extends React.Component<{}, {}> {
render() {
let authMode = this.tempAuthMode.get();
if (this.remoteEdit === null || !this.isAuthEditMode) {
return null;
}
return (
<Modal className="erconn-modal">
<Modal.Header title="Edit Connection" onClose={this.model.closeModal} />
@@ -1331,9 +1341,7 @@ class EditRemoteConnModal extends React.Component<{}, {}> {
{ value: "key+password", label: "key+password" },
]}
value={this.tempAuthMode.get()}
onChange={(val: string) => {
this.tempAuthMode.set(val);
}}
onChange={this.handleChangeAuthMode}
decoration={{
endDecoration: (
<InputDecoration>
@@ -1400,14 +1408,11 @@ class EditRemoteConnModal extends React.Component<{}, {}> {
label="Connect Mode"
options={[
{ value: "startup", label: "startup" },
{ value: "key", label: "key" },
{ value: "auto", label: "auto" },
{ value: "manual", label: "manual" },
]}
value={this.tempConnectMode.get()}
onChange={(val: string) => {
this.tempConnectMode.set(val);
}}
onChange={this.handleChangeConnectMode}
/>
</div>
<If condition={!util.isBlank(this.remoteEdit?.errorstr)}>
+5
View File
@@ -9,6 +9,7 @@ import {
EditRemoteConnModal,
AlertModal,
} from "./modals";
import { ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal } from "./settings";
import * as constants from "../../appconst";
const modalsRegistry: { [key: string]: () => React.ReactElement } = {
@@ -17,6 +18,10 @@ const modalsRegistry: { [key: string]: () => React.ReactElement } = {
[constants.VIEW_REMOTE]: () => <ViewRemoteConnDetailModal />,
[constants.EDIT_REMOTE]: () => <EditRemoteConnModal />,
[constants.ALERT]: () => <AlertModal />,
[constants.SCREEN_SETTINGS]: () => <ScreenSettingsModal />,
[constants.SESSION_SETTINGS]: () => <SessionSettingsModal />,
[constants.LINE_SETTINGS]: () => <LineSettingsModal />,
[constants.CLIENT_SETTINGS]: () => <ClientSettingsModal />,
};
export { modalsRegistry };
File diff suppressed because it is too large Load Diff
+73 -29
View File
@@ -39,6 +39,7 @@ import { PluginModel } from "../../plugins/plugins";
import { Prompt } from "../common/prompt/prompt";
import * as lineutil from "./lineutil";
import { ErrorBoundary } from "../../app/common/error/errorboundary";
import * as appconst from "../appconst";
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
import { ReactComponent as CommentIcon } from "../assets/icons/line/comment.svg";
@@ -75,11 +76,14 @@ class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRigh
iconTitle = "success";
} else {
icon = <XmarkIcon className="fail" />;
iconTitle = "fail";
iconTitle = "exitcode " + exitcode;
}
} else if (status == "hangup" || status == "error") {
} else if (status == "hangup") {
icon = <WarningIcon className="warning" />;
iconTitle = status;
} else if (status == "error") {
icon = <XmarkIcon className="fail" />;
iconTitle = "error";
} else if (status == "running" || "detached") {
icon = <RotateIcon className="warning spin" />;
iconTitle = "running";
@@ -126,7 +130,7 @@ class LineCmd extends React.Component<
isOverflow: OV<boolean> = mobx.observable.box(false, {
name: "line-overflow",
});
isMinimised: OV<boolean> = mobx.observable.box(false, {
isMinimized: OV<boolean> = mobx.observable.box(false, {
name: "line-minimised",
});
isCmdExpanded: OV<boolean> = mobx.observable.box(false, {
@@ -351,12 +355,23 @@ class LineCmd extends React.Component<
}
@boundMethod
clickMinimise() {
clickMinimize() {
mobx.action(() => {
this.isMinimised.set(!this.isMinimised.get());
this.isMinimized.set(!this.isMinimized.get());
})();
}
@boundMethod
clickMoveToSidebar() {
let { line } = this.props;
GlobalCommandRunner.screenSidebarAddLine(line.lineid);
}
@boundMethod
clickRemoveFromSidebar() {
GlobalCommandRunner.screenSidebarRemove();
}
@boundMethod
handleResizeButton() {
console.log("resize button");
@@ -439,6 +454,7 @@ class LineCmd extends React.Component<
mobx.action(() => {
GlobalModel.lineSettingsModal.set(line.linenum);
})();
GlobalModel.modalsModel.pushModal(appconst.LINE_SETTINGS);
}
}
@@ -593,6 +609,14 @@ class LineCmd extends React.Component<
{ name: "computed-shouldCmdFocus" }
)
.get();
let isInSidebar = mobx
.computed(
() => {
return screen.isSidebarOpen() && screen.isLineIdInSidebar(line.lineid);
},
{ name: "computed-isInSidebar" }
)
.get();
let isStatic = staticRender;
let isRunning = cmd.isRunning();
let isExpanded = this.isCmdExpanded.get();
@@ -617,6 +641,7 @@ class LineCmd extends React.Component<
if (rtnStateDiffSize < 10) {
rtnStateDiffSize = Math.max(termFontSize, 10);
}
let containerType = screen.getContainerType();
return (
<div
className={mainDivCn}
@@ -634,38 +659,57 @@ class LineCmd extends React.Component<
{this.renderMeta1(cmd)}
<If condition={!hidePrompt}>{this.renderCmdText(cmd)}</If>
</div>
<div
key="pin"
title="Pin"
className={cn("line-icon", { active: line.pinned })}
onClick={this.clickPin}
style={{ display: "none" }}
>
<PinIcon className="icon" />
</div>
<div
key="bookmark"
title="Bookmark"
className={cn("line-icon", "line-bookmark", "hoverEffect")}
onClick={this.clickBookmark}
>
<FavoritesIcon className="icon" />
</div>
<div
key="minimise"
title={`${this.isMinimised.get() ? "Maximise" : "Minimise"}`}
className={cn(
"line-icon",
"line-minimise",
"hoverEffect",
this.isMinimised.get() ? "line-icon-show" : ""
)}
onClick={this.clickMinimise}
>
{this.isMinimised.get() ? <PlusIcon className="icon plus" /> : <MinusIcon className="icon" />}
<i className="fa-sharp fa-regular fa-bookmark" />
</div>
<If condition={containerType == appconst.LineContainer_Main}>
<div
key="minimize"
title={`${this.isMinimized.get() ? "Maximise" : "Minimize"}`}
className={cn(
"line-icon",
"line-minimize",
"hoverEffect",
this.isMinimized.get() ? "line-icon-show" : ""
)}
onClick={this.clickMinimize}
>
<If condition={this.isMinimized.get()}>
<i className="fa-sharp fa-regular fa-circle-plus" />
</If>
<If condition={!this.isMinimized.get()}>
<i className="fa-sharp fa-regular fa-circle-minus" />
</If>
</div>
<div
className="line-icon line-sidebar"
onClick={this.clickMoveToSidebar}
title="Move to Sidebar"
>
<i className="fa-sharp fa-solid fa-right-to-line" />
</div>
</If>
<If condition={containerType == appconst.LineContainer_Sidebar}>
<div
className="line-icon line-sidebar"
onClick={this.clickRemoveFromSidebar}
title="Move to Sidebar"
>
<i className="fa-sharp fa-solid fa-left-to-line" />
</div>
</If>
</div>
<If condition={!this.isMinimised.get()}>
<If condition={isInSidebar}>
<div className="sidebar-message" style={{ fontSize: termFontSize }}>
&nbsp;&nbsp;showing in sidebar =&gt;
</div>
</If>
<If condition={!this.isMinimized.get() && !isInSidebar}>
<ErrorBoundary plugin={rendererPlugin?.name} lineContext={lineutil.getRendererContext(line)}>
<If condition={rendererPlugin == null && !isNoneRenderer}>
<TerminalRenderer
+6 -11
View File
@@ -35,11 +35,16 @@
}
}
.sidebar-message {
color: @term-yellow;
}
.line-header {
display: flex;
flex-direction: row;
padding-bottom: 0.7rem;
width: 100%;
line-height: 1.2;
&.is-expanded {
height: auto;
@@ -54,9 +59,8 @@
visibility: hidden;
cursor: pointer;
padding: 3px;
width: 2rem;
height: 2rem;
border-radius: 50%;
font-size: 14px;
}
.line-icon-show {
@@ -307,15 +311,6 @@
right: -4px;
}
&.num-4 {
}
&.num-5 {
}
&.num-6 {
}
&.status-done {
background-color: #555;
}
+7 -1
View File
@@ -28,6 +28,8 @@ type ScreenInterface = {
setAnchorFields(anchorLine: number, anchorOffset: number, reason: string): void;
getSelectedLine(): number;
getAnchor(): { anchorLine: number; anchorOffset: number };
isLineIdInSidebar(lineId: string): boolean;
getLineByNum(lineNum: number): T.LineType;
};
// <Line key={line.lineid} line={line} screen={screen} width={width} visible={this.visibleMap.get(lineNumStr)} staticRender={this.staticRender.get()} onHeightChange={this.onHeightChange} overrideCollapsed={this.collapsedMap.get(lineNumStr)} topBorder={topBorder} renderMode={renderMode}/>;
@@ -298,6 +300,10 @@ class LinesView extends React.Component<
if (newLine == 0) {
return;
}
let line = screen.getLineByNum(newLine);
if (line == null || screen.isLineIdInSidebar(line.lineid)) {
return;
}
let lidx = this.findClosestLineIndex(newLine);
this.setLineVisible(newLine, true);
// console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", screen.anchorLine, screen.anchorOffset));
@@ -351,7 +357,7 @@ class LinesView extends React.Component<
}
}
handleResize(entries: any) {
handleResize(entries: ResizeObserverEntry[]) {
let linesElem = this.linesRef.current;
if (linesElem == null) {
return;
+12 -5
View File
@@ -7,11 +7,11 @@ let MagicLayout = {
CmdInputHeight: 101, // 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)
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
LineMarginTop: 12, // .line, margin
ScreenMaxContentWidthBuffer: 50,
ScreenMaxContentHeightBuffer: 0, // calc below
@@ -21,12 +21,19 @@ let MagicLayout = {
// the 3 is for descenders, which get cut off in the terminal without this
TermDescendersHeight: 3,
TermWidthBuffer: 15,
TabWidth: 175,
ScreenSidebarWidthPadding: 5,
ScreenSidebarMinWidth: 200,
ScreenSidebarHeaderHeight: 28,
};
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;
m.ScreenMaxContentHeightBuffer =
m.LineHeaderHeight + m.LinePadding + m.WindowHeightOffset + m.LinesBottomPadding + m.LineMarginTop - 2;
(window as any).MagicLayout = MagicLayout;
+10
View File
@@ -9,6 +9,7 @@
position: relative;
font-size: 12.5px;
line-height: 20px;
backdrop-filter: blur(4px);
.title-bar-drag {
-webkit-app-region: drag;
@@ -320,4 +321,13 @@
top: -3px;
}
}
.updateBanner {
font-weight: bold;
.icon {
font-weight: normal;
font-size: 16px;
}
}
}
+20 -6
View File
@@ -8,14 +8,14 @@ import { boundMethod } from "autobind-decorator";
import cn from "classnames";
import dayjs from "dayjs";
import type { RemoteType } from "../../types/types";
import { If, For } from "tsx-control-statements/components";
import { If } from "tsx-control-statements/components";
import { compareLoose } from "semver";
import { ReactComponent as LeftChevronIcon } from "../assets/icons/chevron_left.svg";
import { ReactComponent as HelpIcon } from "../assets/icons/help.svg";
import { ReactComponent as SettingsIcon } from "../assets/icons/settings.svg";
import { ReactComponent as DiscordIcon } from "../assets/icons/discord.svg";
import { ReactComponent as HistoryIcon } from "../assets/icons/history.svg";
import { ReactComponent as FavoritesIcon } from "../assets/icons/favourites.svg";
import { ReactComponent as AppsIcon } from "../assets/icons/apps.svg";
import { ReactComponent as ConnectionsIcon } from "../assets/icons/connections.svg";
import { ReactComponent as WorkspacesIcon } from "../assets/icons/workspaces.svg";
@@ -23,8 +23,9 @@ import { ReactComponent as AddIcon } from "../assets/icons/add.svg";
import { ReactComponent as ActionsIcon } from "../assets/icons/tab/actions.svg";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner, Session } from "../../model/model";
import { GlobalModel, GlobalCommandRunner, Session, VERSION } from "../../model/model";
import { sortAndFilterRemotes, isBlank, openLink } from "../../util/util";
import * as constants from "../appconst";
import "./sidebar.less";
@@ -130,9 +131,7 @@ class MainSideBar extends React.Component<{}, {}> {
@boundMethod
handleSettingsClick(): void {
mobx.action(() => {
GlobalModel.clientSettingsModal.set(true);
})();
GlobalModel.modalsModel.pushModal(constants.CLIENT_SETTINGS);
}
@boundMethod
@@ -142,6 +141,7 @@ class MainSideBar extends React.Component<{}, {}> {
mobx.action(() => {
GlobalModel.sessionSettingsModal.set(session.sessionId);
})();
GlobalModel.modalsModel.pushModal(constants.SESSION_SETTINGS);
}
getSessions() {
@@ -196,6 +196,11 @@ class MainSideBar extends React.Component<{}, {}> {
}
let isCollapsed = this.collapsed.get();
let mainView = GlobalModel.activeMainView.get();
let clientData = GlobalModel.clientData.get();
let needsUpdate = false;
if (!clientData?.clientopts.noreleasecheck && !isBlank(clientData?.releaseinfo?.latestversion)) {
needsUpdate = compareLoose(VERSION, clientData.releaseinfo.latestversion) < 0;
}
return (
<div className={cn("main-sidebar", { collapsed: isCollapsed }, { "is-dev": GlobalModel.isDev })}>
<div className="title-bar-drag" />
@@ -243,6 +248,15 @@ class MainSideBar extends React.Component<{}, {}> {
</div>
<div className="middle hideScrollbarUntillHover">{this.getSessions()}</div>
<div className="bottom">
<If condition={needsUpdate}>
<div
className="item hoverEffect unselectable updateBanner"
onClick={() => openLink("https://www.waveterm.dev/download")}
>
<i className="fa-sharp fa-regular fa-circle-up icon" />
Update Available
</div>
</If>
<If condition={GlobalModel.isDev}>
<div className="item hoverEffect unselectable" onClick={this.handlePluginsClick}>
<AppsIcon className="icon" />

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