mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35cf4b6a3b | |||
| 8fdcf10cae | |||
| 6336f87cf2 | |||
| 899347e90f | |||
| 39ee41921f | |||
| 2913babea7 | |||
| a787a1a934 | |||
| bf447c60ce | |||
| c1bd5f5765 | |||
| b97c7e186d | |||
| 0442fc84d4 | |||
| 27efae3ad5 | |||
| 4d8c2cd242 | |||
| a560499b58 | |||
| e8b489bcd9 | |||
| 31c12c4705 | |||
| 787bcbc9d8 | |||
| 985304c9aa | |||
| 13cbfdeb6e | |||
| 53290b47e1 | |||
| 4bdd092d33 | |||
| fb2b3dfc6a | |||
| fa27964d49 | |||
| 95132011e4 | |||
| dae4210b72 | |||
| e394ba70a6 | |||
| 483b2eff6f | |||
| 2f4a6fa6e2 | |||
| 4af1b19ffd | |||
| 8b1d930cd6 | |||
| a4e8648968 | |||
| 429ea37419 | |||
| a8f3e356f6 | |||
| 85235aaad8 | |||
| 619cfe4931 | |||
| 11f1c3bb70 | |||
| 8d140c9920 | |||
| d923de412a | |||
| 15485d7235 | |||
| 59aef86e77 | |||
| 205a9d1e2e | |||
| 5353f40a20 | |||
| d3139d51cf | |||
| 5c931de40c | |||
| 497a96bfc2 | |||
| f86f010a34 | |||
| 427447c592 | |||
| 1fe5d425da | |||
| a395043a1c | |||
| ff9f9b43b6 | |||
| 98ba0b46ac | |||
| fb3253d018 | |||
| 8d91d156aa | |||
| b7eec15853 | |||
| 38ff6183a2 | |||
| 1b1e6edaee | |||
| f4aeb92d32 | |||
| b780d5b244 | |||
| d81a6ed480 | |||
| d8393ae50b | |||
| 79deb01ea4 | |||
| 12f9a50686 | |||
| 239933be3e | |||
| 4692cf698b | |||
| 907e87c217 | |||
| f97bb699a1 | |||
| 077545ed93 | |||
| 60d0f0e976 | |||
| cd4671e5bb | |||
| d8a714dd4a | |||
| e0fa1b2560 | |||
| 2dffcc9237 | |||
| 70090b8684 | |||
| 3f8952caf4 | |||
| 15a2486e94 | |||
| 38aa7016b8 | |||
| cff353ca94 | |||
| 5b440ace65 | |||
| 87de0dbc44 | |||
| 74839b8e1b | |||
| 8a24953a88 | |||
| a95854514a | |||
| 229fcf8cd4 | |||
| 83542ddab8 | |||
| b520242f04 | |||
| bc73ed1b6c | |||
| c4647945a4 | |||
| 3db40b1c63 | |||
| f6d7c37915 | |||
| a41e866b1f | |||
| a772715ae4 | |||
| d32e2aae05 | |||
| 3f9d52a7e4 | |||
| a239a8d70e | |||
| a1bddd971f | |||
| 722318f57d | |||
| 3b633328a1 |
+5
-1
@@ -18,11 +18,13 @@
|
||||
"appId": "dev.commandline.waveterm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lexical/react": "^0.14.3",
|
||||
"@monaco-editor/react": "^4.5.1",
|
||||
"@table-nav/core": "^0.0.7",
|
||||
"@table-nav/react": "^0.0.7",
|
||||
"@tanstack/match-sorter-utils": "^8.8.4",
|
||||
"@tanstack/react-table": "^8.10.3",
|
||||
"@withfig/autocomplete": "^2.652.3",
|
||||
"autobind-decorator": "^2.4.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"classnames": "^2.3.1",
|
||||
@@ -31,6 +33,7 @@
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-updater": "^6.1.8",
|
||||
"framer-motion": "^10.16.16",
|
||||
"lexical": "^0.14.3",
|
||||
"mobx": "6.12",
|
||||
"mobx-react": "^7.5.0",
|
||||
"monaco-editor": "^0.44.0",
|
||||
@@ -50,8 +53,8 @@
|
||||
"uuid": "^9.0.0",
|
||||
"winston": "^3.8.2",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-web-links": "^0.9.0",
|
||||
"xterm-addon-serialize": "^0.11.0",
|
||||
"xterm-addon-web-links": "^0.9.0",
|
||||
"xterm-addon-webgl": "^0.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -77,6 +80,7 @@
|
||||
"@types/throttle-debounce": "^5.0.1",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@types/webpack-env": "^1.18.3",
|
||||
"@withfig/autocomplete-types": "^1.30.0",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-jsx-control-statements": "^4.1.2",
|
||||
"copy-webpack-plugin": "^12.0.0",
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
--app-accent-color: rgb(75, 166, 57);
|
||||
--app-accent-bg-color: rgba(75, 166, 57, 0.2);
|
||||
--app-text-color: rgb(0, 0, 0);
|
||||
--app-text-primary-color: rgb(0, 0, 0, 0.9);
|
||||
--app-text-secondary-color: rgb(0, 0, 0, 0.7);
|
||||
--app-text-primary-color: rgb(23, 23, 23);
|
||||
--app-text-secondary-color: rgb(76, 76, 76);
|
||||
--app-border-color: rgb(139 145 138);
|
||||
--app-panel-bg-color: rgb(224, 224, 224);
|
||||
--app-panel-bg-color-dev: rgb(224, 224, 224);
|
||||
|
||||
+3
-11
@@ -20,7 +20,7 @@ import { MainSideBar } from "./sidebar/main";
|
||||
import { RightSideBar } from "./sidebar/right";
|
||||
import { DisconnectedModal, ClientStopModal } from "@/modals";
|
||||
import { ModalsProvider } from "@/modals/provider";
|
||||
import { Button, TermStyleBlock } from "@/elements";
|
||||
import { Button } from "@/elements";
|
||||
import { ErrorBoundary } from "@/common/error/errorboundary";
|
||||
import cn from "classnames";
|
||||
import "./app.less";
|
||||
@@ -124,11 +124,9 @@ class App extends React.Component<{}, {}> {
|
||||
const rightSidebarCollapsed = GlobalModel.rightSidebarModel.getCollapsed();
|
||||
const activeMainView = GlobalModel.activeMainView.get();
|
||||
const lightDarkClass = GlobalModel.isDarkTheme.get() ? "is-dark" : "is-light";
|
||||
const termTheme = GlobalModel.getTermTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`version- + ${renderVersion}`}
|
||||
key={"version-" + renderVersion}
|
||||
id="main"
|
||||
className={cn(
|
||||
"platform-" + platform,
|
||||
@@ -148,17 +146,11 @@ class App extends React.Component<{}, {}> {
|
||||
<If condition={GlobalModel.isDev && rightSidebarCollapsed && activeMainView == "session"}>
|
||||
<div className="right-sidebar-triggers">
|
||||
<Button className="secondary ghost right-sidebar-trigger" onClick={this.openRightSidebar}>
|
||||
<i className="fa-sharp fa-regular fa-lightbulb"></i>
|
||||
<i className="fa-sharp fa-solid fa-sidebar-flip"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</If>
|
||||
<div ref={this.mainContentRef} className="main-content">
|
||||
<TermStyleBlock
|
||||
scope="main"
|
||||
termTheme={termTheme}
|
||||
themeSrcEl={this.mainContentRef.current}
|
||||
themeKey="main"
|
||||
/>
|
||||
<MainSideBar parentRef={this.mainContentRef} />
|
||||
<ErrorBoundary>
|
||||
<PluginsView />
|
||||
|
||||
@@ -68,3 +68,4 @@ export const ErrorCode_InvalidCwd = "ERRCWD";
|
||||
export const InputAuxView_History = "history";
|
||||
export const InputAuxView_Info = "info";
|
||||
export const InputAuxView_AIChat = "aichat";
|
||||
export const InputAuxView_Suggestions = "suggestions";
|
||||
|
||||
@@ -6,14 +6,14 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel, getApi } from "@/models";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "@/models";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "@/common/elements";
|
||||
import { commandRtnHandler, isBlank } from "@/util/util";
|
||||
import { getTermThemes } from "@/util/themeutil";
|
||||
import * as appconst from "@/app/appconst";
|
||||
import { MainView } from "@/common/elements/mainview";
|
||||
|
||||
import "./clientsettings.less";
|
||||
import { MainView } from "../common/elements/mainview";
|
||||
|
||||
class ClientSettingsKeybindings extends React.Component<{}, {}> {
|
||||
componentDidMount() {
|
||||
@@ -70,20 +70,20 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
return;
|
||||
}
|
||||
const prtn = GlobalCommandRunner.setTheme(themeSource, false);
|
||||
getApi().setNativeThemeSource(themeSource);
|
||||
GlobalModel.getElectronApi().setNativeThemeSource(themeSource);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeTermTheme(theme: string): void {
|
||||
// For main terminal theme, the key is main, otherwise it's either
|
||||
// For global terminal theme, the key is global, otherwise it's either
|
||||
// sessionId or screenId.
|
||||
const currTheme = GlobalModel.getTermTheme()["main"];
|
||||
const currTheme = GlobalModel.getTermTheme()["global"];
|
||||
if (currTheme == theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prtn = GlobalCommandRunner.setMainTermTheme(theme, false);
|
||||
const prtn = GlobalCommandRunner.setGlobalTermTheme(theme, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,13 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
prtn = GlobalCommandRunner.releaseCheckAutoOff(false);
|
||||
}
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
getApi().changeAutoUpdate(val);
|
||||
GlobalModel.getElectronApi().changeAutoUpdate(val);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeAutocompleteEnabled(val: boolean): void {
|
||||
const prtn: Promise<CommandRtnType> = GlobalCommandRunner.setAutocompleteEnabled(val);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
getFontSizes(): DropdownItem[] {
|
||||
@@ -208,7 +214,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
const curFontFamily = GlobalModel.getTermFontFamily();
|
||||
const curTheme = GlobalModel.getThemeSource();
|
||||
const termThemes = getTermThemes(GlobalModel.termThemes, "Wave Default");
|
||||
const currTermTheme = GlobalModel.getTermTheme()["main"] ?? termThemes[0].label;
|
||||
const currTermTheme = GlobalModel.getTermTheme()["global"] ?? termThemes[0].label;
|
||||
|
||||
return (
|
||||
<MainView className="clientsettings-view" title="Client Settings" onClose={this.handleClose}>
|
||||
@@ -354,6 +360,15 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Command Autocomplete</div>
|
||||
<div className="settings-input">
|
||||
<Toggle
|
||||
checked={cdata.clientopts.autocompleteenabled ?? false}
|
||||
onChange={this.handleChangeAutocompleteEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsError errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
</MainView>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
width: 100%;
|
||||
border: 2px solid var(--form-element-border-color);
|
||||
border-radius: 6px;
|
||||
line-height: 20px;
|
||||
line-height: 22px;
|
||||
background: var(--form-element-bg-color);
|
||||
|
||||
&.no-label {
|
||||
@@ -63,14 +63,11 @@
|
||||
display: flex;
|
||||
min-width: 120px;
|
||||
padding: 5px 8px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
border-radius: 6px;
|
||||
|
||||
i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&-highlighted,
|
||||
&:hover {
|
||||
background: rgba(241, 246, 243, 0.08);
|
||||
|
||||
@@ -163,7 +163,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
if (isOpen) {
|
||||
const option = options[highlightedIndex];
|
||||
if (option) {
|
||||
this.handleSelect(option, undefined);
|
||||
this.handleSelect(option.value, undefined);
|
||||
}
|
||||
} else {
|
||||
this.toggleDropdown();
|
||||
@@ -185,19 +185,18 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
handleKeyDown(event: React.KeyboardEvent) {}
|
||||
|
||||
@boundMethod
|
||||
handleSelect({ value, noop }: DropdownItem, event?: React.MouseEvent | React.KeyboardEvent) {
|
||||
handleSelect(value: string, event?: React.MouseEvent | React.KeyboardEvent) {
|
||||
const { onChange } = this.props;
|
||||
if (event) {
|
||||
event.stopPropagation(); // This stops the event from bubbling up to the wrapper
|
||||
}
|
||||
|
||||
if (!("value" in this.props)) {
|
||||
this.setState({ internalValue: value });
|
||||
}
|
||||
onChange(value);
|
||||
this.setState({ isOpen: false, isTouched: true });
|
||||
this.unregisterKeybindings();
|
||||
|
||||
if (!("value" in this.props) && !noop) {
|
||||
this.setState({ internalValue: value });
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@@ -246,11 +245,10 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
className={cn("wave-dropdown-item unselectable", {
|
||||
"wave-dropdown-item-highlighted": index === highlightedIndex,
|
||||
})}
|
||||
onClick={(e) => this.handleSelect(option, e)}
|
||||
onClick={(e) => this.handleSelect(option.value, e)}
|
||||
onMouseEnter={() => this.setState({ highlightedIndex: index })}
|
||||
onMouseLeave={() => this.setState({ highlightedIndex: -1 })}
|
||||
>
|
||||
{option.icon && <span className="wave-dropdown-item-icon">{option.icon}</span>}
|
||||
{option.label}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -18,5 +18,4 @@ export { Toggle } from "./toggle";
|
||||
export { Tooltip } from "./tooltip";
|
||||
export { TabIcon } from "./tabicon";
|
||||
export { DatePicker } from "./datepicker";
|
||||
export { TermStyleBlock } from "./termstyleblock";
|
||||
export { CopyButton } from "./copybutton";
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { isBlank } from "@/util/util";
|
||||
|
||||
const VALID_CSS_VARIABLES = [
|
||||
"--term-black",
|
||||
"--term-red",
|
||||
"--term-green",
|
||||
"--term-yellow",
|
||||
"--term-blue",
|
||||
"--term-magenta",
|
||||
"--term-cyan",
|
||||
"--term-white",
|
||||
"--term-bright-black",
|
||||
"--term-bright-red",
|
||||
"--term-bright-green",
|
||||
"--term-bright-yellow",
|
||||
"--term-bright-blue",
|
||||
"--term-bright-magenta",
|
||||
"--term-bright-cyan",
|
||||
"--term-bright-white",
|
||||
"--term-gray",
|
||||
"--term-cmdtext",
|
||||
"--term-foreground",
|
||||
"--term-background",
|
||||
"--term-selection-background",
|
||||
"--term-cursor-accent",
|
||||
];
|
||||
|
||||
@mobxReact.observer
|
||||
class TermStyleBlock extends React.Component<
|
||||
{ scope: "main" | "session" | "screen"; themeSrcEl: HTMLElement; themeKey: string; termTheme: TermThemeType },
|
||||
{ styleRules: string }
|
||||
> {
|
||||
styleRules: OV<string> = mobx.observable.box("", { name: "StyleBlock-styleRules" });
|
||||
theme: string;
|
||||
|
||||
componentDidMount(): void {
|
||||
this.loadThemeStyles();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps): void {
|
||||
const { themeKey, termTheme } = this.props;
|
||||
const currTheme = termTheme[themeKey];
|
||||
if (themeKey !== prevProps.themeKey || currTheme !== this.theme) {
|
||||
this.loadThemeStyles();
|
||||
}
|
||||
}
|
||||
|
||||
isValidCSSColor(color) {
|
||||
const element = document.createElement("div");
|
||||
element.style.color = color;
|
||||
return element.style.color !== "";
|
||||
}
|
||||
|
||||
isValidTermCSSVariable(key, value) {
|
||||
const cssVarName = `--term-${key}`;
|
||||
console.log("cssVarName", cssVarName, "value", value);
|
||||
return VALID_CSS_VARIABLES.includes(cssVarName);
|
||||
}
|
||||
|
||||
camelCaseToKebabCase(str) {
|
||||
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
||||
}
|
||||
|
||||
loadThemeStyles() {
|
||||
const { themeKey, termTheme, scope } = this.props;
|
||||
const currTheme = termTheme[themeKey];
|
||||
|
||||
if (currTheme && currTheme !== this.theme && currTheme) {
|
||||
const rtn = GlobalModel.getTermThemeJson(currTheme);
|
||||
rtn.then((termThemeJson) => {
|
||||
if (termThemeJson && typeof termThemeJson === "object") {
|
||||
const styleProperties = Object.entries(termThemeJson)
|
||||
.filter(([key, value]) => {
|
||||
const cssVarName = `--term-${this.camelCaseToKebabCase(key)}`;
|
||||
return VALID_CSS_VARIABLES.includes(cssVarName) && this.isValidCSSColor(value);
|
||||
})
|
||||
.map(([key, value]) => `--term-${key}: ${value};`)
|
||||
.join(" ");
|
||||
|
||||
mobx.action(() => {
|
||||
this.styleRules.set(`:root { ${styleProperties} }`);
|
||||
GlobalModel.termThemeSrcEls.set(scope, this.props.themeSrcEl);
|
||||
})();
|
||||
GlobalModel.bumpTermRenderVersion();
|
||||
this.theme = currTheme;
|
||||
} else {
|
||||
console.error("termThemeJson is not an object:", termThemeJson);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error("error loading theme styles:", error);
|
||||
});
|
||||
} else {
|
||||
mobx.action(() => {
|
||||
this.styleRules.set("");
|
||||
GlobalModel.termThemeSrcEls.set(scope, null);
|
||||
})();
|
||||
this.theme = currTheme;
|
||||
GlobalModel.bumpTermRenderVersion();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (isBlank(this.styleRules.get())) {
|
||||
return null;
|
||||
}
|
||||
return <style>{this.styleRules.get()}</style>;
|
||||
}
|
||||
}
|
||||
|
||||
export { TermStyleBlock };
|
||||
@@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { GlobalModel, getApi } from "@/models";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { Modal, LinkButton } from "@/elements";
|
||||
import * as util from "@/util/util";
|
||||
import * as appconst from "@/app/appconst";
|
||||
@@ -26,7 +26,7 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
|
||||
@boundMethod
|
||||
updateApp(): void {
|
||||
getApi().installAppUpdate();
|
||||
GlobalModel.getElectronApi().installAppUpdate();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
|
||||
@@ -58,4 +58,8 @@
|
||||
.term-prompt-python {
|
||||
color: var(--term-magenta);
|
||||
}
|
||||
|
||||
.term-prompt-k8s {
|
||||
color: var(--term-magenta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ class Prompt extends React.Component<
|
||||
let branchElem = null;
|
||||
let pythonElem = null;
|
||||
let condaElem = null;
|
||||
let k8sElem = null;
|
||||
if (!isBlank(festate["PROMPTVAR_GITBRANCH"])) {
|
||||
const branchName = festate["PROMPTVAR_GITBRANCH"];
|
||||
branchElem = (
|
||||
@@ -171,9 +172,19 @@ class Prompt extends React.Component<
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (!isBlank(festate["K8SCONTEXT"])) {
|
||||
const k8sContext = festate["K8SCONTEXT"];
|
||||
const k8sNs = festate["K8SNAMESPACE"];
|
||||
k8sElem = (
|
||||
<span title="k8s context:namespace" className="term-prompt-k8s">
|
||||
k8s:({k8sContext}
|
||||
{isBlank(k8sNs) ? "" : ":" + k8sNs}){" "}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className={termClassNames}>
|
||||
{remoteElem} {cwdElem} {branchElem} {condaElem} {pythonElem}
|
||||
{remoteElem} {cwdElem} {branchElem} {condaElem} {pythonElem} {k8sElem}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -649,6 +649,11 @@ class LineContainer extends React.Component<{ historyId: string; width: number }
|
||||
this.line = hvm.getLineById(this.historyItem.lineid);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
GlobalModel.bumpTermRenderVersion();
|
||||
GlobalModel.termThemeSrcEl.set(null);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleHeightChange(lineNum: number, newHeight: number, oldHeight: number): void {
|
||||
return;
|
||||
|
||||
@@ -665,6 +665,83 @@ class LineCmd extends React.Component<
|
||||
};
|
||||
};
|
||||
|
||||
@boundMethod
|
||||
handleContextMenu(e: React.MouseEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let { line, screen } = this.props;
|
||||
const containerType = screen.getContainerType();
|
||||
const isMainContainer = containerType == appconst.LineContainer_Main;
|
||||
const cmd = screen.getCmd(line);
|
||||
let menu: ContextMenuItem[] = [
|
||||
{ role: "copy", label: "Copy", type: "normal" },
|
||||
{ role: "paste", label: "Paste", type: "normal" },
|
||||
{ type: "separator" },
|
||||
{ label: "Copy Command", click: () => this.copyCommandStr() },
|
||||
];
|
||||
const isTerminal = isBlank(line.renderer) || line.renderer == "terminal";
|
||||
if (isTerminal) {
|
||||
menu.push({ label: "Copy Visible Output", click: () => this.copyOutput(false) });
|
||||
menu.push({ label: "Copy Full Output", click: () => this.copyOutput(true) });
|
||||
}
|
||||
if (isMainContainer) {
|
||||
menu.push({ type: "separator" });
|
||||
const isMinimized = line.linestate["wave:min"];
|
||||
if (isMinimized) {
|
||||
menu.push({
|
||||
label: "Show Block Output",
|
||||
click: () => GlobalCommandRunner.lineMinimize(line.lineid, false, true),
|
||||
});
|
||||
} else {
|
||||
menu.push({
|
||||
label: "Hide Block Output",
|
||||
click: () => GlobalCommandRunner.lineMinimize(line.lineid, true, true),
|
||||
});
|
||||
}
|
||||
if (cmd?.isRunning()) {
|
||||
menu.push({ type: "separator" });
|
||||
menu.push({
|
||||
label: "Send Signal",
|
||||
type: "submenu",
|
||||
submenu: [
|
||||
{ label: "SIGINT", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGINT", true) },
|
||||
{ label: "SIGTERM", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGTERM", true) },
|
||||
{ label: "SIGKILL", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGKILL", true) },
|
||||
{ type: "separator" },
|
||||
{ label: "SIGUSR1", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGUSR1", true) },
|
||||
{ label: "SIGUSR2", click: () => GlobalCommandRunner.lineSignal(line.lineid, "SIGUSR2", true) },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
menu.push({ type: "separator" });
|
||||
menu.push({ label: "Restart Line", click: () => GlobalCommandRunner.lineRestart(line.lineid, true) });
|
||||
menu.push({ type: "separator" });
|
||||
menu.push({ label: "Delete Block", click: () => GlobalCommandRunner.lineDelete(line.lineid, true) });
|
||||
}
|
||||
GlobalModel.contextMenuModel.showContextMenu(menu, { x: e.clientX, y: e.clientY });
|
||||
}
|
||||
|
||||
copyCommandStr() {
|
||||
const { line, screen } = this.props;
|
||||
const cmd: Cmd = screen.getCmd(line);
|
||||
if (cmd != null) {
|
||||
navigator.clipboard.writeText(cmd.getCmdStr());
|
||||
}
|
||||
}
|
||||
|
||||
copyOutput(fullOutput: boolean) {
|
||||
const { line, screen } = this.props;
|
||||
let termWrap = screen.getTermWrap(line.lineid);
|
||||
if (termWrap == null) {
|
||||
return;
|
||||
}
|
||||
let outputStr = termWrap.getOutput(fullOutput);
|
||||
if (fullOutput != null) {
|
||||
navigator.clipboard.writeText(outputStr);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { screen, line, width, staticRender, visible } = this.props;
|
||||
const isVisible = visible.get();
|
||||
@@ -750,6 +827,7 @@ class LineCmd extends React.Component<
|
||||
data-lineid={line.lineid}
|
||||
data-linenum={line.linenum}
|
||||
data-screenid={line.screenid}
|
||||
onContextMenu={this.handleContextMenu}
|
||||
>
|
||||
<If condition={isSelected || cmdError}>
|
||||
<div key="mask" className={cn("line-mask", { "error-mask": cmdError })}></div>
|
||||
|
||||
@@ -22,6 +22,23 @@
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
}
|
||||
|
||||
.rsb-modes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px 10px;
|
||||
gap: 5px;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
|
||||
.icon-container {
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
background-color: var(--app-selected-mask-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// Copyright 2023-2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import dayjs from "dayjs";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { ResizableSidebar, Button } from "@/elements";
|
||||
import { WaveBookDisplay } from "./wavebook";
|
||||
|
||||
import "./right.less";
|
||||
|
||||
@@ -55,6 +57,14 @@ class KeybindDevPane extends React.Component<{}, {}> {
|
||||
|
||||
@mobxReact.observer
|
||||
class RightSideBar extends React.Component<RightSideBarProps, {}> {
|
||||
mode: OV<string> = mobx.observable.box(null, { name: "RightSideBar-mode" });
|
||||
|
||||
setMode(mode: string) {
|
||||
mobx.action(() => {
|
||||
this.mode.set(mode);
|
||||
})();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ResizableSidebar
|
||||
@@ -71,9 +81,31 @@ class RightSideBar extends React.Component<RightSideBarProps, {}> {
|
||||
<i className="fa-sharp fa-regular fa-xmark"></i>
|
||||
</Button>
|
||||
</div>
|
||||
<If condition={GlobalModel.isDev}>
|
||||
<div className="rsb-modes">
|
||||
<div className="flex-spacer" />
|
||||
<If condition={GlobalModel.isDev}>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("keybind")}
|
||||
>
|
||||
<i className="fa-fw fa-sharp fa-keyboard fa-solid" />
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("wavebook")}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-book-sparkles"></i>
|
||||
</div>
|
||||
</div>
|
||||
<If condition={this.mode.get() == "keybind"}>
|
||||
<KeybindDevPane></KeybindDevPane>
|
||||
</If>
|
||||
<If condition={this.mode.get() == "wavebook"}>
|
||||
<WaveBookDisplay></WaveBookDisplay>
|
||||
</If>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ResizableSidebar>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import { GlobalModel } from "@/models";
|
||||
|
||||
import * as lexical from "lexical";
|
||||
import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
|
||||
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
||||
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
||||
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
|
||||
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
|
||||
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
|
||||
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
|
||||
import { $convertFromMarkdownString, $convertToMarkdownString, TRANSFORMERS } from "@lexical/markdown";
|
||||
import { CodeNode, CodeHighlightNode } from "@lexical/code";
|
||||
import { LinkNode } from "@lexical/link";
|
||||
import { ListNode, ListItemNode } from "@lexical/list";
|
||||
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
|
||||
import { HorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
|
||||
import { KeybindManager } from "@/util/keyutil";
|
||||
|
||||
const theme = {
|
||||
// Theme styling goes here
|
||||
};
|
||||
|
||||
class WaveBookKeybindings extends React.Component<{}, {}> {
|
||||
componentDidMount(): void {
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
keybindManager.registerKeybinding("pane", "wavebook", "generic:confirm", (waveEvent) => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
componentWillUnmount(): void {
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
keybindManager.unregisterDomain("wavebook");
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WaveBookDisplay extends React.Component<{}, {}> {
|
||||
render() {
|
||||
return "playbooks";
|
||||
}
|
||||
}
|
||||
|
||||
export { WaveBookDisplay };
|
||||
@@ -92,7 +92,7 @@ class AIChat extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
submitChatMessage(messageStr: string) {
|
||||
const curLine = GlobalModel.inputModel.getCurLine();
|
||||
const curLine = GlobalModel.inputModel.curLine;
|
||||
const prtn = GlobalModel.submitChatInfoCommand(messageStr, curLine, false);
|
||||
prtn.then((rtn) => {
|
||||
if (!rtn.success) {
|
||||
|
||||
@@ -4,47 +4,45 @@
|
||||
import * as React from "react";
|
||||
import cn from "classnames";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import "./auxview.less";
|
||||
|
||||
export class AuxiliaryCmdView extends React.Component<
|
||||
{
|
||||
title: string;
|
||||
className?: string;
|
||||
iconClass?: string;
|
||||
titleBarContents?: React.ReactElement[];
|
||||
children?: React.ReactNode;
|
||||
onClose?: React.MouseEventHandler<HTMLDivElement>;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
render() {
|
||||
const { title, className, iconClass, titleBarContents, children, onClose } = this.props;
|
||||
interface AuxiliaryCmdViewProps {
|
||||
title: string;
|
||||
className?: string;
|
||||
iconClass?: string;
|
||||
titleBarContents?: React.ReactElement[];
|
||||
children?: React.ReactNode;
|
||||
onClose?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("auxview", className)}>
|
||||
<div className="auxview-titlebar">
|
||||
<If condition={iconClass != null}>
|
||||
<div className="title-icon">
|
||||
<i className={iconClass} />
|
||||
</div>
|
||||
</If>
|
||||
<div className="title-string">{title}</div>
|
||||
export const AuxiliaryCmdView: React.FC<AuxiliaryCmdViewProps> = observer((props) => {
|
||||
const { title, className, iconClass, titleBarContents, children, onClose } = props;
|
||||
|
||||
<If condition={titleBarContents != null}>{titleBarContents}</If>
|
||||
return (
|
||||
<div className={cn("auxview", className)}>
|
||||
<div className="auxview-titlebar">
|
||||
<If condition={iconClass != null}>
|
||||
<div className="title-icon">
|
||||
<i className={iconClass} />
|
||||
</div>
|
||||
</If>
|
||||
<div className="title-string">{title}</div>
|
||||
|
||||
<div className="flex-spacer"></div>
|
||||
<If condition={titleBarContents != null}>{titleBarContents}</If>
|
||||
|
||||
<If condition={onClose != null}>
|
||||
<div className="close-button" title="Close (ESC)" onClick={onClose}>
|
||||
<i className="fa-sharp fa-solid fa-xmark-large" />
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
<If condition={children != null}>
|
||||
<div className="auxview-content">{children}</div>
|
||||
<div className="flex-spacer"></div>
|
||||
|
||||
<If condition={onClose != null}>
|
||||
<div className="close-button" title="Close (ESC)" onClick={onClose}>
|
||||
<i className="fa-sharp fa-solid fa-xmark-large" />
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
<If condition={children != null}>
|
||||
<div className="auxview-content">{children}</div>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
// TODO: use a generic selector for this
|
||||
&.has-aichat,
|
||||
&.has-history,
|
||||
&.has-info {
|
||||
&.has-info,
|
||||
&.has-suggestions {
|
||||
.base-cmdinput {
|
||||
border-top: 1px solid var(--app-border-color);
|
||||
}
|
||||
@@ -109,9 +110,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
color: var(--app-text-primary-color);
|
||||
background-color: var(--app-bg-color);
|
||||
.textarea,
|
||||
.textarea-ghost {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: var(--termpad) 0;
|
||||
resize: none;
|
||||
overflow: auto;
|
||||
@@ -119,10 +122,21 @@
|
||||
font-family: var(--termfontfamily);
|
||||
line-height: var(--termlineheight);
|
||||
font-size: var(--termfontsize);
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
color: var(--app-text-primary-color);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.textarea-ghost {
|
||||
color: var(--app-text-secondary-color);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
input.history-input {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user