mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d95d45a45 |
+2
-10
@@ -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,
|
||||
@@ -153,12 +151,6 @@ class App extends React.Component<{}, {}> {
|
||||
</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 />
|
||||
|
||||
@@ -64,7 +64,3 @@ export enum StatusIndicatorLevel {
|
||||
|
||||
// matches packet.go
|
||||
export const ErrorCode_InvalidCwd = "ERRCWD";
|
||||
|
||||
export const InputAuxView_History = "history";
|
||||
export const InputAuxView_Info = "info";
|
||||
export const InputAuxView_AIChat = "aichat";
|
||||
|
||||
@@ -76,14 +76,14 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -208,7 +208,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}>
|
||||
|
||||
@@ -6,7 +6,7 @@ import "./button.less";
|
||||
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode;
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
@@ -14,7 +14,6 @@ interface ButtonProps {
|
||||
autoFocus?: boolean;
|
||||
className?: string;
|
||||
termInline?: boolean;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
class Button extends React.Component<ButtonProps> {
|
||||
@@ -24,14 +23,14 @@ class Button extends React.Component<ButtonProps> {
|
||||
};
|
||||
|
||||
@boundMethod
|
||||
handleClick(e) {
|
||||
handleClick() {
|
||||
if (this.props.onClick && !this.props.disabled) {
|
||||
this.props.onClick(e);
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { leftIcon, rightIcon, children, disabled, style, autoFocus, termInline, className, title } = this.props;
|
||||
const { leftIcon, rightIcon, children, disabled, style, autoFocus, termInline, className } = this.props;
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -40,7 +39,6 @@ class Button extends React.Component<ButtonProps> {
|
||||
disabled={disabled}
|
||||
style={style}
|
||||
autoFocus={autoFocus}
|
||||
title={title}
|
||||
>
|
||||
{leftIcon && <span className="icon-left">{leftIcon}</span>}
|
||||
{children}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.copy-button {
|
||||
padding: 5px 5px;
|
||||
|
||||
.fa-check {
|
||||
color: var(--app-success-color);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { Button } from "./button";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
|
||||
import "./copybutton.less";
|
||||
|
||||
type CopyButtonProps = {
|
||||
title: string;
|
||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
};
|
||||
|
||||
@mobxReact.observer
|
||||
class CopyButton extends React.Component<CopyButtonProps, {}> {
|
||||
isCopied: OV<boolean> = mobx.observable.box(false, { name: "isCopied" });
|
||||
|
||||
@boundMethod
|
||||
handleOnClick(e: React.MouseEvent<HTMLButtonElement>) {
|
||||
if (this.isCopied.get()) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.isCopied.set(true);
|
||||
})();
|
||||
setTimeout(() => {
|
||||
mobx.action(() => {
|
||||
this.isCopied.set(false);
|
||||
})();
|
||||
}, 2000);
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(e);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, onClick } = this.props;
|
||||
const isCopied = this.isCopied.get();
|
||||
return (
|
||||
<Button onClick={this.handleOnClick} className="copy-button secondary ghost" title={title}>
|
||||
{isCopied ? (
|
||||
<i className="fa-sharp fa-solid fa-check"></i>
|
||||
) : (
|
||||
<i className="fa-sharp fa-solid fa-copy"></i>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { CopyButton };
|
||||
@@ -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,3 @@ export { Toggle } from "./toggle";
|
||||
export { Tooltip } from "./tooltip";
|
||||
export { TabIcon } from "./tabicon";
|
||||
export { DatePicker } from "./datepicker";
|
||||
export { TermStyleBlock } from "./termstyleblock";
|
||||
export { CopyButton } from "./copybutton";
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { OverlayScrollbarsComponentProps, useOverlayScrollbars } from "overlayscrollbars-react";
|
||||
import React from "react";
|
||||
|
||||
export const ScrollbarsComponent = (props: {
|
||||
options?: OverlayScrollbarsComponentProps["options"];
|
||||
children: React.ReactNode;
|
||||
childrenRef: React.RefObject<any>;
|
||||
}) => {
|
||||
const [initialize, instance] = useOverlayScrollbars({ options: props.options });
|
||||
|
||||
React.useEffect(() => {
|
||||
initialize(props.childrenRef.current);
|
||||
}, [initialize, props.childrenRef.current]);
|
||||
|
||||
return <div ref={props.childrenRef}>{props.children}</div>;
|
||||
};
|
||||
@@ -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 };
|
||||
@@ -312,7 +312,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
<Tooltip
|
||||
message={`(Required) The path to your ssh private key file.`}
|
||||
message={`(Required) The path to your ssh key file.`}
|
||||
icon={<i className="fa-sharp fa-regular fa-circle-question" />}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-circle-question" />
|
||||
|
||||
@@ -338,7 +338,7 @@ class EditRemoteConnModal extends React.Component<{}, {}> {
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
<Tooltip
|
||||
message={`(Required) The path to your ssh private key file.`}
|
||||
message={`(Required) The path to your ssh key file.`}
|
||||
icon={<i className="fa-sharp fa-regular fa-circle-question" />}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-circle-question" />
|
||||
|
||||
@@ -357,10 +357,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wave-button {
|
||||
padding: 5px 5px;
|
||||
}
|
||||
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import { Line } from "@/app/line/linecomps";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||
import { TextField, Dropdown, Button, CopyButton } from "@/elements";
|
||||
import { TextField, Dropdown, Button, DatePicker } from "@/elements";
|
||||
|
||||
import { ReactComponent as ChevronLeftIcon } from "@/assets/icons/history/chevron-left.svg";
|
||||
import { ReactComponent as ChevronRightIcon } from "@/assets/icons/history/chevron-right.svg";
|
||||
@@ -22,6 +22,8 @@ import { ReactComponent as RightIcon } from "@/assets/icons/history/right.svg";
|
||||
import { ReactComponent as SearchIcon } from "@/assets/icons/history/search.svg";
|
||||
import { ReactComponent as TrashIcon } from "@/assets/icons/trash.svg";
|
||||
import { ReactComponent as CheckedCheckbox } from "@/assets/icons/checked-checkbox.svg";
|
||||
import { ReactComponent as CheckIcon } from "@/assets/icons/line/check.svg";
|
||||
import { ReactComponent as CopyIcon } from "@/assets/icons/history/copy.svg";
|
||||
|
||||
import "./history.less";
|
||||
import { MainView } from "../common/elements/mainview";
|
||||
@@ -113,6 +115,7 @@ class HistoryCmdStr extends React.Component<
|
||||
cmdstr: string;
|
||||
onUse: () => void;
|
||||
onCopy: () => void;
|
||||
isCopied: boolean;
|
||||
fontSize: "normal" | "large";
|
||||
limitHeight: boolean;
|
||||
},
|
||||
@@ -135,17 +138,24 @@ class HistoryCmdStr extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
const { cmdstr, fontSize, limitHeight } = this.props;
|
||||
const { isCopied, cmdstr, fontSize, limitHeight } = this.props;
|
||||
return (
|
||||
<div className={cn("cmdstr-code", { "is-large": fontSize == "large" }, { "limit-height": limitHeight })}>
|
||||
<If condition={isCopied}>
|
||||
<div key="copied" className="copied-indicator">
|
||||
<div>copied</div>
|
||||
</div>
|
||||
</If>
|
||||
<div key="code" className="code-div">
|
||||
<code>{cmdstr}</code>
|
||||
</div>
|
||||
<div key="copy" className="actions-block">
|
||||
<CopyButton onClick={this.handleCopy} title="Copy" />
|
||||
<Button className="secondary ghost" title="Use Command" onClick={this.handleUse}>
|
||||
<i className="fa-sharp fa-solid fa-play"></i>
|
||||
</Button>
|
||||
<div className="action-item" onClick={this.handleCopy} title="copy">
|
||||
<CopyIcon className="icon" />
|
||||
</div>
|
||||
<div key="use" className="action-item" title="Use Command" onClick={this.handleUse}>
|
||||
<CheckIcon className="icon" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -180,6 +190,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
tableRszObs: ResizeObserver;
|
||||
sessionDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "sessionDropdownActive" });
|
||||
remoteDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "remoteDropdownActive" });
|
||||
copiedItemId: OV<string> = mobx.observable.box(null, { name: "copiedItemId" });
|
||||
|
||||
@boundMethod
|
||||
handleNext() {
|
||||
@@ -366,6 +377,14 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(item.cmdstr);
|
||||
mobx.action(() => {
|
||||
this.copiedItemId.set(item.historyid);
|
||||
})();
|
||||
setTimeout(() => {
|
||||
mobx.action(() => {
|
||||
this.copiedItemId.set(null);
|
||||
})();
|
||||
}, 600);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@@ -375,7 +394,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
mobx.action(() => {
|
||||
GlobalModel.showSessionView();
|
||||
GlobalModel.inputModel.updateCmdLine({ str: item.cmdstr, pos: item.cmdstr.length });
|
||||
GlobalModel.inputModel.setCurLine(item.cmdstr);
|
||||
setTimeout(() => GlobalModel.inputModel.giveFocus(), 50);
|
||||
})();
|
||||
}
|
||||
@@ -550,6 +569,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
cmdstr={item.cmdstr}
|
||||
onUse={() => this.handleUse(item)}
|
||||
onCopy={() => this.handleCopy(item)}
|
||||
isCopied={this.copiedItemId.get() == item.historyid}
|
||||
fontSize="normal"
|
||||
limitHeight={true}
|
||||
/>
|
||||
@@ -649,6 +669,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;
|
||||
|
||||
@@ -11,6 +11,8 @@ import { Markdown } from "@/elements";
|
||||
import { AuxiliaryCmdView } from "./auxview";
|
||||
|
||||
import "./aichat.less";
|
||||
import { UseOverlayScrollbarsInitialization, useOverlayScrollbars } from "overlayscrollbars-react";
|
||||
import { ScrollbarsComponent } from "@/app/common/elements/scrollbars";
|
||||
|
||||
class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
|
||||
componentDidMount(): void {
|
||||
@@ -27,7 +29,7 @@ class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:cancel", (waveEvent) => {
|
||||
inputModel.closeAuxView();
|
||||
inputModel.closeAIAssistantChat(true);
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "aichat:clearHistory", (waveEvent) => {
|
||||
@@ -252,17 +254,22 @@ class AIChat extends React.Component<{}, {}> {
|
||||
<AuxiliaryCmdView
|
||||
title="Wave AI"
|
||||
className="cmd-aichat"
|
||||
onClose={() => GlobalModel.inputModel.closeAuxView()}
|
||||
onClose={() => GlobalModel.inputModel.closeAIAssistantChat(true)}
|
||||
iconClass="fa-sharp fa-solid fa-sparkles"
|
||||
>
|
||||
<If condition={renderKeybindings}>
|
||||
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
|
||||
</If>
|
||||
<div className="chat-window" ref={this.chatWindowScrollRef}>
|
||||
<For each="chitem" index="idx" of={chatMessageItems}>
|
||||
{this.renderChatMessage(chitem)}
|
||||
</For>
|
||||
</div>
|
||||
<ScrollbarsComponent
|
||||
childrenRef={this.chatWindowScrollRef}
|
||||
options={{ scrollbars: { autoHide: "leave" } }}
|
||||
>
|
||||
<div className="chat-window" ref={this.chatWindowScrollRef}>
|
||||
<For each="chitem" index="idx" of={chatMessageItems}>
|
||||
{this.renderChatMessage(chitem)}
|
||||
</For>
|
||||
</div>
|
||||
</ScrollbarsComponent>
|
||||
<div className="chat-input">
|
||||
<textarea
|
||||
key="main"
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
max-height: max(300px, 40%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
border-top: 2px solid var(--app-border-color);
|
||||
background-color: var(--app-bg-color);
|
||||
position: relative;
|
||||
|
||||
// Apply a border between the base cmdinput and any views shown above it
|
||||
// TODO: use a generic selector for this
|
||||
|
||||
@@ -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 { Choose, If, When } from "tsx-control-statements/components";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
@@ -18,7 +18,6 @@ import { Prompt } from "@/common/prompt/prompt";
|
||||
import { CenteredIcon, RotateIcon } from "@/common/icons/icons";
|
||||
import { AIChat } from "./aichat";
|
||||
import * as util from "@/util/util";
|
||||
import * as appconst from "@/app/appconst";
|
||||
|
||||
import "./cmdinput.less";
|
||||
|
||||
@@ -62,7 +61,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
baseCmdInputClick(e: React.SyntheticEvent): void {
|
||||
baseCmdInputClick(e: React.MouseEvent): void {
|
||||
if (this.promptRef.current != null) {
|
||||
if (this.promptRef.current.contains(e.target)) {
|
||||
return;
|
||||
@@ -72,7 +71,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
GlobalModel.inputModel.setAuxViewFocus(false);
|
||||
GlobalModel.inputModel.setHistoryFocus(false);
|
||||
GlobalModel.inputModel.giveFocus();
|
||||
}
|
||||
|
||||
@@ -81,8 +80,8 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (inputModel.getActiveAuxView() === appconst.InputAuxView_AIChat) {
|
||||
inputModel.closeAuxView();
|
||||
if (inputModel.aIChatShow.get()) {
|
||||
inputModel.closeAIAssistantChat(true);
|
||||
} else {
|
||||
inputModel.openAIAssistantChat();
|
||||
}
|
||||
@@ -94,7 +93,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
e.stopPropagation();
|
||||
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (inputModel.getActiveAuxView() === appconst.InputAuxView_History) {
|
||||
if (inputModel.historyShow.get()) {
|
||||
inputModel.resetHistory();
|
||||
} else {
|
||||
inputModel.openHistory();
|
||||
@@ -156,6 +155,9 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
remote = GlobalModel.getRemote(rptr.remoteid);
|
||||
}
|
||||
feState = feState || {};
|
||||
const infoShow = inputModel.infoShow.get();
|
||||
const historyShow = !infoShow && inputModel.historyShow.get();
|
||||
const aiChatShow = inputModel.aIChatShow.get();
|
||||
const focusVal = inputModel.physicalInputFocused.get();
|
||||
const inputMode: string = inputModel.inputMode.get();
|
||||
const textAreaInputKey = screen == null ? "null" : screen.screenId;
|
||||
@@ -168,7 +170,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
let shellInitMsg: string = null;
|
||||
let hidePrompt = false;
|
||||
|
||||
const openView = inputModel.getActiveAuxView();
|
||||
const openView = inputModel.getOpenView();
|
||||
const hasOpenView = openView ? `has-${openView}` : null;
|
||||
if (ri == null) {
|
||||
let shellStr = "shell";
|
||||
@@ -183,19 +185,15 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
}
|
||||
return (
|
||||
<div ref={this.cmdInputRef} className={cn("cmd-input", hasOpenView, { active: focusVal })}>
|
||||
<Choose>
|
||||
<When condition={openView === appconst.InputAuxView_History}>
|
||||
<div className="cmd-input-grow-spacer"></div>
|
||||
<HistoryInfo />
|
||||
</When>
|
||||
<When condition={openView === appconst.InputAuxView_AIChat}>
|
||||
<div className="cmd-input-grow-spacer"></div>
|
||||
<AIChat />
|
||||
</When>
|
||||
<When condition={openView === appconst.InputAuxView_Info}>
|
||||
<InfoMsg key="infomsg" />
|
||||
</When>
|
||||
</Choose>
|
||||
<If condition={historyShow}>
|
||||
<div className="cmd-input-grow-spacer"></div>
|
||||
<HistoryInfo />
|
||||
</If>
|
||||
<If condition={aiChatShow}>
|
||||
<div className="cmd-input-grow-spacer"></div>
|
||||
<AIChat />
|
||||
</If>
|
||||
<InfoMsg key="infomsg" />
|
||||
<If condition={remote && remote.status != "connected"}>
|
||||
<div className="remote-status-warning">
|
||||
WARNING:
|
||||
@@ -231,12 +229,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
</Button>
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
key="base-cmdinput"
|
||||
className="base-cmdinput"
|
||||
onClick={this.baseCmdInputClick}
|
||||
onSelect={this.baseCmdInputClick}
|
||||
>
|
||||
<div key="base-cmdinput" className="base-cmdinput" onClick={this.baseCmdInputClick}>
|
||||
<div className="cmdinput-actions">
|
||||
<If condition={numRunningLines > 0}>
|
||||
<div
|
||||
|
||||
@@ -15,6 +15,7 @@ import { isBlank } from "@/util/util";
|
||||
|
||||
import "./historyinfo.less";
|
||||
import { AuxiliaryCmdView } from "./auxview";
|
||||
import { ScrollbarsComponent } from "@/app/common/elements/scrollbars";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
@@ -153,6 +154,7 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
lastClickHNum: string = null;
|
||||
lastClickTs: number = 0;
|
||||
containingText: mobx.IObservableValue<string> = mobx.observable.box("");
|
||||
historyItemsRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
|
||||
componentDidMount() {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
@@ -167,14 +169,14 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
|
||||
@boundMethod
|
||||
handleClose() {
|
||||
GlobalModel.inputModel.closeAuxView();
|
||||
GlobalModel.inputModel.toggleInfoMsg();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleItemClick(hitem: HistoryItem) {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
const selItem = inputModel.getHistorySelectedItem();
|
||||
inputModel.setAuxViewFocus(false);
|
||||
inputModel.setHistoryFocus(true);
|
||||
if (this.lastClickHNum == hitem.historynum && selItem != null && selItem.historynum == hitem.historynum) {
|
||||
inputModel.grabSelectedHistoryItem();
|
||||
return;
|
||||
@@ -194,14 +196,14 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
handleClickType() {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
inputModel.setAuxViewFocus(true);
|
||||
inputModel.setHistoryFocus(true);
|
||||
inputModel.toggleHistoryType();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleClickRemote() {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
inputModel.setAuxViewFocus(true);
|
||||
inputModel.setHistoryFocus(true);
|
||||
inputModel.toggleRemoteType();
|
||||
}
|
||||
|
||||
@@ -239,33 +241,39 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
return (
|
||||
<AuxiliaryCmdView
|
||||
title="History"
|
||||
className="cmd-history hide-scrollbar"
|
||||
className="cmd-history"
|
||||
onClose={this.handleClose}
|
||||
titleBarContents={this.getTitleBarContents()}
|
||||
iconClass="fa-sharp fa-solid fa-clock-rotate-left"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"history-items",
|
||||
{ "show-remotes": !opts.limitRemote },
|
||||
{ "show-sessions": opts.queryType == "global" }
|
||||
)}
|
||||
<ScrollbarsComponent
|
||||
childrenRef={this.historyItemsRef}
|
||||
options={{ scrollbars: { autoHide: "scroll" } }}
|
||||
>
|
||||
<If condition={hitems.length == 0}>[no history]</If>
|
||||
<If condition={hitems.length > 0}>
|
||||
<For each="hitem" index="idx" of={hitems}>
|
||||
<HItem
|
||||
key={hitem.historyid}
|
||||
hitem={hitem}
|
||||
isSelected={hitem == selItem}
|
||||
opts={opts}
|
||||
snames={snames}
|
||||
scrNames={scrNames}
|
||||
onClick={this.handleItemClick}
|
||||
></HItem>
|
||||
</For>
|
||||
</If>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"history-items",
|
||||
{ "show-remotes": !opts.limitRemote },
|
||||
{ "show-sessions": opts.queryType == "global" }
|
||||
)}
|
||||
ref={this.historyItemsRef}
|
||||
>
|
||||
<If condition={hitems.length == 0}>[no history]</If>
|
||||
<If condition={hitems.length > 0}>
|
||||
<For each="hitem" index="idx" of={hitems}>
|
||||
<HItem
|
||||
key={hitem.historyid}
|
||||
hitem={hitem}
|
||||
isSelected={hitem == selItem}
|
||||
opts={opts}
|
||||
snames={snames}
|
||||
scrNames={scrNames}
|
||||
onClick={this.handleItemClick}
|
||||
></HItem>
|
||||
</For>
|
||||
</If>
|
||||
</div>
|
||||
</ScrollbarsComponent>
|
||||
</AuxiliaryCmdView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class InfoMsg extends React.Component<{}, {}> {
|
||||
render() {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
const infoMsg: InfoType = inputModel.infoMsg.get();
|
||||
const infoShow = inputModel.getActiveAuxView() == appconst.InputAuxView_Info;
|
||||
const infoShow: boolean = inputModel.infoShow.get();
|
||||
let line: string = null;
|
||||
let istr: string = null;
|
||||
let idx: number = 0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user