mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
Merge pull request #63 from wavetermdev/dev-0.5.0
Merge dev 0.5.0 branch to main
This commit is contained in:
commit
be9a3c288a
20
package.json
20
package.json
@ -18,16 +18,16 @@
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"mobx": "^6.6.0",
|
||||
"mobx-react": "^7.5.0",
|
||||
"monaco-editor": "^0.41.0",
|
||||
"monaco-editor": "^0.44.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-fetch": "^3.2.10",
|
||||
"papaparse": "^5.4.1",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-markdown": "^8.0.5",
|
||||
"remark": "^14.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"react-markdown": "^9.0.0",
|
||||
"remark": "^15.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"sprintf-js": "^1.1.2",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
"tsx-control-statements": "^4.1.1",
|
||||
@ -57,16 +57,18 @@
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/classnames": "^2.3.1",
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/node": "^18.0.3",
|
||||
"@types/papaparse": "^5.3.9",
|
||||
"@types/node": "^20.4.0",
|
||||
"@types/papaparse": "^5.3.10",
|
||||
"@types/react": "^18.0.12",
|
||||
"@types/uuid": "9.0.0",
|
||||
"@types/sprintf-js": "^1.1.3",
|
||||
"@types/throttle-debounce": "^5.0.1",
|
||||
"@types/uuid": "9.0.6",
|
||||
"@types/webpack-env": "^1.18.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-jsx-control-statements": "^4.1.2",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"electron": "27.0.0",
|
||||
"electron": "27.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"http-server": "^14.1.1",
|
||||
"less": "^4.1.2",
|
||||
@ -80,7 +82,7 @@
|
||||
"typescript": "^4.7.3",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.9.1",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
} from "./common/modals/settings";
|
||||
import { RemotesModal } from "./connections/connections";
|
||||
import { TosModal } from "./common/modals/modals";
|
||||
import { MainSideBar } from "./sidebar/MainSideBar";
|
||||
import { MainSideBar } from "./sidebar/sidebar";
|
||||
import { DisconnectedModal, ClientStopModal, AlertModal, AboutModal } from "./common/modals/modals";
|
||||
import { ErrorBoundary } from "./common/error/errorboundary";
|
||||
import "./app.less";
|
||||
|
@ -625,7 +625,81 @@
|
||||
}
|
||||
}
|
||||
|
||||
.textfield {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid @term-white;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
margin-bottom: 1rem;
|
||||
background-color: transparent;
|
||||
height: 44px;
|
||||
min-width: 412px;
|
||||
gap: 6px;
|
||||
|
||||
&.focused {
|
||||
border-color: @term-green;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: @term-red;
|
||||
}
|
||||
|
||||
.textfield-inner {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
|
||||
.textfield-label {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
font-size: 12.5px;
|
||||
transition: all 0.3s;
|
||||
color: @term-white;
|
||||
line-height: 10px;
|
||||
|
||||
&.float {
|
||||
font-size: 10px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
&.start {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.textfield-input {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
border: none;
|
||||
padding: 5px 0 5px 16px;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
color: @term-bright-white;
|
||||
line-height: 20px;
|
||||
|
||||
&.start {
|
||||
padding: 5px 16px 5px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-decoration {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.inline-edit {
|
||||
.icon {
|
||||
@ -665,4 +739,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import remarkGfm from "remark-gfm";
|
||||
import cn from "classnames";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import type { RemoteType } from "../../types/types";
|
||||
import { debounce } from "throttle-debounce";
|
||||
|
||||
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
|
||||
import { ReactComponent as CopyIcon } from "../assets/icons/history/copy.svg";
|
||||
@ -123,6 +124,152 @@ class Checkbox extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
interface InputDecorationProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class InputDecoration extends React.Component<InputDecorationProps, {}> {
|
||||
render() {
|
||||
const { children, onClick } = this.props;
|
||||
|
||||
return <div className="input-decoration">{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
interface TextFieldDecorationProps {
|
||||
startDecoration?: React.ReactNode;
|
||||
endDecoration?: React.ReactNode;
|
||||
}
|
||||
interface TextFieldProps {
|
||||
label: string;
|
||||
value?: string;
|
||||
className?: string;
|
||||
onChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
defaultValue?: string;
|
||||
decoration?: TextFieldDecorationProps;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
interface TextFieldState {
|
||||
focused: boolean;
|
||||
internalValue: string;
|
||||
error: boolean;
|
||||
showHelpText: boolean;
|
||||
hasContent: boolean;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
inputRef: React.RefObject<HTMLInputElement>;
|
||||
state: TextFieldState;
|
||||
|
||||
constructor(props: TextFieldProps) {
|
||||
super(props);
|
||||
const hasInitialContent = Boolean(props.value || props.defaultValue);
|
||||
this.state = {
|
||||
focused: false,
|
||||
hasContent: hasInitialContent,
|
||||
internalValue: props.defaultValue || "",
|
||||
error: false,
|
||||
showHelpText: false,
|
||||
};
|
||||
this.inputRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: TextFieldProps) {
|
||||
// Only update the focus state if using as controlled
|
||||
if (this.props.value !== undefined && this.props.value !== prevProps.value) {
|
||||
this.setState({ focused: Boolean(this.props.value) });
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleFocus() {
|
||||
this.setState({ focused: true });
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleBlur() {
|
||||
const { required } = this.props;
|
||||
if (this.inputRef.current) {
|
||||
const value = this.inputRef.current.value;
|
||||
if (required && !value) {
|
||||
this.setState({ error: true, focused: false });
|
||||
} else {
|
||||
this.setState({ error: false, focused: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleHelpTextClick() {
|
||||
this.setState((prevState) => ({ showHelpText: !prevState.showHelpText }));
|
||||
}
|
||||
|
||||
debouncedOnChange = debounce(300, (value) => {
|
||||
const { onChange } = this.props;
|
||||
onChange?.(value);
|
||||
});
|
||||
|
||||
@boundMethod
|
||||
handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const { required } = this.props;
|
||||
const inputValue = e.target.value;
|
||||
|
||||
// Check if value is empty and the field is required
|
||||
if (required && !inputValue) {
|
||||
this.setState({ error: true, hasContent: false });
|
||||
} else {
|
||||
this.setState({ error: false, hasContent: Boolean(inputValue) });
|
||||
}
|
||||
|
||||
// Update the internal state for uncontrolled version
|
||||
if (this.props.value === undefined) {
|
||||
this.setState({ internalValue: inputValue });
|
||||
}
|
||||
|
||||
this.debouncedOnChange(inputValue);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { label, value, placeholder, decoration, className } = this.props;
|
||||
const { focused, internalValue, error } = this.state;
|
||||
|
||||
// Decide if the input should behave as controlled or uncontrolled
|
||||
const inputValue = value !== undefined ? value : internalValue;
|
||||
|
||||
return (
|
||||
<div className={cn(`textfield ${className || ""}`, { focused: focused, error: error })}>
|
||||
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
|
||||
<div className="textfield-inner">
|
||||
<label
|
||||
className={cn("textfield-label", {
|
||||
float: this.state.hasContent || this.state.focused || placeholder,
|
||||
start: decoration?.startDecoration,
|
||||
})}
|
||||
htmlFor={label}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
className={cn("textfield-input", { start: decoration?.startDecoration })}
|
||||
ref={this.inputRef}
|
||||
id={label}
|
||||
value={inputValue}
|
||||
onChange={this.handleInputChange}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
{decoration?.endDecoration && <div>{decoration.endDecoration}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class RemoteStatusLight extends React.Component<{ remote: RemoteType }, {}> {
|
||||
render() {
|
||||
@ -362,4 +509,6 @@ export {
|
||||
InfoMessage,
|
||||
Markdown,
|
||||
SettingsError,
|
||||
TextField,
|
||||
InputDecoration,
|
||||
};
|
||||
|
@ -330,7 +330,7 @@
|
||||
.about-content {
|
||||
margin-bottom: 0;
|
||||
|
||||
section {
|
||||
.wave-section {
|
||||
.logo-wrapper {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
@ -403,12 +403,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
section:nth-child(3) {
|
||||
.wave-section:nth-child(3) {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
.button-link {
|
||||
.wave-button-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -418,7 +418,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
section:last-child {
|
||||
.wave-section:last-child {
|
||||
margin-bottom: 24px;
|
||||
color: @term-white;
|
||||
}
|
||||
@ -426,7 +426,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
.wave-button {
|
||||
display: flex;
|
||||
padding: 6px 16px;
|
||||
align-items: center;
|
||||
@ -435,7 +435,7 @@
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.button.color-green {
|
||||
.wave-button.color-green {
|
||||
color: @term-bright-white;
|
||||
background: @term-green !important; // !important is needed to override the default button color
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
||||
@ -446,7 +446,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.button.color-standard {
|
||||
.wave-button.color-standard {
|
||||
color: @term-white;
|
||||
background: var(--overlays-white-6, rgba(255, 255, 255, 0.12));
|
||||
|
||||
@ -455,7 +455,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.button-link {
|
||||
.wave-button-link {
|
||||
display: flex;
|
||||
padding: 6px 16px;
|
||||
align-items: center;
|
||||
@ -465,14 +465,12 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
.wave-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal.welcome-modal {
|
||||
|
@ -268,15 +268,17 @@ class TosModal extends React.Component<{}, {}> {
|
||||
<div className="item-inner">
|
||||
<div className="item-title">Telemetry</div>
|
||||
<div className="item-text">
|
||||
We only collect minimal <i>anonymous</i> telemetry data to help us
|
||||
understand how many people are using Wave.
|
||||
We only collect minimal <i>anonymous</i> telemetry data to help us understand
|
||||
how many people are using Wave.
|
||||
</div>
|
||||
<div className="item-field" style={{marginTop: 2}}>
|
||||
<div className="item-field" style={{ marginTop: 2 }}>
|
||||
<Toggle
|
||||
checked={!cdata.clientopts.notelemetry}
|
||||
onChange={this.handleChangeTelemetry}
|
||||
/>
|
||||
<div className="item-label">Telemetry {cdata.clientopts.notelemetry ? "Disabled" : "Enabled"}</div>
|
||||
<div className="item-label">
|
||||
Telemetry {cdata.clientopts.notelemetry ? "Disabled" : "Enabled"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -287,8 +289,9 @@ class TosModal extends React.Component<{}, {}> {
|
||||
<div className="item-inner">
|
||||
<div className="item-title">Join our Community</div>
|
||||
<div className="item-text">
|
||||
Get help, submit feature requests, report bugs,
|
||||
or just chat with fellow terminal enthusiasts.<br/>
|
||||
Get help, submit feature requests, report bugs, or just chat with fellow
|
||||
terminal enthusiasts.
|
||||
<br />
|
||||
<a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}>
|
||||
Join the Wave Discord Channel
|
||||
</a>
|
||||
@ -305,8 +308,8 @@ class TosModal extends React.Component<{}, {}> {
|
||||
<div className="item-inner">
|
||||
<div className="item-title">Support us on GitHub</div>
|
||||
<div className="item-text">
|
||||
We're <i>open source</i> and committed to providing a free terminal for individual
|
||||
users. Please show your support us by giving us a star on{" "}
|
||||
We're <i>open source</i> and committed to providing a free terminal for
|
||||
individual users. Please show your support us by giving us a star on{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
|
||||
@ -406,7 +409,7 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
</div>
|
||||
</header>
|
||||
<div className="content about-content">
|
||||
<section>
|
||||
<section className="wave-section about-section">
|
||||
<div className="logo-wrapper">
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
@ -415,10 +418,12 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
<div className="text-standard">Modern Terminal for Seamless Workflow</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="text-standard">{this.getStatus(this.isUpToDate())}</section>
|
||||
<section>
|
||||
<section className="wave-section about-section text-standard">
|
||||
{this.getStatus(this.isUpToDate())}
|
||||
</section>
|
||||
<section className="wave-section about-section">
|
||||
<a
|
||||
className="button button-link color-standard"
|
||||
className="wave-button wave-button-link color-standard"
|
||||
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
|
||||
target="_blank"
|
||||
>
|
||||
@ -426,7 +431,7 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
Github
|
||||
</a>
|
||||
<a
|
||||
className="button button-link color-standard"
|
||||
className="wave-button wave-button-link color-standard"
|
||||
href={util.makeExternLink("https://www.commandline.dev/")}
|
||||
target="_blank"
|
||||
>
|
||||
@ -434,7 +439,7 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
Website
|
||||
</a>
|
||||
<a
|
||||
className="button button-link color-standard"
|
||||
className="wave-button wave-button-link color-standard"
|
||||
href={util.makeExternLink(
|
||||
"https://github.com/wavetermdev/waveterm/blob/main/LICENSE"
|
||||
)}
|
||||
@ -444,7 +449,9 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
License
|
||||
</a>
|
||||
</section>
|
||||
<section className="text-standard">Copyright © 2023 Command Line Inc.</section>
|
||||
<section className="wave-section about-section text-standard">
|
||||
Copyright © 2023 Command Line Inc.
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,15 +19,15 @@
|
||||
@textarea-background: #2a2a2a;
|
||||
|
||||
@text-primary: #fff;
|
||||
@text-secondary: #C3C8C2;
|
||||
@text-secondary: #c3c8c2;
|
||||
@text-caption: #8b918a;
|
||||
|
||||
@accent-color: #3B3F3A;
|
||||
@accent-color: #3b3f3a;
|
||||
|
||||
@status-outline: #151715;
|
||||
@dropdown-menu: rgba(21, 23, 21, 1);
|
||||
|
||||
@status-connected: #46A758;
|
||||
@status-connected: #46a758;
|
||||
@status-connecting: #f5d90a;
|
||||
@status-error: #e54d2e;
|
||||
@status-disconnected: #c3c8c2;
|
||||
@ -53,11 +53,11 @@
|
||||
@tab-orange: #ef713b;
|
||||
@tab-yellow: #e0b956;
|
||||
@tab-green: #58c142;
|
||||
@tab-mint: #4BFFA9;
|
||||
@tab-cyan: #4BDFFF;
|
||||
@tab-blue: #3971FF;
|
||||
@tab-violet: #BA76FF;
|
||||
@tab-pink: #E05677;
|
||||
@tab-mint: #4bffa9;
|
||||
@tab-cyan: #4bdfff;
|
||||
@tab-blue: #3971ff;
|
||||
@tab-violet: #ba76ff;
|
||||
@tab-pink: #e05677;
|
||||
@tab-white: #ffffff;
|
||||
|
||||
@tab-black-text: #333;
|
||||
|
@ -119,11 +119,12 @@
|
||||
margin: 16px;
|
||||
|
||||
.newtab-section {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
|
||||
&.conn-section {
|
||||
gap: 8px;
|
||||
@ -151,6 +152,7 @@
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
|
||||
.icon {
|
||||
width: 20px;
|
||||
@ -169,6 +171,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
.status-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 3px;
|
||||
|
||||
svg.status-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
svg.add-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
path {
|
||||
fill: @text-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-standard {
|
||||
color: @text-secondary;
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
color: @text-caption;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(241, 246, 243, 0.08);
|
||||
}
|
||||
|
||||
.icon.color-white + .check-icon {
|
||||
path {
|
||||
fill: black;
|
||||
|
@ -10,7 +10,7 @@ import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import dayjs from "dayjs";
|
||||
import { GlobalCommandRunner, TabColors } from "../../../model/model";
|
||||
import { GlobalCommandRunner, TabColors, TabIcons } from "../../../model/model";
|
||||
import type { LineType, RenderModeType, LineFactoryProps, CommandRtnType } from "../../../types/types";
|
||||
import * as T from "../../../types/types";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
@ -20,7 +20,8 @@ import { GlobalModel, ScreenLines, Screen, Session } from "../../../model/model"
|
||||
import { Line } from "../../line/linecomps";
|
||||
import { LinesView } from "../../line/linesview";
|
||||
import { ConnectionDropdown } from "../../connections/connections";
|
||||
import * as util from "../../../util/util";
|
||||
import * as util from "../../../util/util";
|
||||
import { TextField, InputDecoration } from "../../common/common";
|
||||
import { ReactComponent as EllipseIcon } from "../../assets/icons/ellipse.svg";
|
||||
import { ReactComponent as Check12Icon } from "../../assets/icons/check12.svg";
|
||||
import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg";
|
||||
@ -37,7 +38,7 @@ dayjs.extend(localizedFormat);
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class ScreenView extends React.Component<{ session: Session, screen: Screen }, {}> {
|
||||
class ScreenView extends React.Component<{ session: Session; screen: Screen }, {}> {
|
||||
render() {
|
||||
let { session, screen } = this.props;
|
||||
if (screen == null) {
|
||||
@ -54,7 +55,8 @@ class ScreenView extends React.Component<{ session: Session, screen: Screen }, {
|
||||
|
||||
@mobxReact.observer
|
||||
class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
errorMessage: OV<string> = mobx.observable.box(null, { name: "NewTabSettings-errorMessage" });
|
||||
connDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "NewTabSettings-connDropdownActive" });
|
||||
errorMessage: OV<string | null> = mobx.observable.box(null, { name: "NewTabSettings-errorMessage" });
|
||||
|
||||
@boundMethod
|
||||
selectTabColor(color: string): void {
|
||||
@ -67,15 +69,29 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
inlineUpdateName(val: string): void {
|
||||
selectTabIcon(icon: string): void {
|
||||
let { screen } = this.props;
|
||||
if (util.isStrEq(val, screen.name.get())) {
|
||||
if (screen.getTabIcon() == icon) {
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { tabicon: icon }, false);
|
||||
util.commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
updateName(val: string): void {
|
||||
let { screen } = this.props;
|
||||
let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { name: val }, false);
|
||||
util.commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
toggleConnDropdown(): void {
|
||||
mobx.action(() => {
|
||||
this.connDropdownActive.set(!this.connDropdownActive.get());
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
selectRemote(cname: string): void {
|
||||
let prtn = GlobalCommandRunner.screenSetRemote(cname, true, false);
|
||||
@ -84,62 +100,112 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
|
||||
@boundMethod
|
||||
clickNewConnection(): void {
|
||||
GlobalModel.remotesModalModel.openModalForEdit({remoteedit: true}, true);
|
||||
GlobalModel.remotesModalModel.openModalForEdit({ remoteedit: true }, true);
|
||||
}
|
||||
|
||||
renderTabIconSelector(): React.ReactNode {
|
||||
let { screen } = this.props;
|
||||
let curIcon = screen.getTabIcon();
|
||||
if (util.isBlank(curIcon) || curIcon == "default") {
|
||||
curIcon = "square";
|
||||
}
|
||||
let icon: string | null = null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-s1 unselectable">Select the icon</div>
|
||||
<div className="control-iconlist">
|
||||
<For each="icon" of={TabIcons}>
|
||||
<div
|
||||
className="icondiv"
|
||||
key={icon}
|
||||
title={icon || ""}
|
||||
onClick={() => this.selectTabIcon(icon || "")}
|
||||
>
|
||||
<i className={`fa-sharp fa-solid fa-${icon}`}></i>
|
||||
</div>
|
||||
</For>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderTabColorSelector(): React.ReactNode {
|
||||
let { screen } = this.props;
|
||||
let curColor = screen.getTabColor();
|
||||
if (util.isBlank(curColor) || curColor == "default") {
|
||||
curColor = "green";
|
||||
}
|
||||
let color: string | null = null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-s1 unselectable">Select the color</div>
|
||||
<div className="control-iconlist">
|
||||
<For each="color" of={TabColors}>
|
||||
<div
|
||||
className="icondiv"
|
||||
key={color}
|
||||
title={color || ""}
|
||||
onClick={() => this.selectTabColor(color || "")}
|
||||
>
|
||||
<EllipseIcon className={cn("icon", "color-" + color)} />
|
||||
<If condition={color == curColor}>
|
||||
<Check12Icon className="check-icon" />
|
||||
</If>
|
||||
</div>
|
||||
</For>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { screen } = this.props;
|
||||
let rptr = screen.curRemote.get();
|
||||
let curColor = screen.getTabColor();
|
||||
if (util.isBlank(curColor) || curColor == "default") {
|
||||
curColor = "green";
|
||||
}
|
||||
let color: string = null;
|
||||
let curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
|
||||
return (
|
||||
<div className="newtab-container">
|
||||
<div className="newtab-section name-section">
|
||||
<div className="text-standard">Name</div>
|
||||
<TextField
|
||||
label="Title"
|
||||
required={true}
|
||||
defaultValue={screen.name.get() ?? ""}
|
||||
onChange={this.updateName}
|
||||
decoration={{
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
<i className="fa-sharp fa-regular fa-circle-question"></i>
|
||||
</InputDecoration>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="newtab-spacer" />
|
||||
<div className="newtab-section conn-section">
|
||||
<div className="text-s1 unselectable">
|
||||
You're connected to [{getRemoteStr(rptr)}]. Do you want to change it?
|
||||
You're connected to [{getRemoteStr(rptr)}]. Do you want to change it?
|
||||
</div>
|
||||
<div>
|
||||
<ConnectionDropdown curRemote={curRemote} allowNewConn={true} onSelectRemote={this.selectRemote} onNewConn={this.clickNewConnection}/>
|
||||
<ConnectionDropdown
|
||||
curRemote={curRemote}
|
||||
allowNewConn={true}
|
||||
onSelectRemote={this.selectRemote}
|
||||
onNewConn={this.clickNewConnection}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-caption cr-help-text">
|
||||
To change connection from the command line use `cr [alias|user@host]`
|
||||
</div>
|
||||
</div>
|
||||
<div className="newtab-spacer"/>
|
||||
<div className="newtab-section settings-field">
|
||||
<div className="text-s1 unselectable">
|
||||
Name
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
<InlineSettingsTextEdit
|
||||
placeholder="name"
|
||||
text={screen.name.get() ?? "(none)"}
|
||||
value={screen.name.get() ?? ""}
|
||||
onChange={this.inlineUpdateName}
|
||||
maxLength={50}
|
||||
showIcon={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="newtab-spacer"/>
|
||||
<div className="newtab-spacer" />
|
||||
<div className="newtab-section">
|
||||
<div className="text-s1 unselectable">
|
||||
Select the color
|
||||
</div>
|
||||
<div className="control-iconlist">
|
||||
<For each="color" of={TabColors}>
|
||||
<div className="icondiv" key={color} title={color} onClick={() => this.selectTabColor(color)}>
|
||||
<EllipseIcon className={cn("icon", "color-" + color)}/>
|
||||
<If condition={color == curColor}>
|
||||
<Check12Icon className="check-icon"/>
|
||||
</If>
|
||||
</div>
|
||||
</For>
|
||||
</div>
|
||||
<div>{this.renderTabIconSelector()}</div>
|
||||
</div>
|
||||
<div className="newtab-spacer" />
|
||||
<div className="newtab-section">
|
||||
<div>{this.renderTabColorSelector()}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -148,7 +214,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
|
||||
// screen is not null
|
||||
@mobxReact.observer
|
||||
class ScreenWindowView extends React.Component<{ session: Session, screen: Screen }, {}> {
|
||||
class ScreenWindowView extends React.Component<{ session: Session; screen: Screen }, {}> {
|
||||
rszObs: any;
|
||||
windowViewRef: React.RefObject<any>;
|
||||
|
||||
@ -309,13 +375,17 @@ class ScreenWindowView extends React.Component<{ session: Session, screen: Scree
|
||||
</div>
|
||||
<If condition={lines.length == 0}>
|
||||
<If condition={screen.nextLineNum.get() == 1}>
|
||||
<NewTabSettings screen={screen}/>
|
||||
<NewTabSettings screen={screen} />
|
||||
</If>
|
||||
<If condition={screen.nextLineNum.get() != 1}>
|
||||
<div className="window-view" ref={this.windowViewRef} data-screenid={screen.screenId}>
|
||||
<div key="lines" className="lines"></div>
|
||||
<div key="window-empty" className={cn("window-empty")}>
|
||||
<div><code className="text-standard">[workspace="{session.name.get()}" screen="{screen.name.get()}"]</code></div>
|
||||
<div>
|
||||
<code className="text-standard">
|
||||
[workspace="{session.name.get()}" screen="{screen.name.get()}"]
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
|
@ -8,14 +8,28 @@
|
||||
border-radius: 12px 0px 0px 0px;
|
||||
}
|
||||
|
||||
&.color-green, &.color-default {
|
||||
&.color-green,
|
||||
&.color-default {
|
||||
svg.left-icon path {
|
||||
fill: @tab-green;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-green;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-green;
|
||||
background: linear-gradient(180deg, rgba(88, 193, 66, 0.20) 9.34%, rgba(88, 193, 66, 0.03) 44.16%, rgba(88, 193, 66, 0.00) 86.79%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(88, 193, 66, 0.2) 9.34%,
|
||||
rgba(88, 193, 66, 0.03) 44.16%,
|
||||
rgba(88, 193, 66, 0) 86.79%
|
||||
);
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-green;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,9 +38,18 @@
|
||||
fill: @tab-orange;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-orange;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-orange;
|
||||
background: linear-gradient(180deg, rgba(239, 113, 59, 0.20) 9.34%, rgba(239, 113, 59, 0.03) 44.16%, rgba(239, 113, 59, 0.00) 86.79%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(239, 113, 59, 0.2) 9.34%,
|
||||
rgba(239, 113, 59, 0.03) 44.16%,
|
||||
rgba(239, 113, 59, 0) 86.79%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,9 +58,18 @@
|
||||
fill: @tab-red;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-red;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-red;
|
||||
background: linear-gradient(180deg, rgba(229, 77, 46, 0.20) 9.34%, rgba(229, 77, 46, 0.03) 44.16%, rgba(229, 77, 46, 0.00) 86.79%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(229, 77, 46, 0.2) 9.34%,
|
||||
rgba(229, 77, 46, 0.03) 44.16%,
|
||||
rgba(229, 77, 46, 0) 86.79%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,9 +78,18 @@
|
||||
fill: @tab-yellow;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
.icon i {
|
||||
color: @tab-yellow;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-yellow;
|
||||
background: linear-gradient(180deg, rgba(224, 185, 86, 0.20) 9.34%, rgba(224, 185, 86, 0.03) 44.16%, rgba(224, 185, 86, 0.00) 86.79%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(224, 185, 86, 0.2) 9.34%,
|
||||
rgba(224, 185, 86, 0.03) 44.16%,
|
||||
rgba(224, 185, 86, 0) 86.79%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,9 +98,18 @@
|
||||
fill: @tab-blue;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-blue;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-blue;
|
||||
background: linear-gradient(180deg, rgba(57, 113, 255, 0.20) 9.34%, rgba(57, 113, 255, 0.03) 44.16%, rgba(57, 113, 255, 0.00) 77.18%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(57, 113, 255, 0.2) 9.34%,
|
||||
rgba(57, 113, 255, 0.03) 44.16%,
|
||||
rgba(57, 113, 255, 0) 77.18%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,9 +118,18 @@
|
||||
fill: @tab-mint;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-mint;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-mint;
|
||||
background: linear-gradient(180deg, rgba(75, 255, 169, 0.20) 9.34%, rgba(75, 255, 169, 0.03) 44.16%, rgba(75, 255, 169, 0.00) 77.18%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(75, 255, 169, 0.2) 9.34%,
|
||||
rgba(75, 255, 169, 0.03) 44.16%,
|
||||
rgba(75, 255, 169, 0) 77.18%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,9 +138,18 @@
|
||||
fill: @tab-cyan;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-cyan;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-cyan;
|
||||
background: linear-gradient(180deg, rgba(75, 223, 255, 0.20) 9.34%, rgba(75, 223, 255, 0.03) 44.16%, rgba(58, 186, 214, 0.00) 86.79%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(75, 223, 255, 0.2) 9.34%,
|
||||
rgba(75, 223, 255, 0.03) 44.16%,
|
||||
rgba(58, 186, 214, 0) 86.79%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,9 +158,18 @@
|
||||
fill: @tab-white;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-white;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-white;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.20) 9.34%, rgba(255, 255, 255, 0.03) 44.16%, rgba(255, 255, 255, 0.00) 86.79%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.2) 9.34%,
|
||||
rgba(255, 255, 255, 0.03) 44.16%,
|
||||
rgba(255, 255, 255, 0) 86.79%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,9 +178,18 @@
|
||||
fill: @tab-violet;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-violet;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-violet;
|
||||
background: linear-gradient(180deg, rgba(186, 118, 255, 0.20) 9.34%, rgba(186, 118, 255, 0.03) 44.16%, rgba(186, 118, 255, 0.00) 86.79%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(186, 118, 255, 0.2) 9.34%,
|
||||
rgba(186, 118, 255, 0.03) 44.16%,
|
||||
rgba(186, 118, 255, 0) 86.79%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,9 +198,18 @@
|
||||
fill: @tab-pink;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
color: @tab-pink;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid @tab-pink;
|
||||
background: linear-gradient(180deg, rgba(255, 136, 165, 0.20) 9.34%, rgba(255, 136, 165, 0.03) 44.16%, rgba(255, 136, 165, 0.00) 86.79%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 136, 165, 0.2) 9.34%,
|
||||
rgba(255, 136, 165, 0.03) 44.16%,
|
||||
rgba(255, 136, 165, 0) 86.79%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,19 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
||||
})();
|
||||
}
|
||||
|
||||
renderTab(screen: Screen, activeScreenId: string, index: number): any {
|
||||
renderTabIcon = (screen: Screen): React.ReactNode => {
|
||||
const tabIcon = screen.getTabIcon();
|
||||
if (tabIcon === "default") {
|
||||
return <SquareIcon className="icon left-icon" />;
|
||||
}
|
||||
return (
|
||||
<div className="icon">
|
||||
<i className={`fa-sharp fa-solid fa-${tabIcon}`}></i>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
renderTab(screen: Screen, activeScreenId: string, index: number): JSX.Element {
|
||||
let tabIndex = null;
|
||||
if (index + 1 <= 9) {
|
||||
tabIndex = <div className="tab-index">{renderCmdText(String(index + 1))}</div>;
|
||||
@ -132,7 +144,7 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
||||
onClick={() => this.handleSwitchScreen(screen.screenId)}
|
||||
onContextMenu={(event) => this.openScreenSettings(event, screen)}
|
||||
>
|
||||
<SquareIcon className="icon left-icon" />
|
||||
{this.renderTabIcon(screen)}
|
||||
<div className="tab-name truncate">
|
||||
{archived}
|
||||
{webShared}
|
||||
@ -149,7 +161,7 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
let screen: Screen = null;
|
||||
let screen: Screen | null = null;
|
||||
let index = 0;
|
||||
let showingScreens = [];
|
||||
let activeScreenId = session.activeScreenId.get();
|
||||
|
@ -95,6 +95,18 @@ const MaxFontSize = 15;
|
||||
const InputChunkSize = 500;
|
||||
const RemoteColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"];
|
||||
const TabColors = ["red", "orange", "yellow", "green", "mint", "cyan", "blue", "violet", "pink", "white"];
|
||||
const TabIcons = [
|
||||
"sparkle",
|
||||
"fire",
|
||||
"ghost",
|
||||
"cloud",
|
||||
"compass",
|
||||
"crown",
|
||||
"droplet",
|
||||
"graduation-cap",
|
||||
"heart",
|
||||
"file",
|
||||
];
|
||||
|
||||
// @ts-ignore
|
||||
const VERSION = __WAVETERM_VERSION__;
|
||||
@ -469,6 +481,15 @@ class Screen {
|
||||
return tabColor;
|
||||
}
|
||||
|
||||
getTabIcon(): string {
|
||||
let tabIcon = "default";
|
||||
let screenOpts = this.opts.get();
|
||||
if (screenOpts != null && !isBlank(screenOpts.tabicon)) {
|
||||
tabIcon = screenOpts.tabicon;
|
||||
}
|
||||
return tabIcon;
|
||||
}
|
||||
|
||||
getCurRemoteInstance(): RemoteInstanceType {
|
||||
let session = GlobalModel.getSessionById(this.sessionId);
|
||||
let rptr = this.curRemote.get();
|
||||
@ -3492,7 +3513,7 @@ class Model {
|
||||
submitCommand(
|
||||
metaCmd: string,
|
||||
metaSubCmd: string,
|
||||
args: string[],
|
||||
args: string[] | null,
|
||||
kwargs: Record<string, string>,
|
||||
interactive: boolean
|
||||
): Promise<CommandRtnType> {
|
||||
@ -3513,7 +3534,7 @@ class Model {
|
||||
pk.kwargs,
|
||||
pk.interactive
|
||||
);
|
||||
*/
|
||||
*/
|
||||
return this.submitCommandPacket(pk, interactive);
|
||||
}
|
||||
|
||||
@ -3950,10 +3971,10 @@ class CommandRunner {
|
||||
|
||||
screenSetSettings(
|
||||
screenId: string,
|
||||
settings: { tabcolor?: string; name?: string; sharename?: string },
|
||||
settings: { tabcolor?: string; tabicon?: string; name?: string; sharename?: string },
|
||||
interactive: boolean
|
||||
): Promise<CommandRtnType> {
|
||||
let kwargs = Object.assign({}, settings);
|
||||
let kwargs: { [key: string]: any } = Object.assign({}, settings);
|
||||
kwargs["nohist"] = "1";
|
||||
kwargs["screen"] = screenId;
|
||||
return GlobalModel.submitCommand("screen", "set", null, kwargs, interactive);
|
||||
@ -4169,6 +4190,7 @@ export {
|
||||
Screen,
|
||||
riToRPtr,
|
||||
TabColors,
|
||||
TabIcons,
|
||||
RemoteColors,
|
||||
getTermPtyData,
|
||||
RemotesModalModel,
|
||||
|
@ -50,6 +50,7 @@ type LineType = {
|
||||
|
||||
type ScreenOptsType = {
|
||||
tabcolor?: string;
|
||||
tabicon?: string;
|
||||
pterm?: string;
|
||||
};
|
||||
|
||||
@ -167,7 +168,7 @@ type FeCmdPacketType = {
|
||||
type: string;
|
||||
metacmd: string;
|
||||
metasubcmd?: string;
|
||||
args: string[];
|
||||
args: string[] | null;
|
||||
kwargs: Record<string, string>;
|
||||
rawstr?: string;
|
||||
uicontext: UIContextType;
|
||||
@ -632,6 +633,7 @@ type FileInfoType = {
|
||||
|
||||
type ExtBlob = Blob & {
|
||||
notFound: boolean;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
type ExtFile = File & {
|
||||
|
@ -11,6 +11,7 @@
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true
|
||||
"isolatedModules": true,
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ const (
|
||||
)
|
||||
|
||||
var ColorNames = []string{"yellow", "blue", "pink", "mint", "cyan", "violet", "orange", "green", "red", "white"}
|
||||
var TabIcons = []string{"sparkle", "fire", "ghost", "cloud", "compass", "crown", "droplet", "graduation-cap", "heart", "file"}
|
||||
var RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"}
|
||||
var RemoteSetArgs = []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}
|
||||
|
||||
@ -81,6 +82,7 @@ var GlobalCmds = []string{"session", "screen", "remote", "set", "client", "telem
|
||||
|
||||
var SetVarNameMap map[string]string = map[string]string{
|
||||
"tabcolor": "screen.tabcolor",
|
||||
"tabicon": "screen.tabicon",
|
||||
"pterm": "screen.pterm",
|
||||
"anchor": "screen.anchor",
|
||||
"focus": "screen.focus",
|
||||
@ -91,7 +93,7 @@ var SetVarScopes = []SetVarScope{
|
||||
SetVarScope{ScopeName: "global", VarNames: []string{}},
|
||||
SetVarScope{ScopeName: "client", VarNames: []string{"telemetry"}},
|
||||
SetVarScope{ScopeName: "session", VarNames: []string{"name", "pos"}},
|
||||
SetVarScope{ScopeName: "screen", VarNames: []string{"name", "tabcolor", "pos", "pterm", "anchor", "focus", "line"}},
|
||||
SetVarScope{ScopeName: "screen", VarNames: []string{"name", "tabcolor", "tabicon", "pos", "pterm", "anchor", "focus", "line"}},
|
||||
SetVarScope{ScopeName: "line", VarNames: []string{}},
|
||||
// connection = remote, remote = remoteinstance
|
||||
SetVarScope{ScopeName: "connection", VarNames: []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}},
|
||||
@ -757,6 +759,16 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
|
||||
varsUpdated = append(varsUpdated, "tabcolor")
|
||||
setNonAnchor = true
|
||||
}
|
||||
if pk.Kwargs["tabicon"] != "" {
|
||||
icon := pk.Kwargs["tabicon"]
|
||||
err = validateIcon(icon, "screen tabicon")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateMap[sstore.ScreenField_TabIcon] = icon
|
||||
varsUpdated = append(varsUpdated, "tabicon")
|
||||
setNonAnchor = true
|
||||
}
|
||||
if pk.Kwargs["pos"] != "" {
|
||||
varsUpdated = append(varsUpdated, "pos")
|
||||
setNonAnchor = true
|
||||
@ -806,7 +818,7 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
|
||||
}
|
||||
}
|
||||
if len(varsUpdated) == 0 {
|
||||
return nil, fmt.Errorf("/screen:set no updates, can set %s", formatStrs([]string{"name", "pos", "tabcolor", "focus", "anchor", "line", "sharename"}, "or", false))
|
||||
return nil, fmt.Errorf("/screen:set no updates, can set %s", formatStrs([]string{"name", "pos", "tabcolor", "tabicon", "focus", "anchor", "line", "sharename"}, "or", false))
|
||||
}
|
||||
screen, err := sstore.UpdateScreen(ctx, ids.ScreenId, updateMap)
|
||||
if err != nil {
|
||||
@ -1978,6 +1990,15 @@ func validateColor(color string, typeStr string) error {
|
||||
return fmt.Errorf("invalid %s, valid colors are: %s", typeStr, formatStrs(ColorNames, "or", false))
|
||||
}
|
||||
|
||||
func validateIcon(icon string, typeStr string) error {
|
||||
for _, c := range TabIcons {
|
||||
if icon == c {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("invalid %s, valid icons are: %s", typeStr, formatStrs(TabIcons, "or", false))
|
||||
}
|
||||
|
||||
func validateRemoteColor(color string, typeStr string) error {
|
||||
for _, c := range RemoteColorNames {
|
||||
if color == c {
|
||||
|
@ -1709,6 +1709,7 @@ const (
|
||||
ScreenField_SelectedLine = "selectedline" // int
|
||||
ScreenField_Focus = "focustype" // string
|
||||
ScreenField_TabColor = "tabcolor" // string
|
||||
ScreenField_TabIcon = "tabicon" // string
|
||||
ScreenField_PTerm = "pterm" // string
|
||||
ScreenField_Name = "name" // string
|
||||
ScreenField_ShareName = "sharename" // string
|
||||
@ -1743,6 +1744,10 @@ func UpdateScreen(ctx context.Context, screenId string, editMap map[string]inter
|
||||
query = `UPDATE screen SET screenopts = json_set(screenopts, '$.tabcolor', ?) WHERE screenid = ?`
|
||||
tx.Exec(query, tabColor, screenId)
|
||||
}
|
||||
if tabIcon, found := editMap[ScreenField_TabIcon]; found {
|
||||
query = `UPDATE screen SET screenopts = json_set(screenopts, '$.tabicon', ?) WHERE screenid = ?`
|
||||
tx.Exec(query, tabIcon, screenId)
|
||||
}
|
||||
if pterm, found := editMap[ScreenField_PTerm]; found {
|
||||
query = `UPDATE screen SET screenopts = json_set(screenopts, '$.pterm', ?) WHERE screenid = ?`
|
||||
tx.Exec(query, pterm, screenId)
|
||||
|
@ -433,6 +433,7 @@ func (h *HistoryItemType) FromMap(m map[string]interface{}) bool {
|
||||
|
||||
type ScreenOptsType struct {
|
||||
TabColor string `json:"tabcolor,omitempty"`
|
||||
TabIcon string `json:"tabicon,omitempty"`
|
||||
PTerm string `json:"pterm,omitempty"`
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user