mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ce7b92232 | |||
| 81a9e74991 | |||
| 679e85a84a | |||
| da69a0f583 | |||
| 37ab1bca90 | |||
| 40757fa7f4 | |||
| e576f7f07d | |||
| 9e804232c2 | |||
| d3dbac6e77 | |||
| 198de02a65 | |||
| 8f64d6a589 | |||
| 1a51d93a54 | |||
| 9ab3b2a5b7 | |||
| 856c879cdf | |||
| ea3f744d93 | |||
| dd3d52e47d | |||
| 32499f0421 | |||
| b136c915df | |||
| 0648d48ba1 | |||
| b97423268c | |||
| 34ec4ff39f | |||
| 018bb14b6a | |||
| 99f5c094d2 | |||
| f23da6a476 |
@@ -1,9 +1,9 @@
|
||||
name: "Build Helper"
|
||||
on: workflow_dispatch
|
||||
env:
|
||||
WAVETERM_VERSION: 0.6.0
|
||||
GO_VERSION: '1.21.5'
|
||||
NODE_VERSION: '21.5.0'
|
||||
WAVETERM_VERSION: 0.6.1
|
||||
GO_VERSION: "1.21.5"
|
||||
NODE_VERSION: "21.5.0"
|
||||
jobs:
|
||||
runbuild-darwin-x64:
|
||||
name: "Build MacOS x64"
|
||||
@@ -12,23 +12,23 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{env.GO_VERSION}}
|
||||
cache-dependency-path: |
|
||||
wavesrv/go.sum
|
||||
waveshell/go.sum
|
||||
go-version: ${{env.GO_VERSION}}
|
||||
cache-dependency-path: |
|
||||
wavesrv/go.sum
|
||||
waveshell/go.sum
|
||||
- run: brew tap scripthaus-dev/scripthaus
|
||||
- run: brew install scripthaus
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
cache: 'yarn'
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
cache: "yarn"
|
||||
- run: yarn --frozen-lockfile
|
||||
- run: scripthaus run build-package
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: waveterm-build-darwin-x64
|
||||
path: out/make/zip/darwin/x64/*.zip
|
||||
retention-days: 2
|
||||
name: waveterm-build-darwin-x64
|
||||
path: out/make/zip/darwin/x64/*.zip
|
||||
retention-days: 2
|
||||
runbuild-darwin-arm64:
|
||||
name: "Build MacOS arm64"
|
||||
runs-on: macos-latest-xlarge
|
||||
@@ -36,23 +36,23 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{env.GO_VERSION}}
|
||||
cache-dependency-path: |
|
||||
wavesrv/go.sum
|
||||
waveshell/go.sum
|
||||
go-version: ${{env.GO_VERSION}}
|
||||
cache-dependency-path: |
|
||||
wavesrv/go.sum
|
||||
waveshell/go.sum
|
||||
- run: brew tap scripthaus-dev/scripthaus
|
||||
- run: brew install scripthaus
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
cache: 'yarn'
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
cache: "yarn"
|
||||
- run: yarn --frozen-lockfile
|
||||
- run: scripthaus run build-package
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: waveterm-build-darwin-arm64
|
||||
path: out/make/zip/darwin/arm64/*.zip
|
||||
retention-days: 2
|
||||
name: waveterm-build-darwin-arm64
|
||||
path: out/make/zip/darwin/arm64/*.zip
|
||||
retention-days: 2
|
||||
runbuild-linux:
|
||||
name: "Build Linux x64"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -61,42 +61,40 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: scripthaus-dev/scripthaus
|
||||
path: scripthaus
|
||||
repository: scripthaus-dev/scripthaus
|
||||
path: scripthaus
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{env.GO_VERSION}}
|
||||
cache-dependency-path: |
|
||||
wavesrv/go.sum
|
||||
waveshell/go.sum
|
||||
scripthaus/go.sum
|
||||
go-version: ${{env.GO_VERSION}}
|
||||
cache-dependency-path: |
|
||||
wavesrv/go.sum
|
||||
waveshell/go.sum
|
||||
scripthaus/go.sum
|
||||
- run: |
|
||||
go work use ./scripthaus;
|
||||
cd scripthaus;
|
||||
go get ./...;
|
||||
CGO_ENABLED=1 go build -o scripthaus cmd/main.go
|
||||
go work use ./scripthaus;
|
||||
cd scripthaus;
|
||||
go get ./...;
|
||||
CGO_ENABLED=1 go build -o scripthaus cmd/main.go
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
cache: 'yarn'
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
cache: "yarn"
|
||||
- run: yarn --frozen-lockfile
|
||||
- run: ./scripthaus/scripthaus run build-package-linux
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
merge-multiple: true
|
||||
path: buildtemp
|
||||
merge-multiple: true
|
||||
path: buildtemp
|
||||
- run: |
|
||||
mv out/make/zip/linux/x64/Wave-linux-x64-$WAVETERM_VERSION.zip buildtemp/waveterm-linux-x64-v$WAVETERM_VERSION.zip
|
||||
mv out/make/zip/linux/x64/Wave-linux-x64-$WAVETERM_VERSION.zip buildtemp/waveterm-linux-x64-v$WAVETERM_VERSION.zip
|
||||
- run: (cd buildtemp; zip ../waveterm-builds.zip *)
|
||||
- run: aws s3 cp waveterm-builds.zip s3://waveterm-github-artifacts/
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.S3_USERID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.S3_SECRETKEY }}"
|
||||
AWS_DEFAULT_REGION: us-west-2
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.S3_USERID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.S3_SECRETKEY }}"
|
||||
AWS_DEFAULT_REGION: us-west-2
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: waveterm-builds
|
||||
path: buildtemp
|
||||
retention-days: 2
|
||||
|
||||
|
||||
name: waveterm-builds
|
||||
path: buildtemp
|
||||
retention-days: 2
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["esbenp.prettier-vscode", "golang.go", "dbaeumer.vscode-eslint"]
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[less]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
@@ -1131,6 +1131,67 @@ Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## github.com/kevinburke/ssh_config
|
||||
|
||||
* Name: github.com/kevinburke/ssh_config
|
||||
* Version: v0.0.0-20240109090616-36c8da3d7376
|
||||
* License: [MIT](https://github.com/wavetermdev/ssh_config/blob/36c8da3d7376/LICENSE)
|
||||
|
||||
```txt
|
||||
Copyright (c) 2017 Kevin Burke.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
===================
|
||||
|
||||
The lexer and parser borrow heavily from github.com/pelletier/go-toml. The
|
||||
license for that project is copied below.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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.6.0
|
||||
VERSION=0.6.1
|
||||
rm -rf temp
|
||||
rm -rf builds
|
||||
mkdir temp
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "waveterm",
|
||||
"author": "Command Line Inc",
|
||||
"productName": "Wave",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"main": "dist/emain.js",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
||||
+16
-12
@@ -99,9 +99,6 @@ body a {
|
||||
|
||||
body code {
|
||||
font-family: @terminal-font;
|
||||
}
|
||||
|
||||
body code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@@ -123,11 +120,19 @@ svg.icon {
|
||||
}
|
||||
|
||||
.hideScrollbarUntillHover {
|
||||
overflow: hidden;
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
overflow: auto;
|
||||
overflow: scroll;
|
||||
|
||||
&::-webkit-scrollbar-thumb,
|
||||
&::-webkit-scrollbar-track {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-corner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,13 +630,13 @@ a.a-block {
|
||||
|
||||
.spin {
|
||||
animation: infiniteRotate 2s linear infinite;
|
||||
|
||||
|
||||
@keyframes infiniteRotate {
|
||||
from {
|
||||
transform:rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform:rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -647,7 +652,6 @@ a.a-block {
|
||||
margin-right: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(241, 246, 243, 0.08);
|
||||
background: rgba(13, 13, 13, 0.85);
|
||||
|
||||
.header {
|
||||
margin: 24px 18px;
|
||||
|
||||
+12
-4
@@ -30,6 +30,7 @@ type OV<V> = mobx.IObservableValue<V>;
|
||||
@mobxReact.observer
|
||||
class App extends React.Component<{}, {}> {
|
||||
dcWait: OV<boolean> = mobx.observable.box(false, { name: "dcWait" });
|
||||
mainContentRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
@@ -75,6 +76,13 @@ class App extends React.Component<{}, {}> {
|
||||
let hasClientStop = GlobalModel.getHasClientStop();
|
||||
let dcWait = this.dcWait.get();
|
||||
let platform = GlobalModel.getPlatform();
|
||||
let clientData = GlobalModel.clientData.get();
|
||||
|
||||
// Previously, this is done in sidebar.tsx but it causes flicker when clientData is null cos screen-view shifts around.
|
||||
// Doing it here fixes the flicker cos app is not rendered until clientData is populated.
|
||||
if (clientData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (disconnected || hasClientStop) {
|
||||
if (!dcWait) {
|
||||
@@ -82,8 +90,8 @@ class App extends React.Component<{}, {}> {
|
||||
}
|
||||
return (
|
||||
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
|
||||
<div className="main-content">
|
||||
<MainSideBar />
|
||||
<div ref={this.mainContentRef} className="main-content">
|
||||
<MainSideBar parentRef={this.mainContentRef} clientData={clientData} />
|
||||
<div className="session-view" />
|
||||
</div>
|
||||
<If condition={dcWait}>
|
||||
@@ -102,8 +110,8 @@ class App extends React.Component<{}, {}> {
|
||||
}
|
||||
return (
|
||||
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
|
||||
<div className="main-content">
|
||||
<MainSideBar />
|
||||
<div ref={this.mainContentRef} className="main-content">
|
||||
<MainSideBar parentRef={this.mainContentRef} clientData={clientData} />
|
||||
<ErrorBoundary>
|
||||
<PluginsView />
|
||||
<WorkspaceView />
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="440" height="440" version="1.1" viewBox="0 0 116.42 116.42" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle id="indicator" cx="58.266" cy="58.266" r="28.311" stroke-width=".19016"/>
|
||||
<path id="spinner" d="m107.2 58.21a48.988 48.988 0 0 1-48.988 48.988" fill="none" stroke="#000" stroke-width="16.856" style="paint-order:normal"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 443 B |
@@ -405,7 +405,7 @@
|
||||
padding: 6px 6px 6px 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
pre.selected {
|
||||
outline: 2px solid @term-green;
|
||||
}
|
||||
@@ -609,7 +609,6 @@
|
||||
|
||||
.wave-dropdown {
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
height: 44px;
|
||||
min-width: 150px;
|
||||
width: 100%;
|
||||
@@ -715,9 +714,7 @@
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 0;
|
||||
margin-top: 2px;
|
||||
padding: 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 6px;
|
||||
@@ -775,7 +772,6 @@
|
||||
min-width: 412px;
|
||||
gap: 6px;
|
||||
border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15));
|
||||
border-radius: 6px;
|
||||
background: var(--element-hover-2, rgba(255, 255, 255, 0.06));
|
||||
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;
|
||||
@@ -931,7 +927,6 @@
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
@@ -1156,18 +1151,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
&.error {
|
||||
color: @term-red;
|
||||
}
|
||||
&.success {
|
||||
color: @term-green;
|
||||
}
|
||||
&.output {
|
||||
color: @term-white;
|
||||
}
|
||||
}
|
||||
|
||||
+177
-43
@@ -9,10 +9,12 @@ import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import cn from "classnames";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { RemoteType, StatusIndicatorLevel } from "../../types/types";
|
||||
import { RemoteType } from "../../types/types";
|
||||
import ReactDOM from "react-dom";
|
||||
import { GlobalModel } from "../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../model/model";
|
||||
import * as appconst from "../appconst";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil";
|
||||
import { MagicLayout } from "../magiclayout";
|
||||
|
||||
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
|
||||
import { ReactComponent as CopyIcon } from "../assets/icons/history/copy.svg";
|
||||
@@ -117,7 +119,7 @@ class Checkbox extends React.Component<
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
checkedInternal: this.props.checked !== undefined ? this.props.checked : Boolean(this.props.defaultChecked),
|
||||
checkedInternal: this.props.checked ?? Boolean(this.props.defaultChecked),
|
||||
};
|
||||
this.generatedId = `checkbox-${Checkbox.idCounter++}`;
|
||||
}
|
||||
@@ -287,15 +289,16 @@ class Button extends React.Component<ButtonProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { leftIcon, rightIcon, theme, children, disabled, variant, color, style } = this.props;
|
||||
const { leftIcon, rightIcon, theme, children, disabled, variant, color, style, autoFocus, className } =
|
||||
this.props;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn("wave-button", theme, variant, color, { disabled: disabled })}
|
||||
className={cn("wave-button", theme, variant, color, { disabled: disabled }, className)}
|
||||
onClick={this.handleClick}
|
||||
disabled={disabled}
|
||||
style={style}
|
||||
autoFocus={this.props.autoFocus}
|
||||
autoFocus={autoFocus}
|
||||
>
|
||||
{leftIcon && <span className="icon-left">{leftIcon}</span>}
|
||||
{children}
|
||||
@@ -714,13 +717,14 @@ class InlineSettingsTextEdit extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
handleKeyDown(e: any): void {
|
||||
if (e.code == "Enter") {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.confirmChange();
|
||||
return;
|
||||
}
|
||||
if (e.code == "Escape") {
|
||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.cancelChange();
|
||||
@@ -868,7 +872,7 @@ class Markdown extends React.Component<
|
||||
if (codeSelect) {
|
||||
return <CodeBlockMarkdown codeSelectSelectedIndex={codeSelectIndex}>{props.children}</CodeBlockMarkdown>;
|
||||
} else {
|
||||
let clickHandler = (e: React.MouseEvent<HTMLElement>) => {
|
||||
const clickHandler = (e: React.MouseEvent<HTMLElement>) => {
|
||||
let blockText = (e.target as HTMLElement).innerText;
|
||||
if (blockText) {
|
||||
blockText = blockText.replace(/\n$/, ""); // remove trailing newline
|
||||
@@ -896,7 +900,9 @@ class Markdown extends React.Component<
|
||||
};
|
||||
return (
|
||||
<div className={cn("markdown content", this.props.extraClassName)} style={this.props.style}>
|
||||
<ReactMarkdown children={text} remarkPlugins={[remarkGfm]} components={markdownComponents} />
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||
{text}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1239,38 +1245,6 @@ class Modal extends React.Component<ModalProps> {
|
||||
}
|
||||
}
|
||||
|
||||
interface StatusIndicatorProps {
|
||||
level: StatusIndicatorLevel;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
class StatusIndicator extends React.Component<StatusIndicatorProps> {
|
||||
render() {
|
||||
const statusIndicatorLevel = this.props.level;
|
||||
let statusIndicator = null;
|
||||
if (statusIndicatorLevel != StatusIndicatorLevel.None) {
|
||||
let statusIndicatorClass = null;
|
||||
switch (statusIndicatorLevel) {
|
||||
case StatusIndicatorLevel.Output:
|
||||
statusIndicatorClass = "output";
|
||||
break;
|
||||
case StatusIndicatorLevel.Success:
|
||||
statusIndicatorClass = "success";
|
||||
break;
|
||||
case StatusIndicatorLevel.Error:
|
||||
statusIndicatorClass = "error";
|
||||
break;
|
||||
}
|
||||
statusIndicator = (
|
||||
<div
|
||||
className={`${this.props.className} fa-sharp fa-solid fa-circle-small status-indicator ${statusIndicatorClass}`}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
return statusIndicator;
|
||||
}
|
||||
}
|
||||
|
||||
function ShowWaveShellInstallPrompt(callbackFn: () => void) {
|
||||
let message: string = `
|
||||
In order to use Wave's advanced features like unified history and persistent sessions, Wave installs a small, open-source helper program called WaveShell on your remote machine. WaveShell does not open any external ports and only communicates with your *local* Wave terminal instance over ssh. For more information please see [the docs](https://docs.waveterm.dev/reference/waveshell).
|
||||
@@ -1292,6 +1266,166 @@ In order to use Wave's advanced features like unified history and persistent ses
|
||||
});
|
||||
}
|
||||
|
||||
interface ResizableSidebarProps {
|
||||
parentRef: React.RefObject<HTMLElement>;
|
||||
position: "left" | "right";
|
||||
enableSnap?: boolean;
|
||||
className?: string;
|
||||
children?: (toggleCollapsed: () => void) => React.ReactNode;
|
||||
toggleCollapse?: () => void;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class ResizableSidebar extends React.Component<ResizableSidebarProps> {
|
||||
resizeStartWidth: number = 0;
|
||||
startX: number = 0;
|
||||
prevDelta: number = 0;
|
||||
prevDragDirection: string = null;
|
||||
disposeReaction: any;
|
||||
|
||||
@boundMethod
|
||||
startResizing(event: React.MouseEvent<HTMLDivElement>) {
|
||||
event.preventDefault();
|
||||
|
||||
const { parentRef, position } = this.props;
|
||||
const parentRect = parentRef.current?.getBoundingClientRect();
|
||||
|
||||
if (!parentRect) return;
|
||||
|
||||
if (position === "right") {
|
||||
this.startX = parentRect.right - event.clientX;
|
||||
} else {
|
||||
this.startX = event.clientX - parentRect.left;
|
||||
}
|
||||
|
||||
const mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||
const collapsed = mainSidebarModel.getCollapsed();
|
||||
|
||||
this.resizeStartWidth = mainSidebarModel.getWidth();
|
||||
document.addEventListener("mousemove", this.onMouseMove);
|
||||
document.addEventListener("mouseup", this.stopResizing);
|
||||
|
||||
document.body.style.cursor = "col-resize";
|
||||
mobx.action(() => {
|
||||
mainSidebarModel.setTempWidthAndTempCollapsed(this.resizeStartWidth, collapsed);
|
||||
mainSidebarModel.isDragging.set(true);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onMouseMove(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
const { parentRef, enableSnap, position } = this.props;
|
||||
const parentRect = parentRef.current?.getBoundingClientRect();
|
||||
const mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||
|
||||
if (!mainSidebarModel.isDragging.get() || !parentRect) return;
|
||||
|
||||
let delta: number, newWidth: number;
|
||||
|
||||
if (position === "right") {
|
||||
delta = parentRect.right - event.clientX - this.startX;
|
||||
} else {
|
||||
delta = event.clientX - parentRect.left - this.startX;
|
||||
}
|
||||
|
||||
newWidth = this.resizeStartWidth + delta;
|
||||
|
||||
if (enableSnap) {
|
||||
const minWidth = MagicLayout.MainSidebarMinWidth;
|
||||
const snapPoint = minWidth + MagicLayout.MainSidebarSnapThreshold;
|
||||
const dragResistance = MagicLayout.MainSidebarDragResistance;
|
||||
let dragDirection: string;
|
||||
|
||||
if (delta - this.prevDelta > 0) {
|
||||
dragDirection = "+";
|
||||
} else if (delta - this.prevDelta == 0) {
|
||||
if (this.prevDragDirection == "+") {
|
||||
dragDirection = "+";
|
||||
} else {
|
||||
dragDirection = "-";
|
||||
}
|
||||
} else {
|
||||
dragDirection = "-";
|
||||
}
|
||||
|
||||
this.prevDelta = delta;
|
||||
this.prevDragDirection = dragDirection;
|
||||
|
||||
if (newWidth - dragResistance > minWidth && newWidth < snapPoint && dragDirection == "+") {
|
||||
newWidth = snapPoint;
|
||||
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false);
|
||||
} else if (newWidth + dragResistance < snapPoint && dragDirection == "-") {
|
||||
newWidth = minWidth;
|
||||
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true);
|
||||
} else if (newWidth > snapPoint) {
|
||||
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false);
|
||||
}
|
||||
} else {
|
||||
if (newWidth <= MagicLayout.MainSidebarMinWidth) {
|
||||
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, true);
|
||||
} else {
|
||||
mainSidebarModel.setTempWidthAndTempCollapsed(newWidth, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
stopResizing() {
|
||||
let mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||
|
||||
GlobalCommandRunner.clientSetSidebar(
|
||||
mainSidebarModel.tempWidth.get(),
|
||||
mainSidebarModel.tempCollapsed.get()
|
||||
).finally(() => {
|
||||
mobx.action(() => {
|
||||
mainSidebarModel.isDragging.set(false);
|
||||
})();
|
||||
});
|
||||
|
||||
document.removeEventListener("mousemove", this.onMouseMove);
|
||||
document.removeEventListener("mouseup", this.stopResizing);
|
||||
document.body.style.cursor = "";
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
toggleCollapsed() {
|
||||
const mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||
|
||||
const tempCollapsed = mainSidebarModel.getCollapsed();
|
||||
const width = mainSidebarModel.getWidth(true);
|
||||
mainSidebarModel.setTempWidthAndTempCollapsed(width, !tempCollapsed);
|
||||
GlobalCommandRunner.clientSetSidebar(width, !tempCollapsed);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, children } = this.props;
|
||||
const mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||
const width = mainSidebarModel.getWidth();
|
||||
const isCollapsed = mainSidebarModel.getCollapsed();
|
||||
|
||||
return (
|
||||
<div className={cn("sidebar", className, { collapsed: isCollapsed })} style={{ width }}>
|
||||
<div className="sidebar-content">{children(this.toggleCollapsed)}</div>
|
||||
<div
|
||||
className="sidebar-handle"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
[this.props.position === "left" ? "right" : "left"]: 0,
|
||||
bottom: 0,
|
||||
width: "5px",
|
||||
cursor: "col-resize",
|
||||
}}
|
||||
onMouseDown={this.startResizing}
|
||||
onDoubleClick={this.toggleCollapsed}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
CmdStrCode,
|
||||
Toggle,
|
||||
@@ -1313,6 +1447,6 @@ export {
|
||||
LinkButton,
|
||||
Status,
|
||||
Modal,
|
||||
StatusIndicator,
|
||||
ResizableSidebar,
|
||||
ShowWaveShellInstallPrompt,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
@import "../../../app/common/themes/themes.less";
|
||||
|
||||
.front-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.positional-icon-visible {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.positional-icon-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.positional-icon {
|
||||
display: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
.svg-icon svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.positional-icon-inner {
|
||||
display: flex;
|
||||
width: inherit;
|
||||
height: 100%;
|
||||
line-height: inherit;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
& > div,
|
||||
i,
|
||||
.svg-icon,
|
||||
span {
|
||||
width: 20px;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
.icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The following accounts for a debounce in the status indicator. We will only display the status indicator icon if the parent indicates that it should be visible AND one of the following is true:
|
||||
1. There is a status to be shown.
|
||||
2. There is a spinner to be shown and the required delay has passed.
|
||||
*/
|
||||
.status-indicator-visible {
|
||||
&.spinner-visible,
|
||||
&.output,
|
||||
&.error,
|
||||
&.success {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
|
||||
// This is set by the timeout in the status indicator component.
|
||||
&.spinner-visible {
|
||||
#spinner {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
#spinner,
|
||||
#indicator {
|
||||
visibility: hidden;
|
||||
}
|
||||
.spin #spinner {
|
||||
stroke: @term-white;
|
||||
}
|
||||
&.error #indicator {
|
||||
visibility: visible;
|
||||
fill: @term-red;
|
||||
}
|
||||
&.success #indicator {
|
||||
visibility: visible;
|
||||
fill: @term-green;
|
||||
}
|
||||
&.output #indicator {
|
||||
visibility: visible;
|
||||
fill: @term-white;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import React from "react";
|
||||
import { StatusIndicatorLevel } from "../../../types/types";
|
||||
import cn from "classnames";
|
||||
import { ReactComponent as SpinnerIndicator } from "../../assets/icons/spinner-indicator.svg";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
|
||||
import { ReactComponent as RotateIconSvg } from "../../assets/icons/line/rotate.svg";
|
||||
|
||||
interface PositionalIconProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
divRef?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export class FrontIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
ref={this.props.divRef}
|
||||
className={cn("front-icon", "positional-icon", this.props.className)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<div className="positional-icon-inner">{this.props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CenteredIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
ref={this.props.divRef}
|
||||
className={cn("centered-icon", "positional-icon", this.props.className)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<div className="positional-icon-inner">{this.props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface ActionsIconProps {
|
||||
onClick: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export class ActionsIcon extends React.Component<ActionsIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<CenteredIcon className="actions" onClick={this.props.onClick}>
|
||||
<div className="icon hoverEffect fa-sharp fa-solid fa-1x fa-ellipsis-vertical"></div>
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SyncSpin extends React.Component<{
|
||||
classRef?: React.RefObject<HTMLDivElement>;
|
||||
children?: React.ReactNode;
|
||||
shouldSync?: boolean;
|
||||
}> {
|
||||
listenerAdded: boolean = false;
|
||||
|
||||
componentDidMount() {
|
||||
this.syncSpinner();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.syncSpinner();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
const classRef = this.props.classRef;
|
||||
if (classRef.current != null && this.listenerAdded) {
|
||||
const elem = classRef.current;
|
||||
const svgElem = elem.querySelector("svg");
|
||||
if (svgElem != null) {
|
||||
svgElem.removeEventListener("animationstart", this.handleAnimationStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleAnimationStart(e: AnimationEvent) {
|
||||
const classRef = this.props.classRef;
|
||||
if (classRef.current == null) {
|
||||
return;
|
||||
}
|
||||
const svgElem = classRef.current.querySelector("svg");
|
||||
if (svgElem == null) {
|
||||
return;
|
||||
}
|
||||
const animArr = svgElem.getAnimations();
|
||||
if (animArr == null || animArr.length == 0) {
|
||||
return;
|
||||
}
|
||||
animArr[0].startTime = 0;
|
||||
}
|
||||
|
||||
syncSpinner() {
|
||||
const { classRef, shouldSync } = this.props;
|
||||
const shouldSyncVal = shouldSync ?? true;
|
||||
if (!shouldSyncVal || classRef.current == null) {
|
||||
return;
|
||||
}
|
||||
const elem = classRef.current;
|
||||
const svgElem = elem.querySelector("svg");
|
||||
if (svgElem == null) {
|
||||
return;
|
||||
}
|
||||
if (!this.listenerAdded) {
|
||||
svgElem.addEventListener("animationstart", this.handleAnimationStart);
|
||||
this.listenerAdded = true;
|
||||
}
|
||||
const animArr = svgElem.getAnimations();
|
||||
if (animArr == null || animArr.length == 0) {
|
||||
return;
|
||||
}
|
||||
animArr[0].startTime = 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
interface StatusIndicatorProps {
|
||||
/**
|
||||
* The level of the status indicator. This will determine the color of the status indicator.
|
||||
*/
|
||||
level: StatusIndicatorLevel;
|
||||
className?: string;
|
||||
/**
|
||||
* If true, a spinner will be shown around the status indicator.
|
||||
*/
|
||||
runningCommands?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This component is used to show the status of a command. It will show a spinner around the status indicator if there are running commands. It will also delay showing the spinner for a short time to prevent flickering.
|
||||
*/
|
||||
@mobxReact.observer
|
||||
export class StatusIndicator extends React.Component<StatusIndicatorProps> {
|
||||
iconRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
spinnerVisible: mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
timeout: NodeJS.Timeout;
|
||||
|
||||
clearSpinnerTimeout() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.spinnerVisible.set(false);
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will apply a delay after there is a running command before showing the spinner. This prevents flickering for commands that return quickly.
|
||||
*/
|
||||
updateMountCallback() {
|
||||
const runningCommands = this.props.runningCommands ?? false;
|
||||
if (runningCommands && !this.timeout) {
|
||||
this.timeout = setTimeout(
|
||||
mobx.action(() => {
|
||||
this.spinnerVisible.set(true);
|
||||
}),
|
||||
100
|
||||
);
|
||||
} else if (!runningCommands) {
|
||||
this.clearSpinnerTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(): void {
|
||||
this.updateMountCallback();
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.updateMountCallback();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.clearSpinnerTimeout();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { level, className, runningCommands } = this.props;
|
||||
const spinnerVisible = this.spinnerVisible.get();
|
||||
let statusIndicator = null;
|
||||
if (level != StatusIndicatorLevel.None || spinnerVisible) {
|
||||
let indicatorLevelClass = null;
|
||||
switch (level) {
|
||||
case StatusIndicatorLevel.Output:
|
||||
indicatorLevelClass = "output";
|
||||
break;
|
||||
case StatusIndicatorLevel.Success:
|
||||
indicatorLevelClass = "success";
|
||||
break;
|
||||
case StatusIndicatorLevel.Error:
|
||||
indicatorLevelClass = "error";
|
||||
break;
|
||||
}
|
||||
|
||||
const spinnerVisibleClass = spinnerVisible ? "spinner-visible" : null;
|
||||
statusIndicator = (
|
||||
<CenteredIcon
|
||||
divRef={this.iconRef}
|
||||
className={cn(className, indicatorLevelClass, spinnerVisibleClass, "status-indicator")}
|
||||
>
|
||||
<SpinnerIndicator className={spinnerVisible ? "spin" : null} />
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<SyncSpin classRef={this.iconRef} shouldSync={runningCommands}>
|
||||
{statusIndicator}
|
||||
</SyncSpin>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class RotateIcon extends React.Component<{
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}> {
|
||||
iconRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
render() {
|
||||
return (
|
||||
<SyncSpin classRef={this.iconRef}>
|
||||
<RotateIconSvg className={this.props.className ?? ""} />
|
||||
</SyncSpin>
|
||||
);
|
||||
}
|
||||
}
|
||||
+49
-24
@@ -15,6 +15,7 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import { Line } from "../line/linecomps";
|
||||
import { CmdStrCode } from "../common/common";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil";
|
||||
|
||||
import { ReactComponent as FavoritesIcon } from "../assets/icons/favourites.svg";
|
||||
import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg";
|
||||
@@ -95,14 +96,14 @@ function formatSessionName(snames: Record<string, string>, sessionId: string): s
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class HistoryCheckbox extends React.Component<{ checked: boolean, partialCheck?: boolean, onClick?: () => void }, {}> {
|
||||
class HistoryCheckbox extends React.Component<{ checked: boolean; partialCheck?: boolean; onClick?: () => void }, {}> {
|
||||
@boundMethod
|
||||
clickHandler(): void {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (this.props.checked) {
|
||||
return <CheckedCheckbox onClick={this.clickHandler} className="history-checkbox checkbox-icon" />;
|
||||
@@ -110,14 +111,18 @@ class HistoryCheckbox extends React.Component<{ checked: boolean, partialCheck?:
|
||||
if (this.props.partialCheck) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<rect x="0.5" y="0.5" width="15" height="15" rx="3.5" fill="#D5FEAF" fill-opacity="0.026"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 8C4 6.89543 4.89543 6 6 6H10C11.1046 6 12 6.89543 12 8C12 9.10457 11.1046 10 10 10H6C4.89543 10 4 9.10457 4 8Z" fill="#58C142"/>
|
||||
<rect x="0.5" y="0.5" width="15" height="15" rx="3.5" stroke="#3B3F3A"/>
|
||||
<rect x="0.5" y="0.5" width="15" height="15" rx="3.5" fill="#D5FEAF" fill-opacity="0.026" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M4 8C4 6.89543 4.89543 6 6 6H10C11.1046 6 12 6.89543 12 8C12 9.10457 11.1046 10 10 10H6C4.89543 10 4 9.10457 4 8Z"
|
||||
fill="#58C142"
|
||||
/>
|
||||
<rect x="0.5" y="0.5" width="15" height="15" rx="3.5" stroke="#3B3F3A" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return <div onClick={this.clickHandler} className="history-checkbox state-unchecked"/>
|
||||
);
|
||||
} else {
|
||||
return <div onClick={this.clickHandler} className="history-checkbox state-unchecked" />;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,7 +212,8 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
|
||||
@boundMethod
|
||||
searchKeyDown(e: any) {
|
||||
if (e.code == "Enter") {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
e.preventDefault();
|
||||
GlobalModel.historyViewModel.submitSearch();
|
||||
return;
|
||||
@@ -452,8 +458,8 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
<div onClick={this.toggleSessionDropdown}>
|
||||
<span className="label">
|
||||
{hvm.searchSessionId.get() == null
|
||||
? "Limit Workspace"
|
||||
: formatSessionName(snames, hvm.searchSessionId.get())}
|
||||
? "Limit Workspace"
|
||||
: formatSessionName(snames, hvm.searchSessionId.get())}
|
||||
</span>
|
||||
<AngleDownIcon className="icon" />
|
||||
</div>
|
||||
@@ -486,8 +492,8 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
<div onClick={this.toggleRemoteDropdown}>
|
||||
<span className="label">
|
||||
{hvm.searchRemoteId.get() == null
|
||||
? "Limit Remote"
|
||||
: formatRemoteName(rnames, { remoteid: hvm.searchRemoteId.get() })}
|
||||
? "Limit Remote"
|
||||
: formatRemoteName(rnames, { remoteid: hvm.searchRemoteId.get() })}
|
||||
</span>
|
||||
<AngleDownIcon className="icon" />
|
||||
</div>
|
||||
@@ -513,9 +519,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
</div>
|
||||
</div>
|
||||
<div className="fromts">
|
||||
<div className="fromts-text">
|
||||
From:
|
||||
</div>
|
||||
<div className="fromts-text">From: </div>
|
||||
<div className="hoverEffect">
|
||||
<input
|
||||
type="date"
|
||||
@@ -550,7 +554,10 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
</div>
|
||||
<div className={cn("control-bar", "is-top", { "is-hidden": items.length == 0 })}>
|
||||
<div className="control-checkbox" onClick={this.handleControlCheckbox} title="Toggle Selection">
|
||||
<HistoryCheckbox checked={numSelected > 0 && numSelected == items.length} partialCheck={numSelected > 0}/>
|
||||
<HistoryCheckbox
|
||||
checked={numSelected > 0 && numSelected == items.length}
|
||||
partialCheck={numSelected > 0}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -562,7 +569,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
>
|
||||
<span>
|
||||
<TrashIcon className="trash-icon" title="Purge Selected Items" />
|
||||
Delete Items
|
||||
Delete Items
|
||||
</span>
|
||||
</div>
|
||||
<div className="spacer" />
|
||||
@@ -591,7 +598,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
className={cn("history-item", { "is-selected": hvm.selectedItems.get(item.historyid) })}
|
||||
>
|
||||
<td className="selectbox" onClick={() => this.handleSelect(item.historyid)}>
|
||||
<HistoryCheckbox checked={hvm.selectedItems.get(item.historyid)}/>
|
||||
<HistoryCheckbox checked={hvm.selectedItems.get(item.historyid)} />
|
||||
</td>
|
||||
<td className="cmdstr">
|
||||
<HistoryCmdStr
|
||||
@@ -608,13 +615,31 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
<td className="ts text-standard">{getHistoryViewTs(nowDate, item.ts)}</td>
|
||||
<td className="downarrow" onClick={() => this.activateItem(item.historyid)}>
|
||||
<If condition={activeItemId != item.historyid}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M12.1297 6.62492C12.3999 6.93881 12.3645 7.41237 12.0506 7.68263L8.48447 10.7531C8.20296 10.9955 7.78645 10.9952 7.50519 10.7526L3.94636 7.68213C3.63274 7.41155 3.59785 6.93796 3.86843 6.62434C4.13901 6.31072 4.6126 6.27583 4.92622 6.54641L7.99562 9.19459L11.0719 6.54591C11.3858 6.27565 11.8594 6.31102 12.1297 6.62492Z" fill="#C3C8C2"/>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M12.1297 6.62492C12.3999 6.93881 12.3645 7.41237 12.0506 7.68263L8.48447 10.7531C8.20296 10.9955 7.78645 10.9952 7.50519 10.7526L3.94636 7.68213C3.63274 7.41155 3.59785 6.93796 3.86843 6.62434C4.13901 6.31072 4.6126 6.27583 4.92622 6.54641L7.99562 9.19459L11.0719 6.54591C11.3858 6.27565 11.8594 6.31102 12.1297 6.62492Z"
|
||||
fill="#C3C8C2"
|
||||
/>
|
||||
</svg>
|
||||
</If>
|
||||
<If condition={activeItemId == item.historyid}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M3.87035 9.37508C3.60009 9.06119 3.63546 8.58763 3.94936 8.31737L7.51553 5.24692C7.79704 5.00455 8.21355 5.00476 8.49481 5.24742L12.0536 8.31787C12.3673 8.58845 12.4022 9.06204 12.1316 9.37566C11.861 9.68928 11.3874 9.72417 11.0738 9.45359L8.00438 6.80541L4.92806 9.45409C4.61416 9.72435 4.14061 9.68898 3.87035 9.37508Z" fill="#C3C8C2"/>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M3.87035 9.37508C3.60009 9.06119 3.63546 8.58763 3.94936 8.31737L7.51553 5.24692C7.79704 5.00455 8.21355 5.00476 8.49481 5.24742L12.0536 8.31787C12.3673 8.58845 12.4022 9.06204 12.1316 9.37566C11.861 9.68928 11.3874 9.72417 11.0738 9.45359L8.00438 6.80541L4.92806 9.45409C4.61416 9.72435 4.14061 9.68898 3.87035 9.37508Z"
|
||||
fill="#C3C8C2"
|
||||
/>
|
||||
</svg>
|
||||
</If>
|
||||
</td>
|
||||
|
||||
+96
-86
@@ -13,8 +13,6 @@ import { GlobalModel, GlobalCommandRunner, Cmd, getTermPtyData } from "../../mod
|
||||
import { termHeightFromRows } from "../../util/textmeasure";
|
||||
import type {
|
||||
LineType,
|
||||
RemoteType,
|
||||
RemotePtrType,
|
||||
RenderModeType,
|
||||
RendererOpts,
|
||||
RendererPluginType,
|
||||
@@ -24,11 +22,6 @@ import type {
|
||||
} from "../../types/types";
|
||||
import cn from "classnames";
|
||||
|
||||
import { ReactComponent as FavoritesIcon } from "../assets/icons/favourites.svg";
|
||||
import { ReactComponent as PinIcon } from "../assets/icons/pin.svg";
|
||||
import { ReactComponent as PlusIcon } from "../assets/icons/plus.svg";
|
||||
import { ReactComponent as MinusIcon } from "../assets/icons/minus.svg";
|
||||
|
||||
import type { LineContainerModel } from "../../model/model";
|
||||
import { renderCmdText } from "../common/common";
|
||||
import { SimpleBlobRenderer } from "../../plugins/core/basicrenderer";
|
||||
@@ -44,12 +37,13 @@ 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";
|
||||
import { ReactComponent as QuestionIcon } from "../assets/icons/line/question.svg";
|
||||
import { ReactComponent as RotateIcon } from "../assets/icons/line/rotate.svg";
|
||||
import { ReactComponent as WarningIcon } from "../assets/icons/line/triangle-exclamation.svg";
|
||||
import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg";
|
||||
import { ReactComponent as FillIcon } from "../assets/icons/line/fill.svg";
|
||||
import { ReactComponent as GearIcon } from "../assets/icons/line/gear.svg";
|
||||
|
||||
import { RotateIcon } from "../common/icons/icons";
|
||||
|
||||
import "./lines.less";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
@@ -59,12 +53,12 @@ type OV<V> = mobx.IObservableValue<V>;
|
||||
@mobxReact.observer
|
||||
class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRightClick?: (e: any) => void }, {}> {
|
||||
render() {
|
||||
let { line, cmd } = this.props;
|
||||
let lineNumStr = (line.linenumtemp ? "~" : "#") + String(line.linenum);
|
||||
let status = cmd != null ? cmd.getStatus() : "done";
|
||||
let rtnstate = cmd != null ? cmd.getRtnState() : false;
|
||||
let exitcode = cmd != null ? cmd.getExitCode() : 0;
|
||||
let isComment = line.linetype == "text";
|
||||
const { line, cmd } = this.props;
|
||||
const lineNumStr = (line.linenumtemp ? "~" : "#") + String(line.linenum);
|
||||
const status = cmd != null ? cmd.getStatus() : "done";
|
||||
const rtnstate = cmd != null ? cmd.getRtnState() : false;
|
||||
const exitcode = cmd != null ? cmd.getExitCode() : 0;
|
||||
const isComment = line.linetype == "text";
|
||||
let icon = null;
|
||||
let iconTitle = null;
|
||||
if (isComment) {
|
||||
@@ -142,7 +136,7 @@ class LineCmd extends React.Component<
|
||||
}
|
||||
|
||||
checkStateDiffLoad(): void {
|
||||
let { screen, line, staticRender, visible } = this.props;
|
||||
const { screen, line, staticRender, visible } = this.props;
|
||||
if (staticRender) {
|
||||
return;
|
||||
}
|
||||
@@ -153,7 +147,7 @@ class LineCmd extends React.Component<
|
||||
}
|
||||
return;
|
||||
}
|
||||
let cmd = screen.getCmd(line);
|
||||
const cmd = screen.getCmd(line);
|
||||
if (cmd == null || !cmd.getRtnState() || this.rtnStateDiffFetched) {
|
||||
return;
|
||||
}
|
||||
@@ -167,15 +161,15 @@ class LineCmd extends React.Component<
|
||||
if (this.rtnStateDiffFetched) {
|
||||
return;
|
||||
}
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
this.rtnStateDiffFetched = true;
|
||||
let usp = new URLSearchParams({
|
||||
const usp = new URLSearchParams({
|
||||
linenum: String(line.linenum),
|
||||
screenid: line.screenid,
|
||||
lineid: line.lineid,
|
||||
});
|
||||
let url = GlobalModel.getBaseHostPort() + "/api/rtnstate?" + usp.toString();
|
||||
let fetchHeaders = GlobalModel.getFetchHeaders();
|
||||
const url = GlobalModel.getBaseHostPort() + "/api/rtnstate?" + usp.toString();
|
||||
const fetchHeaders = GlobalModel.getFetchHeaders();
|
||||
fetch(url, { headers: fetchHeaders })
|
||||
.then((resp) => {
|
||||
if (!resp.ok) {
|
||||
@@ -233,7 +227,7 @@ class LineCmd extends React.Component<
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
let isMultiLine = lineutil.isMultiLineCmdText(cmd.getCmdStr());
|
||||
const isMultiLine = lineutil.isMultiLineCmdText(cmd.getCmdStr());
|
||||
return (
|
||||
<div key="meta2" className="meta meta-line2" ref={this.cmdTextRef}>
|
||||
<div className="metapart-mono cmdtext">
|
||||
@@ -252,7 +246,7 @@ class LineCmd extends React.Component<
|
||||
|
||||
// TODO: this might not be necessary anymore because we're using this.lastHeight
|
||||
getSnapshotBeforeUpdate(prevProps, prevState): { height: number } {
|
||||
let elem = this.lineRef.current;
|
||||
const elem = this.lineRef.current;
|
||||
if (elem == null) {
|
||||
return { height: 0 };
|
||||
}
|
||||
@@ -266,25 +260,25 @@ class LineCmd extends React.Component<
|
||||
}
|
||||
|
||||
checkCmdText() {
|
||||
let metaElem = this.cmdTextRef.current;
|
||||
const metaElem = this.cmdTextRef.current;
|
||||
if (metaElem == null || metaElem.childNodes.length == 0) {
|
||||
return;
|
||||
}
|
||||
let metaElemWidth = metaElem.offsetWidth;
|
||||
const metaElemWidth = metaElem.offsetWidth;
|
||||
if (metaElemWidth == 0) {
|
||||
return;
|
||||
}
|
||||
let metaChild = metaElem.firstChild;
|
||||
const metaChild = metaElem.firstChild;
|
||||
if (metaChild == null) {
|
||||
return;
|
||||
}
|
||||
let children = metaChild.childNodes;
|
||||
const children = metaChild.childNodes;
|
||||
let childWidth = 0;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let ch = children[i];
|
||||
childWidth += ch.offsetWidth;
|
||||
}
|
||||
let isOverflow = childWidth > metaElemWidth;
|
||||
const isOverflow = childWidth > metaElemWidth;
|
||||
if (isOverflow && isOverflow != this.isOverflow.get()) {
|
||||
mobx.action(() => {
|
||||
this.isOverflow.set(isOverflow);
|
||||
@@ -297,16 +291,16 @@ class LineCmd extends React.Component<
|
||||
if (this.props.onHeightChange == null) {
|
||||
return;
|
||||
}
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
let curHeight = 0;
|
||||
let elem = this.lineRef.current;
|
||||
const elem = this.lineRef.current;
|
||||
if (elem != null) {
|
||||
curHeight = elem.offsetHeight;
|
||||
}
|
||||
if (this.lastHeight == curHeight) {
|
||||
return;
|
||||
}
|
||||
let lastHeight = this.lastHeight;
|
||||
const lastHeight = this.lastHeight;
|
||||
this.lastHeight = curHeight;
|
||||
this.props.onHeightChange(line.linenum, curHeight, lastHeight);
|
||||
// console.log("line height change: ", line.linenum, lastHeight, "=>", curHeight);
|
||||
@@ -314,13 +308,13 @@ class LineCmd extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
handleClick() {
|
||||
let { line, noSelect } = this.props;
|
||||
const { line, noSelect } = this.props;
|
||||
if (noSelect) {
|
||||
return;
|
||||
}
|
||||
let sel = window.getSelection();
|
||||
const sel = window.getSelection();
|
||||
if (this.lineRef.current != null) {
|
||||
let selText = sel.toString();
|
||||
const selText = sel.toString();
|
||||
if (sel.anchorNode != null && this.lineRef.current.contains(sel.anchorNode) && !isBlank(selText)) {
|
||||
return;
|
||||
}
|
||||
@@ -330,7 +324,7 @@ class LineCmd extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
clickStar() {
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
if (!line.star || line.star == 0) {
|
||||
GlobalCommandRunner.lineStar(line.lineid, 1);
|
||||
} else {
|
||||
@@ -340,7 +334,7 @@ class LineCmd extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
clickPin() {
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
if (!line.pinned) {
|
||||
GlobalCommandRunner.linePin(line.lineid, true);
|
||||
} else {
|
||||
@@ -350,16 +344,22 @@ class LineCmd extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
clickBookmark() {
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
GlobalCommandRunner.lineBookmark(line.lineid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickDelete() {
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
GlobalCommandRunner.lineDelete(line.lineid, true);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickRestart() {
|
||||
const { line } = this.props;
|
||||
GlobalCommandRunner.lineRestart(line.lineid, true);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickMinimize() {
|
||||
mobx.action(() => {
|
||||
@@ -369,7 +369,7 @@ class LineCmd extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
clickMoveToSidebar() {
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
GlobalCommandRunner.screenSidebarAddLine(line.lineid);
|
||||
}
|
||||
|
||||
@@ -384,20 +384,20 @@ class LineCmd extends React.Component<
|
||||
}
|
||||
|
||||
getIsHidePrompt(): boolean {
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
let rendererPlugin: RendererPluginType = null;
|
||||
let isNoneRenderer = line.renderer == "none";
|
||||
const isNoneRenderer = line.renderer == "none";
|
||||
if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) {
|
||||
rendererPlugin = PluginModel.getRendererPluginByName(line.renderer);
|
||||
}
|
||||
let hidePrompt = rendererPlugin != null && rendererPlugin.hidePrompt;
|
||||
const hidePrompt = rendererPlugin?.hidePrompt;
|
||||
return hidePrompt;
|
||||
}
|
||||
|
||||
getTerminalRendererHeight(cmd: Cmd): number {
|
||||
let { screen, line, width, renderMode } = this.props;
|
||||
const { screen, line, width } = this.props;
|
||||
let height = 45 + 24; // height of zero height terminal
|
||||
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
|
||||
const usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
|
||||
if (usedRows > 0) {
|
||||
height = 48 + 24 + termHeightFromRows(usedRows, GlobalModel.termFontSize.get());
|
||||
}
|
||||
@@ -406,7 +406,7 @@ class LineCmd extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
onAvatarRightClick(e: any): void {
|
||||
let { line, noSelect } = this.props;
|
||||
const { line, noSelect } = this.props;
|
||||
if (noSelect) {
|
||||
return;
|
||||
}
|
||||
@@ -420,20 +420,20 @@ class LineCmd extends React.Component<
|
||||
}
|
||||
|
||||
renderSimple() {
|
||||
let { screen, line } = this.props;
|
||||
let cmd = screen.getCmd(line);
|
||||
const { screen, line } = this.props;
|
||||
const cmd = screen.getCmd(line);
|
||||
let height: number = 0;
|
||||
if (isBlank(line.renderer) || line.renderer == "terminal") {
|
||||
height = this.getTerminalRendererHeight(cmd);
|
||||
} else {
|
||||
// header is 16px tall with hide-prompt, 36px otherwise
|
||||
let { screen, line, width } = this.props;
|
||||
let hidePrompt = this.getIsHidePrompt();
|
||||
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
|
||||
const { screen, line, width } = this.props;
|
||||
const hidePrompt = this.getIsHidePrompt();
|
||||
const usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
|
||||
height = (hidePrompt ? 16 + 6 : 36 + 6) + usedRows;
|
||||
}
|
||||
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
let mainDivCn = cn("line", "line-cmd", "line-simple");
|
||||
const formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
const mainDivCn = cn("line", "line-cmd", "line-simple");
|
||||
return (
|
||||
<div
|
||||
className={mainDivCn}
|
||||
@@ -467,12 +467,22 @@ class LineCmd extends React.Component<
|
||||
renderMeta1(cmd: Cmd) {
|
||||
let { line } = this.props;
|
||||
let termOpts = cmd.getTermOpts();
|
||||
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
let formattedTime: string = "";
|
||||
let restartTs = cmd.getRestartTs();
|
||||
let timeTitle: string = null;
|
||||
if (restartTs != null && restartTs > 0) {
|
||||
formattedTime = "restarted @ " + lineutil.getLineDateTimeStr(restartTs);
|
||||
timeTitle = "original start time " + lineutil.getLineDateTimeStr(line.ts);
|
||||
} else {
|
||||
formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
}
|
||||
let renderer = line.renderer;
|
||||
return (
|
||||
<div key="meta1" className="meta meta-line1">
|
||||
<SmallLineAvatar line={line} cmd={cmd} />
|
||||
<div className="ts">{formattedTime}</div>
|
||||
<div title={timeTitle} className="ts">
|
||||
{formattedTime}
|
||||
</div>
|
||||
<div> </div>
|
||||
<If condition={!isBlank(renderer) && renderer != "terminal"}>
|
||||
<div className="renderer">
|
||||
@@ -491,7 +501,7 @@ class LineCmd extends React.Component<
|
||||
}
|
||||
|
||||
getRendererOpts(cmd: Cmd): RendererOpts {
|
||||
let { screen } = this.props;
|
||||
const { screen } = this.props;
|
||||
return {
|
||||
maxSize: screen.getMaxContentSize(),
|
||||
idealSize: screen.getIdealContentSize(),
|
||||
@@ -501,9 +511,9 @@ class LineCmd extends React.Component<
|
||||
}
|
||||
|
||||
makeRendererModelInitializeParams(): RendererModelInitializeParams {
|
||||
let { screen, line } = this.props;
|
||||
let context = lineutil.getRendererContext(line);
|
||||
let cmd = screen.getCmd(line); // won't be null
|
||||
const { screen, line } = this.props;
|
||||
const context = lineutil.getRendererContext(line);
|
||||
const cmd = screen.getCmd(line); // won't be null
|
||||
let savedHeight = screen.getContentHeight(context);
|
||||
if (savedHeight == null) {
|
||||
if (line.contentheight != null && line.contentheight != -1) {
|
||||
@@ -512,7 +522,7 @@ class LineCmd extends React.Component<
|
||||
savedHeight = 0;
|
||||
}
|
||||
}
|
||||
let api = {
|
||||
const api = {
|
||||
saveHeight: (height: number) => {
|
||||
screen.setContentHeight(lineutil.getRendererContext(line), height);
|
||||
},
|
||||
@@ -566,12 +576,12 @@ class LineCmd extends React.Component<
|
||||
};
|
||||
|
||||
render() {
|
||||
let { screen, line, width, staticRender, visible } = this.props;
|
||||
let isVisible = visible.get();
|
||||
const { screen, line, width, staticRender, visible } = this.props;
|
||||
const isVisible = visible.get();
|
||||
if (staticRender || !isVisible) {
|
||||
return this.renderSimple();
|
||||
}
|
||||
let cmd = screen.getCmd(line);
|
||||
const cmd = screen.getCmd(line);
|
||||
if (cmd == null) {
|
||||
return (
|
||||
<div
|
||||
@@ -585,19 +595,17 @@ class LineCmd extends React.Component<
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let status = cmd.getStatus();
|
||||
let lineNumStr = (line.linenumtemp ? "~" : "") + String(line.linenum);
|
||||
let isSelected = mobx
|
||||
const isSelected = mobx
|
||||
.computed(() => screen.getSelectedLine() == line.linenum, {
|
||||
name: "computed-isSelected",
|
||||
})
|
||||
.get();
|
||||
let isPhysicalFocused = mobx
|
||||
const isPhysicalFocused = mobx
|
||||
.computed(() => screen.getIsFocused(line.linenum), {
|
||||
name: "computed-getIsFocused",
|
||||
})
|
||||
.get();
|
||||
let isFocused = mobx
|
||||
const isFocused = mobx
|
||||
.computed(
|
||||
() => {
|
||||
let screenFocusType = screen.getFocusType();
|
||||
@@ -606,7 +614,7 @@ class LineCmd extends React.Component<
|
||||
{ name: "computed-isFocused" }
|
||||
)
|
||||
.get();
|
||||
let shouldCmdFocus = mobx
|
||||
const shouldCmdFocus = mobx
|
||||
.computed(
|
||||
() => {
|
||||
let screenFocusType = screen.getFocusType();
|
||||
@@ -615,7 +623,7 @@ class LineCmd extends React.Component<
|
||||
{ name: "computed-shouldCmdFocus" }
|
||||
)
|
||||
.get();
|
||||
let isInSidebar = mobx
|
||||
const isInSidebar = mobx
|
||||
.computed(
|
||||
() => {
|
||||
return screen.isSidebarOpen() && screen.isLineIdInSidebar(line.lineid);
|
||||
@@ -623,11 +631,10 @@ class LineCmd extends React.Component<
|
||||
{ name: "computed-isInSidebar" }
|
||||
)
|
||||
.get();
|
||||
let isStatic = staticRender;
|
||||
let isRunning = cmd.isRunning();
|
||||
let isExpanded = this.isCmdExpanded.get();
|
||||
let rsdiff = this.rtnStateDiff.get();
|
||||
let mainDivCn = cn(
|
||||
const isRunning = cmd.isRunning();
|
||||
const isExpanded = this.isCmdExpanded.get();
|
||||
const rsdiff = this.rtnStateDiff.get();
|
||||
const mainDivCn = cn(
|
||||
"line",
|
||||
"line-cmd",
|
||||
{ selected: isSelected },
|
||||
@@ -636,18 +643,18 @@ class LineCmd extends React.Component<
|
||||
{ "has-rtnstate": cmd.getRtnState() }
|
||||
);
|
||||
let rendererPlugin: RendererPluginType = null;
|
||||
let isNoneRenderer = line.renderer == "none";
|
||||
const isNoneRenderer = line.renderer == "none";
|
||||
if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) {
|
||||
rendererPlugin = PluginModel.getRendererPluginByName(line.renderer);
|
||||
}
|
||||
let rendererType = lineutil.getRendererType(line);
|
||||
let hidePrompt = rendererPlugin != null && rendererPlugin.hidePrompt;
|
||||
let termFontSize = GlobalModel.termFontSize.get();
|
||||
const rendererType = lineutil.getRendererType(line);
|
||||
const hidePrompt = rendererPlugin?.hidePrompt;
|
||||
const termFontSize = GlobalModel.termFontSize.get();
|
||||
let rtnStateDiffSize = termFontSize - 2;
|
||||
if (rtnStateDiffSize < 10) {
|
||||
rtnStateDiffSize = Math.max(termFontSize, 10);
|
||||
}
|
||||
let containerType = screen.getContainerType();
|
||||
const containerType = screen.getContainerType();
|
||||
return (
|
||||
<div
|
||||
className={mainDivCn}
|
||||
@@ -665,6 +672,9 @@ class LineCmd extends React.Component<
|
||||
{this.renderMeta1(cmd)}
|
||||
<If condition={!hidePrompt}>{this.renderCmdText(cmd)}</If>
|
||||
</div>
|
||||
<div key="restart" title="Restart Command" className="line-icon" onClick={this.clickRestart}>
|
||||
<i className="fa-sharp fa-regular fa-arrows-rotate" />
|
||||
</div>
|
||||
<div key="delete" title="Delete Line (⌘D)" className="line-icon" onClick={this.clickDelete}>
|
||||
<i className="fa-sharp fa-regular fa-trash" />
|
||||
</div>
|
||||
@@ -803,7 +813,7 @@ class Line extends React.Component<
|
||||
{}
|
||||
> {
|
||||
render() {
|
||||
let line = this.props.line;
|
||||
const line = this.props.line;
|
||||
if (line.archived) {
|
||||
return null;
|
||||
}
|
||||
@@ -829,7 +839,7 @@ class LineText extends React.Component<
|
||||
> {
|
||||
@boundMethod
|
||||
clickHandler() {
|
||||
let { line, noSelect } = this.props;
|
||||
const { line, noSelect } = this.props;
|
||||
if (noSelect) {
|
||||
return;
|
||||
}
|
||||
@@ -838,7 +848,7 @@ class LineText extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
onAvatarRightClick(e: any): void {
|
||||
let { line, noSelect } = this.props;
|
||||
const { line, noSelect } = this.props;
|
||||
if (noSelect) {
|
||||
return;
|
||||
}
|
||||
@@ -852,19 +862,19 @@ class LineText extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
let { screen, line, renderMode } = this.props;
|
||||
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
let isSelected = mobx
|
||||
const { screen, line } = this.props;
|
||||
const formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
const isSelected = mobx
|
||||
.computed(() => screen.getSelectedLine() == line.linenum, {
|
||||
name: "computed-isSelected",
|
||||
})
|
||||
.get();
|
||||
let isFocused = mobx
|
||||
const isFocused = mobx
|
||||
.computed(() => screen.getFocusType() == "cmd", {
|
||||
name: "computed-isFocused",
|
||||
})
|
||||
.get();
|
||||
let mainClass = cn("line", "line-text", "focus-parent");
|
||||
const mainClass = cn("line", "line-text", "focus-parent");
|
||||
return (
|
||||
<div
|
||||
className={mainClass}
|
||||
|
||||
@@ -27,6 +27,12 @@ let MagicLayout = {
|
||||
ScreenSidebarWidthPadding: 5,
|
||||
ScreenSidebarMinWidth: 200,
|
||||
ScreenSidebarHeaderHeight: 28,
|
||||
|
||||
MainSidebarMinWidth: 75,
|
||||
MainSidebarMaxWidth: 300,
|
||||
MainSidebarSnapThreshold: 90,
|
||||
MainSidebarDragResistance: 50,
|
||||
MainSidebarDefaultWidth: 240,
|
||||
};
|
||||
|
||||
let m = MagicLayout;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
@import "../../app/common/themes/themes.less";
|
||||
@import "../../app/common/icons/icons.less";
|
||||
|
||||
.main-sidebar {
|
||||
padding: 0;
|
||||
min-width: 20rem;
|
||||
max-width: 20rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
font-size: 12.5px;
|
||||
line-height: 20px;
|
||||
backdrop-filter: blur(4px);
|
||||
z-index: 20;
|
||||
|
||||
.title-bar-drag {
|
||||
-webkit-app-region: drag;
|
||||
@@ -23,14 +23,19 @@
|
||||
&.collapsed {
|
||||
width: 6em;
|
||||
min-width: 6em;
|
||||
.arrow-container, .collapse-button {
|
||||
.arrow-container,
|
||||
.collapse-button {
|
||||
transform: rotate(180deg);
|
||||
margin-top: 20px;
|
||||
}
|
||||
.contents {
|
||||
margin-top: 26px;
|
||||
|
||||
.top, .workspaces-item, .middle, .bottom, .separator {
|
||||
|
||||
.top,
|
||||
.workspaces,
|
||||
.middle,
|
||||
.bottom,
|
||||
.separator {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
@@ -43,7 +48,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.logo-container img {
|
||||
.logo-container {
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
@@ -79,10 +84,14 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.logo-container {
|
||||
flex-shrink: 0;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100px;
|
||||
}
|
||||
@@ -90,7 +99,7 @@
|
||||
.collapse-button {
|
||||
transition: transform 0.3s ease-in-out;
|
||||
margin-right: 14px;
|
||||
|
||||
|
||||
svg {
|
||||
margin-top: 3px;
|
||||
width: 1.5em;
|
||||
@@ -120,12 +129,7 @@
|
||||
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||
}
|
||||
.index {
|
||||
margin: 0 1.5em 0 -0.5rem;
|
||||
font-size: 0.8em;
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
width: 2em;
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
}
|
||||
.hotkey {
|
||||
float: left !important;
|
||||
@@ -145,57 +149,74 @@
|
||||
|
||||
.item {
|
||||
padding: 5px;
|
||||
margin: 0 6px;
|
||||
margin-left: 6px;
|
||||
border-radius: 4px;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity 0.1s ease-in-out, visibility 0.1s step-end;
|
||||
.sessionName {
|
||||
width: 12rem;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: inherit;
|
||||
max-width: inherit;
|
||||
min-width: inherit;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.front-icon {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
|
||||
.end-icons {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.item-contents {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.icon {
|
||||
margin: -2px 8px 0px 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.actions.icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.hotkey {
|
||||
float: right;
|
||||
margin-right: 6px;
|
||||
visibility: hidden;
|
||||
letter-spacing: 6px;
|
||||
margin-left: auto;
|
||||
}
|
||||
.disabled .hotkey {
|
||||
display: none;
|
||||
}
|
||||
&:hover .hotkey {
|
||||
visibility: visible;
|
||||
}
|
||||
.actions {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover .actions {
|
||||
visibility: visible;
|
||||
}
|
||||
.add_workspace {
|
||||
float: right;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 2px;
|
||||
margin-right: 6px;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
vertical-align: middle;
|
||||
svg {
|
||||
fill: @base-color;
|
||||
&:hover {
|
||||
:not(.disabled) .hotkey {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
.actions {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:hover) .status-indicator {
|
||||
.status-indicator-visible;
|
||||
}
|
||||
|
||||
&.workspaces {
|
||||
cursor: default;
|
||||
.add-workspace {
|
||||
cursor: pointer;
|
||||
.positional-icon-visible;
|
||||
float: right;
|
||||
padding: 2px;
|
||||
margin-right: 6px;
|
||||
.fa-plus {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.front-icon {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.end-icons {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.fa-discord {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+163
-133
@@ -7,42 +7,65 @@ import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import dayjs from "dayjs";
|
||||
import type { RemoteType } from "../../types/types";
|
||||
import type { ClientDataType, RemoteType } from "../../types/types";
|
||||
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 AppsIcon } from "../assets/icons/apps.svg";
|
||||
import { ReactComponent as ConnectionsIcon } from "../assets/icons/connections.svg";
|
||||
import { ReactComponent as WorkspacesIcon } from "../assets/icons/workspaces.svg";
|
||||
import { ReactComponent as AddIcon } from "../assets/icons/add.svg";
|
||||
import { ReactComponent as ActionsIcon } from "../assets/icons/tab/actions.svg";
|
||||
import { ReactComponent as SettingsIcon } from "../assets/icons/settings.svg";
|
||||
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel, GlobalCommandRunner, Session, VERSION } from "../../model/model";
|
||||
import { sortAndFilterRemotes, isBlank, openLink } from "../../util/util";
|
||||
import { isBlank, openLink } from "../../util/util";
|
||||
import { ResizableSidebar } from "../common/common";
|
||||
import * as constants from "../appconst";
|
||||
|
||||
import "./sidebar.less";
|
||||
import { ActionsIcon, CenteredIcon, FrontIcon, StatusIndicator } from "../common/icons/icons";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
class SideBarItem extends React.Component<{
|
||||
frontIcon: React.ReactNode;
|
||||
contents: React.ReactNode | string;
|
||||
endIcons?: React.ReactNode[];
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={cn("item", "unselectable", "hoverEffect", this.props.className)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<FrontIcon>{this.props.frontIcon}</FrontIcon>
|
||||
<div className="item-contents truncate">{this.props.contents}</div>
|
||||
<div className="end-icons">{this.props.endIcons}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HotKeyIcon extends React.Component<{ hotkey: string }> {
|
||||
render() {
|
||||
return (
|
||||
<CenteredIcon className="hotkey">
|
||||
<span>⌘{this.props.hotkey}</span>
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface MainSideBarProps {
|
||||
parentRef: React.RefObject<HTMLElement>;
|
||||
clientData: ClientDataType;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class MainSideBar extends React.Component<{}, {}> {
|
||||
collapsed: mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
|
||||
@boundMethod
|
||||
toggleCollapsed() {
|
||||
mobx.action(() => {
|
||||
this.collapsed.set(!this.collapsed.get());
|
||||
})();
|
||||
}
|
||||
class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||
sidebarRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
handleSessionClick(sessionId: string) {
|
||||
GlobalCommandRunner.switchSession(sessionId);
|
||||
@@ -99,7 +122,6 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
handlePlaybookClick(): void {
|
||||
console.log("playbook click");
|
||||
return;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@@ -150,141 +172,149 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
|
||||
getSessions() {
|
||||
if (!GlobalModel.sessionListLoaded.get()) return <div className="item">loading ...</div>;
|
||||
let sessionList = [];
|
||||
let activeSessionId = GlobalModel.activeSessionId.get();
|
||||
for (let session of GlobalModel.sessionList) {
|
||||
const sessionList: Session[] = [];
|
||||
const activeSessionId = GlobalModel.activeSessionId.get();
|
||||
for (const session of GlobalModel.sessionList) {
|
||||
if (!session.archived.get() || session.sessionId == activeSessionId) {
|
||||
sessionList.push(session);
|
||||
}
|
||||
}
|
||||
return sessionList.map((session, index) => {
|
||||
const isActive = GlobalModel.activeMainView.get() == "session" && activeSessionId == session.sessionId;
|
||||
const sessionScreens = GlobalModel.getSessionScreens(session.sessionId);
|
||||
const sessionIndicator = Math.max(...sessionScreens.map((screen) => screen.statusIndicator.get()));
|
||||
const sessionRunningCommands = sessionScreens.some((screen) => screen.numRunningCmds.get() > 0);
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`item hoverEffect ${isActive ? "active" : ""}`}
|
||||
<SideBarItem
|
||||
key={session.sessionId}
|
||||
className={`${isActive ? "active" : ""}`}
|
||||
frontIcon={<span className="index">{index + 1}</span>}
|
||||
contents={session.name.get()}
|
||||
endIcons={[
|
||||
<StatusIndicator
|
||||
key="statusindicator"
|
||||
level={sessionIndicator}
|
||||
runningCommands={sessionRunningCommands}
|
||||
/>,
|
||||
<ActionsIcon key="actions" onClick={(e) => this.openSessionSettings(e, session)} />,
|
||||
]}
|
||||
onClick={() => this.handleSessionClick(session.sessionId)}
|
||||
>
|
||||
<span className="index">{index + 1}</span>
|
||||
<span className="truncate sessionName">{session.name.get()}</span>
|
||||
<ActionsIcon
|
||||
className="icon hoverEffect actions"
|
||||
onClick={(e) => this.openSessionSettings(e, session)}
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let model = GlobalModel;
|
||||
let activeSessionId = model.activeSessionId.get();
|
||||
let activeScreen = model.getActiveScreen();
|
||||
let activeRemoteId: string = null;
|
||||
if (activeScreen != null) {
|
||||
let rptr = activeScreen.curRemote.get();
|
||||
if (rptr != null && !isBlank(rptr.remoteid)) {
|
||||
activeRemoteId = rptr.remoteid;
|
||||
}
|
||||
}
|
||||
let remotes = model.remotes ?? [];
|
||||
remotes = sortAndFilterRemotes(remotes);
|
||||
let sessionList = [];
|
||||
for (let session of model.sessionList) {
|
||||
if (!session.archived.get() || session.sessionId == activeSessionId) {
|
||||
sessionList.push(session);
|
||||
}
|
||||
}
|
||||
let isCollapsed = this.collapsed.get();
|
||||
let clientData = GlobalModel.clientData.get();
|
||||
let clientData = this.props.clientData;
|
||||
let needsUpdate = false;
|
||||
if (!clientData?.clientopts.noreleasecheck && !isBlank(clientData?.releaseinfo?.latestversion)) {
|
||||
needsUpdate = compareLoose(VERSION, clientData.releaseinfo.latestversion) < 0;
|
||||
}
|
||||
let mainSidebar = GlobalModel.mainSidebarModel;
|
||||
let isCollapsed = mainSidebar.getCollapsed();
|
||||
return (
|
||||
<div className={cn("main-sidebar", { collapsed: isCollapsed }, { "is-dev": GlobalModel.isDev })}>
|
||||
<div className="title-bar-drag" />
|
||||
<div className="contents">
|
||||
<div className="logo">
|
||||
<If condition={isCollapsed}>
|
||||
<div className="logo-container" onClick={this.toggleCollapsed}>
|
||||
<img src="public/logos/wave-logo.png" />
|
||||
<ResizableSidebar
|
||||
className="main-sidebar"
|
||||
position="left"
|
||||
enableSnap={true}
|
||||
parentRef={this.props.parentRef}
|
||||
>
|
||||
{(toggleCollapse) => (
|
||||
<React.Fragment>
|
||||
<div className="title-bar-drag" />
|
||||
<div className="contents">
|
||||
<div className="logo">
|
||||
<If condition={isCollapsed}>
|
||||
<div className="logo-container" onClick={toggleCollapse}>
|
||||
<img src="public/logos/wave-logo.png" />
|
||||
</div>
|
||||
</If>
|
||||
<If condition={!isCollapsed}>
|
||||
<div className="logo-container">
|
||||
<img src="public/logos/wave-dark.png" />
|
||||
</div>
|
||||
<div className="spacer" />
|
||||
<div className="collapse-button" onClick={toggleCollapse}>
|
||||
<LeftChevronIcon className="icon" />
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={!isCollapsed}>
|
||||
<div className="logo-container">
|
||||
<img src="public/logos/wave-dark.png" />
|
||||
<div className="separator" />
|
||||
<div className="top">
|
||||
<SideBarItem
|
||||
key="history"
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-clock-rotate-left icon" />}
|
||||
contents="History"
|
||||
endIcons={[<HotKeyIcon key="hotkey" hotkey="H" />]}
|
||||
onClick={this.handleHistoryClick}
|
||||
/>
|
||||
{/* <SideBarItem className="hoverEffect unselectable" frontIcon={<FavoritesIcon className="icon" />} contents="Favorites" endIcon={<span className="hotkey">⌘B</span>} onClick={this.handleBookmarksClick}/> */}
|
||||
<SideBarItem
|
||||
key="connections"
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-globe icon " />}
|
||||
contents="Connections"
|
||||
onClick={this.handleConnectionsClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="spacer" />
|
||||
<div className="collapse-button" onClick={this.toggleCollapsed}>
|
||||
<LeftChevronIcon className="icon" />
|
||||
<div className="separator" />
|
||||
<SideBarItem
|
||||
key="workspaces"
|
||||
className="workspaces"
|
||||
frontIcon={<WorkspacesIcon className="icon" />}
|
||||
contents="Workspaces"
|
||||
endIcons={[
|
||||
<CenteredIcon
|
||||
key="add-workspace"
|
||||
className="add-workspace hoverEffect"
|
||||
onClick={this.handleNewSession}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-plus"></i>
|
||||
</CenteredIcon>,
|
||||
]}
|
||||
/>
|
||||
<div className="middle hideScrollbarUntillHover">{this.getSessions()}</div>
|
||||
<div className="bottom">
|
||||
<If condition={needsUpdate}>
|
||||
<SideBarItem
|
||||
key="update-available"
|
||||
className="updateBanner"
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-circle-up icon" />}
|
||||
contents="Update Available"
|
||||
onClick={() => openLink("https://www.waveterm.dev/download?ref=upgrade")}
|
||||
/>
|
||||
</If>
|
||||
<If condition={GlobalModel.isDev}>
|
||||
<SideBarItem
|
||||
key="apps"
|
||||
frontIcon={<AppsIcon className="icon" />}
|
||||
contents="Apps"
|
||||
onClick={this.handlePluginsClick}
|
||||
endIcons={[<HotKeyIcon key="hotkey" hotkey="A" />]}
|
||||
/>
|
||||
</If>
|
||||
<SideBarItem
|
||||
key="settings"
|
||||
frontIcon={<SettingsIcon className="icon" />}
|
||||
contents="Settings"
|
||||
onClick={this.handleSettingsClick}
|
||||
/>
|
||||
<SideBarItem
|
||||
key="documentation"
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-circle-question icon" />}
|
||||
contents="Documentation"
|
||||
onClick={() => openLink("https://docs.waveterm.dev")}
|
||||
/>
|
||||
<SideBarItem
|
||||
key="discord"
|
||||
frontIcon={<i className="fa-brands fa-discord icon" />}
|
||||
contents="Discord"
|
||||
onClick={() => openLink("https://discord.gg/XfvZ334gwU")}
|
||||
/>
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
<div className="separator" />
|
||||
<div className="top">
|
||||
<div className="item hoverEffect unselectable" onClick={this.handleHistoryClick}>
|
||||
<HistoryIcon className="icon" />
|
||||
History
|
||||
<span className="hotkey">⌘H</span>
|
||||
</div>
|
||||
{/* <div className="item hoverEffect unselectable" onClick={this.handleBookmarksClick}>
|
||||
<FavoritesIcon className="icon" />
|
||||
Favorites
|
||||
<span className="hotkey">⌘B</span>
|
||||
</div> */}
|
||||
<div className="item hoverEffect unselectable" onClick={this.handleConnectionsClick}>
|
||||
<ConnectionsIcon className="icon" />
|
||||
Connections
|
||||
</div>
|
||||
</div>
|
||||
<div className="separator" />
|
||||
<div className="item workspaces-item unselectable">
|
||||
<WorkspacesIcon className="icon" />
|
||||
Workspaces
|
||||
<div className="add_workspace hoverEffect" onClick={this.handleNewSession}>
|
||||
<AddIcon />
|
||||
</div>
|
||||
</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?ref=upgrade")}
|
||||
>
|
||||
<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" />
|
||||
Apps
|
||||
<span className="hotkey">⌘A</span>
|
||||
</div>
|
||||
</If>
|
||||
<div className="item hoverEffect unselectable" onClick={this.handleSettingsClick}>
|
||||
<SettingsIcon className="icon" />
|
||||
Settings
|
||||
</div>
|
||||
<div
|
||||
className="item hoverEffect unselectable"
|
||||
onClick={() => openLink("https://docs.waveterm.dev")}
|
||||
>
|
||||
<HelpIcon className="icon" />
|
||||
Documentation
|
||||
</div>
|
||||
<div
|
||||
className="item hoverEffect unselectable"
|
||||
onClick={() => openLink("https://discord.gg/XfvZ334gwU")}
|
||||
>
|
||||
<DiscordIcon className="icon discord" />
|
||||
Discord
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ResizableSidebar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { TextAreaInput } from "./textareainput";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import type { OpenAICmdInfoChatMessageType } from "../../../types/types";
|
||||
import { Markdown } from "../../common/common";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../../util/keyutil";
|
||||
|
||||
@mobxReact.observer
|
||||
class AIChat extends React.Component<{}, {}> {
|
||||
@@ -76,8 +77,8 @@ class AIChat extends React.Component<{}, {}> {
|
||||
let inputModel = model.inputModel;
|
||||
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
|
||||
let resetCodeSelect = !ctrlMod;
|
||||
|
||||
if (e.code == "Enter") {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
e.preventDefault();
|
||||
if (!ctrlMod) {
|
||||
if (inputModel.getCodeSelectSelectedIndex() == -1) {
|
||||
@@ -91,17 +92,18 @@ class AIChat extends React.Component<{}, {}> {
|
||||
e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end");
|
||||
}
|
||||
}
|
||||
if (e.code == "Escape") {
|
||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
inputModel.closeAIAssistantChat();
|
||||
}
|
||||
if (e.code == "KeyL" && e.getModifierState("Control")) {
|
||||
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:l")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
inputModel.clearAIAssistantChat();
|
||||
}
|
||||
if (e.code == "ArrowUp") {
|
||||
if (checkKeyPressed(waveEvent, "ArrowUp")) {
|
||||
if (this.getLinePos(e.target).linePos > 1) {
|
||||
// normal up arrow
|
||||
return;
|
||||
@@ -110,7 +112,7 @@ class AIChat extends React.Component<{}, {}> {
|
||||
inputModel.codeSelectSelectNextOldestCodeBlock();
|
||||
resetCodeSelect = false;
|
||||
}
|
||||
if (e.code == "ArrowDown") {
|
||||
if (checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
|
||||
return;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user