mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d1bffb5a0d | |||
| df94b91e6c | |||
| 856c879cdf | |||
| ea3f744d93 | |||
| dd3d52e47d | |||
| 32499f0421 | |||
| b136c915df | |||
| 0648d48ba1 | |||
| b97423268c | |||
| 34ec4ff39f | |||
| 018bb14b6a | |||
| 1547fc856e | |||
| 37821738d8 | |||
| 8e79eeccca | |||
| 203f1f7505 | |||
| 57a7641f82 | |||
| 99f5c094d2 | |||
| f23da6a476 | |||
| 03ea444030 |
@@ -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
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-41
@@ -13,6 +13,7 @@ import { RemoteType, StatusIndicatorLevel } from "../../types/types";
|
||||
import ReactDOM from "react-dom";
|
||||
import { GlobalModel } from "../../model/model";
|
||||
import * as appconst from "../appconst";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../util/keyutil";
|
||||
|
||||
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
|
||||
import { ReactComponent as CopyIcon } from "../assets/icons/history/copy.svg";
|
||||
@@ -117,7 +118,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 +288,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 +716,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 +871,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 +899,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 +1244,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).
|
||||
@@ -1313,6 +1286,5 @@ export {
|
||||
LinkButton,
|
||||
Status,
|
||||
Modal,
|
||||
StatusIndicator,
|
||||
ShowWaveShellInstallPrompt,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
#spinner,
|
||||
#indicator {
|
||||
visibility: hidden;
|
||||
}
|
||||
.spin #spinner {
|
||||
visibility: visible;
|
||||
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,77 @@
|
||||
import React from "react";
|
||||
import { StatusIndicatorLevel } from "../../../types/types";
|
||||
import cn from "classnames";
|
||||
import { ReactComponent as SpinnerIndicator } from "../../assets/icons/spinner-indicator.svg";
|
||||
|
||||
interface PositionalIconProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export class FrontIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div className={cn("front-icon", "positional-icon", this.props.className)}>
|
||||
<div className="positional-icon-inner">{this.props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CenteredIcon extends React.Component<PositionalIconProps> {
|
||||
render() {
|
||||
return (
|
||||
<div 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface StatusIndicatorProps {
|
||||
level: StatusIndicatorLevel;
|
||||
className?: string;
|
||||
runningCommands?: boolean;
|
||||
}
|
||||
|
||||
export class StatusIndicator extends React.Component<StatusIndicatorProps> {
|
||||
render() {
|
||||
const { level, className, runningCommands } = this.props;
|
||||
let statusIndicator = null;
|
||||
if (level != StatusIndicatorLevel.None || runningCommands) {
|
||||
let levelClass = null;
|
||||
switch (level) {
|
||||
case StatusIndicatorLevel.Output:
|
||||
levelClass = "output";
|
||||
break;
|
||||
case StatusIndicatorLevel.Success:
|
||||
levelClass = "success";
|
||||
break;
|
||||
case StatusIndicatorLevel.Error:
|
||||
levelClass = "error";
|
||||
break;
|
||||
}
|
||||
statusIndicator = (
|
||||
<CenteredIcon className={cn(className, levelClass, "status-indicator")}>
|
||||
<SpinnerIndicator className={runningCommands ? "spin" : null} />
|
||||
</CenteredIcon>
|
||||
);
|
||||
}
|
||||
return statusIndicator;
|
||||
}
|
||||
}
|
||||
+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>
|
||||
|
||||
@@ -360,6 +360,12 @@ class LineCmd extends React.Component<
|
||||
GlobalCommandRunner.lineDelete(line.lineid, true);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickRestart() {
|
||||
let { line } = this.props;
|
||||
GlobalCommandRunner.lineRestart(line.lineid, true);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickMinimize() {
|
||||
mobx.action(() => {
|
||||
@@ -467,12 +473,21 @@ 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">
|
||||
@@ -665,6 +680,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>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "../../app/common/themes/themes.less";
|
||||
@import "../../app/common/icons/icons.less";
|
||||
|
||||
.main-sidebar {
|
||||
padding: 0;
|
||||
@@ -23,14 +24,20 @@
|
||||
&.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-item,
|
||||
.middle,
|
||||
.bottom,
|
||||
.separator {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
@@ -82,7 +89,7 @@
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
width: 100px;
|
||||
}
|
||||
@@ -90,7 +97,7 @@
|
||||
.collapse-button {
|
||||
transition: transform 0.3s ease-in-out;
|
||||
margin-right: 14px;
|
||||
|
||||
|
||||
svg {
|
||||
margin-top: 3px;
|
||||
width: 1.5em;
|
||||
@@ -120,12 +127,9 @@
|
||||
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;
|
||||
text-align: center;
|
||||
width: 2em;
|
||||
display: inline-block;
|
||||
}
|
||||
.hotkey {
|
||||
float: left !important;
|
||||
@@ -145,58 +149,70 @@
|
||||
|
||||
.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;
|
||||
}
|
||||
.disabled .hotkey {
|
||||
display: none;
|
||||
&:hover {
|
||||
:not(.disabled) .hotkey {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
.actions {
|
||||
.positional-icon-visible;
|
||||
}
|
||||
}
|
||||
&:hover .hotkey {
|
||||
visibility: visible;
|
||||
}
|
||||
.actions {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover .actions {
|
||||
visibility: visible;
|
||||
|
||||
&:not(:hover) .status-indicator {
|
||||
.positional-icon-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;
|
||||
}
|
||||
}
|
||||
|
||||
.front-icon {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.end-icons {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.fa-discord {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
|
||||
+99
-85
@@ -12,27 +12,54 @@ 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 * 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class MainSideBar extends React.Component<{}, {}> {
|
||||
collapsed: mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
@@ -99,7 +126,6 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
handlePlaybookClick(): void {
|
||||
console.log("playbook click");
|
||||
return;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@@ -159,42 +185,29 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
}
|
||||
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
|
||||
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 needsUpdate = false;
|
||||
@@ -223,65 +236,66 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
</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>
|
||||
<SideBarItem
|
||||
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
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-globe icon " />}
|
||||
contents="Connections"
|
||||
onClick={this.handleConnectionsClick}
|
||||
/>
|
||||
</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>
|
||||
<SideBarItem
|
||||
frontIcon={<WorkspacesIcon className="icon" />}
|
||||
contents="Workspaces"
|
||||
endIcons={[
|
||||
<div
|
||||
key="add_workspace"
|
||||
className="add_workspace hoverEffect"
|
||||
onClick={this.handleNewSession}
|
||||
>
|
||||
<AddIcon />
|
||||
</div>,
|
||||
]}
|
||||
/>
|
||||
<div className="middle hideScrollbarUntillHover">{this.getSessions()}</div>
|
||||
<div className="bottom">
|
||||
<If condition={needsUpdate}>
|
||||
<div
|
||||
className="item hoverEffect unselectable updateBanner"
|
||||
<SideBarItem
|
||||
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")}
|
||||
>
|
||||
<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>
|
||||
<SideBarItem
|
||||
frontIcon={<AppsIcon className="icon" />}
|
||||
contents="Apps"
|
||||
onClick={this.handlePluginsClick}
|
||||
endIcons={[<HotKeyIcon key="hotkey" hotkey="A" />]}
|
||||
/>
|
||||
</If>
|
||||
<div className="item hoverEffect unselectable" onClick={this.handleSettingsClick}>
|
||||
<SettingsIcon className="icon" />
|
||||
Settings
|
||||
</div>
|
||||
<div
|
||||
className="item hoverEffect unselectable"
|
||||
<SideBarItem
|
||||
frontIcon={<SettingsIcon className="icon" />}
|
||||
contents="Settings"
|
||||
onClick={this.handleSettingsClick}
|
||||
/>
|
||||
<SideBarItem
|
||||
frontIcon={<i className="fa-sharp fa-regular fa-circle-question icon" />}
|
||||
contents="Documentation"
|
||||
onClick={() => openLink("https://docs.waveterm.dev")}
|
||||
>
|
||||
<HelpIcon className="icon" />
|
||||
Documentation
|
||||
</div>
|
||||
<div
|
||||
className="item hoverEffect unselectable"
|
||||
/>
|
||||
<SideBarItem
|
||||
frontIcon={<i className="fa-brands fa-discord icon" />}
|
||||
contents="Discord"
|
||||
onClick={() => openLink("https://discord.gg/XfvZ334gwU")}
|
||||
>
|
||||
<DiscordIcon className="icon discord" />
|
||||
Discord
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
.cmd-input-filter {
|
||||
opacity: 0.5;
|
||||
&:hover {
|
||||
opacity: 1.0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
@@ -198,14 +198,14 @@
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1.0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cmd-aichat {
|
||||
|
||||
.cmd-aichat {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-flow: column nowrap;
|
||||
@@ -219,7 +219,7 @@
|
||||
flex-shrink: 1;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
|
||||
.chat-textarea {
|
||||
color: @term-bright-white;
|
||||
background-color: @textarea-background;
|
||||
@@ -242,29 +242,27 @@
|
||||
}
|
||||
|
||||
.chat-msg {
|
||||
margin-top:5px;
|
||||
margin-bottom:5px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.chat-msg-assistant {
|
||||
color: @term-white;
|
||||
}
|
||||
|
||||
.chat-msg-user {
|
||||
|
||||
.chat-msg-user {
|
||||
.msg-text {
|
||||
font-family: @markdown-font;
|
||||
font-size: 14px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.chat-msg-error {
|
||||
color: @term-bright-red;
|
||||
font-family: @markdown-font;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
.grow-spacer {
|
||||
flex: 1 0 10px;
|
||||
|
||||
@@ -13,6 +13,7 @@ import { GlobalModel, GlobalCommandRunner, Screen } from "../../../model/model";
|
||||
import { getMonoFontSize } from "../../../util/textmeasure";
|
||||
import { isModKeyPress, hasNoModifiers } from "../../../util/util";
|
||||
import * as appconst from "../../appconst";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../../../util/keyutil";
|
||||
|
||||
type OV<T> = mobx.IObservableValue<T>;
|
||||
|
||||
@@ -177,12 +178,13 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
|
||||
let curLine = inputModel.getCurLine();
|
||||
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
let lastTab = this.lastTab;
|
||||
this.lastTab = e.code == "Tab";
|
||||
this.lastTab = checkKeyPressed(waveEvent, "Tab");
|
||||
let lastHist = this.lastHistoryUpDown;
|
||||
this.lastHistoryUpDown = false;
|
||||
|
||||
if (e.code == "Tab") {
|
||||
if (checkKeyPressed(waveEvent, "Tab")) {
|
||||
e.preventDefault();
|
||||
if (lastTab) {
|
||||
GlobalModel.submitCommand(
|
||||
@@ -204,7 +206,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (e.code == "Enter") {
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
e.preventDefault();
|
||||
if (!ctrlMod) {
|
||||
if (GlobalModel.inputModel.isEmpty()) {
|
||||
@@ -224,7 +226,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
GlobalModel.inputModel.setCurLine(e.target.value);
|
||||
return;
|
||||
}
|
||||
if (e.code == "Escape") {
|
||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
@@ -235,50 +237,50 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
inputModel.closeAIAssistantChat();
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyE" && e.getModifierState("Meta")) {
|
||||
if (checkKeyPressed(waveEvent, "Cmd:e")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
inputModel.toggleExpandInput();
|
||||
}
|
||||
if (e.code == "KeyC" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:c")) {
|
||||
e.preventDefault();
|
||||
inputModel.resetInput();
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyU" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:u")) {
|
||||
e.preventDefault();
|
||||
this.controlU();
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyP" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:p")) {
|
||||
e.preventDefault();
|
||||
this.controlP();
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyN" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:n")) {
|
||||
e.preventDefault();
|
||||
this.controlN();
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyW" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:w")) {
|
||||
e.preventDefault();
|
||||
this.controlW();
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyY" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:y")) {
|
||||
e.preventDefault();
|
||||
this.controlY();
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyR" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:r")) {
|
||||
e.preventDefault();
|
||||
inputModel.openHistory();
|
||||
return;
|
||||
}
|
||||
if ((e.code == "ArrowUp" || e.code == "ArrowDown") && hasNoModifiers(e)) {
|
||||
if (checkKeyPressed(waveEvent, "ArrowUp") || checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||
if (!inputModel.isHistoryLoaded()) {
|
||||
if (e.code == "ArrowUp") {
|
||||
if (checkKeyPressed(waveEvent, "ArrowUp")) {
|
||||
this.lastHistoryUpDown = true;
|
||||
inputModel.loadHistory(false, 1, "screen");
|
||||
}
|
||||
@@ -286,7 +288,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
}
|
||||
// invisible history movement
|
||||
let linePos = this.getLinePos(e.target);
|
||||
if (e.code == "ArrowUp") {
|
||||
if (checkKeyPressed(waveEvent, "ArrowUp")) {
|
||||
if (!lastHist && linePos.linePos > 1) {
|
||||
// regular arrow
|
||||
return;
|
||||
@@ -296,7 +298,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
this.lastHistoryUpDown = true;
|
||||
return;
|
||||
}
|
||||
if (e.code == "ArrowDown") {
|
||||
if (checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||
if (!lastHist && linePos.linePos < linePos.numLines) {
|
||||
// regular arrow
|
||||
return;
|
||||
@@ -307,16 +309,16 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (e.code == "PageUp" || e.code == "PageDown") {
|
||||
if (checkKeyPressed(waveEvent, "PageUp") || checkKeyPressed(waveEvent, "PageDown")) {
|
||||
e.preventDefault();
|
||||
let infoScroll = inputModel.hasScrollingInfoMsg();
|
||||
if (infoScroll) {
|
||||
let div = document.querySelector(".cmd-input-info");
|
||||
let amt = pageSize(div);
|
||||
scrollDiv(div, e.code == "PageUp" ? -amt : amt);
|
||||
scrollDiv(div, checkKeyPressed(waveEvent, "PageUp") ? -amt : amt);
|
||||
}
|
||||
}
|
||||
if (e.code == "Space" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:Space")) {
|
||||
e.preventDefault();
|
||||
inputModel.openAIAssistantChat();
|
||||
}
|
||||
@@ -338,32 +340,29 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
|
||||
@boundMethod
|
||||
onHistoryKeyDown(e: any) {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
if (e.code == "Escape") {
|
||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
e.preventDefault();
|
||||
inputModel.resetHistory();
|
||||
return;
|
||||
}
|
||||
if (e.code == "Enter") {
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
e.preventDefault();
|
||||
inputModel.grabSelectedHistoryItem();
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyG" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:g")) {
|
||||
e.preventDefault();
|
||||
inputModel.resetInput();
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyC" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:c")) {
|
||||
e.preventDefault();
|
||||
inputModel.resetInput();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
e.code == "KeyR" &&
|
||||
(e.getModifierState("Meta") || e.getModifierState("Control")) &&
|
||||
!e.getModifierState("Shift")
|
||||
) {
|
||||
if (checkKeyPressed(waveEvent, "Cmd:r") || checkKeyPressed(waveEvent, "Ctrl:r")) {
|
||||
e.preventDefault();
|
||||
let opts = mobx.toJS(inputModel.historyQueryOpts.get());
|
||||
if (opts.limitRemote) {
|
||||
@@ -376,7 +375,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
inputModel.setHistoryQueryOpts(opts);
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyS" && (e.getModifierState("Meta") || e.getModifierState("Control"))) {
|
||||
if (checkKeyPressed(waveEvent, "Cmd:s") || checkKeyPressed(waveEvent, "Ctrl:s")) {
|
||||
e.preventDefault();
|
||||
let opts = mobx.toJS(inputModel.historyQueryOpts.get());
|
||||
let htype = opts.queryType;
|
||||
@@ -390,26 +389,26 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
inputModel.setHistoryType(htype);
|
||||
return;
|
||||
}
|
||||
if (e.code == "Tab") {
|
||||
if (checkKeyPressed(waveEvent, "Tab")) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.code == "ArrowUp" || e.code == "ArrowDown") {
|
||||
if (checkKeyPressed(waveEvent, "ArrowUp") || checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||
e.preventDefault();
|
||||
inputModel.moveHistorySelection(e.code == "ArrowUp" ? 1 : -1);
|
||||
inputModel.moveHistorySelection(checkKeyPressed(waveEvent, "ArrowUp") ? 1 : -1);
|
||||
return;
|
||||
}
|
||||
if (e.code == "PageUp" || e.code == "PageDown") {
|
||||
if (checkKeyPressed(waveEvent, "PageUp") || checkKeyPressed(waveEvent, "PageDown")) {
|
||||
e.preventDefault();
|
||||
inputModel.moveHistorySelection(e.code == "PageUp" ? 10 : -10);
|
||||
inputModel.moveHistorySelection(checkKeyPressed(waveEvent, "PageUp") ? 10 : -10);
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyP" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:p")) {
|
||||
e.preventDefault();
|
||||
inputModel.moveHistorySelection(1);
|
||||
return;
|
||||
}
|
||||
if (e.code == "KeyN" && e.getModifierState("Control")) {
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:n")) {
|
||||
e.preventDefault();
|
||||
inputModel.moveHistorySelection(-1);
|
||||
return;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user