mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d95d45a45 | |||
| 1c23701181 | |||
| 0fe767cdf3 | |||
| 097623ab51 | |||
| 9de25e4869 | |||
| a139633375 | |||
| ccc63937b6 | |||
| dc7fc2c823 | |||
| ca5117cda0 |
+17
-15
@@ -42,16 +42,19 @@
|
||||
--app-bg-color: black;
|
||||
--app-accent-color: rgb(88, 193, 66);
|
||||
--app-accent-bg-color: rgba(88, 193, 66, 0.25);
|
||||
--app-accent-bg-darker-color: rgba(88, 193, 66, 0.5);
|
||||
--app-text-color: rgb(211, 215, 207);
|
||||
--app-text-primary-color: rgb(255, 255, 255);
|
||||
--app-text-secondary-color: rgb(195, 200, 194);
|
||||
--app-text-disabled-color: rgb(173, 173, 173);
|
||||
--app-text-bg-color: rgb(23, 23, 23);
|
||||
--app-text-bg-disabled-color: rgb(51, 51, 51);
|
||||
--app-border-color: rgb(51, 51, 51);
|
||||
--app-maincontent-bg-color: #333;
|
||||
--app-maincontent-bg-color: rgb(51, 51, 51);
|
||||
--app-panel-bg-color: rgba(21, 23, 21, 1);
|
||||
--app-panel-bg-color-dev: rgb(21, 23, 48);
|
||||
--app-icon-color: rgb(139, 145, 138);
|
||||
--app-icon-hover-color: #fff;
|
||||
--app-icon-hover-color: rgb(255, 255, 255);
|
||||
--app-selected-mask-color: rgba(255, 255, 255, 0.06);
|
||||
|
||||
/* global status colors */
|
||||
@@ -122,18 +125,18 @@
|
||||
|
||||
/* line colors */
|
||||
--line-sidebar-message-color: rgb(196, 160, 0);
|
||||
--line-background: rgba(21, 23, 21, 1);
|
||||
--line-avatar-color: #eceeec;
|
||||
--line-background: var(--app-panel-bg-color);
|
||||
--line-avatar-color: rgb(236, 238, 236);
|
||||
--line-text-color: rgb(211, 215, 207);
|
||||
--line-svg-fill-color: rgb(150, 152, 150);
|
||||
--line-svg-hover-fill-color: #eceeec;
|
||||
--line-svg-hover-fill-color: rgb(236, 238, 236);
|
||||
--line-separator-color: rgb(126, 126, 126);
|
||||
--line-error-color: var(--app-error-color);
|
||||
--line-warning-color: var(--app-warning-color);
|
||||
--line-base-soft-blue-color: #729fcf;
|
||||
--line-base-soft-blue-color: rgb(114, 159, 207);
|
||||
--line-active-border-color: var(--app-accent-color);
|
||||
--line-selected-bg-color: rgba(255, 255, 255, 0.05);
|
||||
--line-selected-border-left-color: #777777;
|
||||
--line-selected-border-left-color: rgb(119, 119, 119);
|
||||
--line-selected-error-border-color: rgba(204, 0, 0, 0.8);
|
||||
--line-selected-error-bg-color: rgb(19, 4, 3);
|
||||
--line-error-bg-color: rgba(200, 0, 0, 0.1);
|
||||
@@ -152,22 +155,21 @@
|
||||
/* table colors */
|
||||
--table-border-color: rgba(241, 246, 243, 0.15);
|
||||
--table-thead-border-top-color: rgba(250, 250, 250, 0.1);
|
||||
--table-thead-bright-border-color: #ccc;
|
||||
--table-thead-bright-border-color: rgb(204, 204, 204);
|
||||
--table-thead-bg-color: rgba(250, 250, 250, 0.02);
|
||||
--table-tr-border-bottom-color: rgba(241, 246, 243, 0.15);
|
||||
--table-tr-hover-bg-color: rgba(255, 255, 255, 0.06);
|
||||
--table-tr-selected-bg-color: #222;
|
||||
--table-tr-selected-hover-bg-color: #333;
|
||||
--table-tr-selected-bg-color: rgb(34, 34, 34);
|
||||
--table-tr-selected-hover-bg-color: var(--app-maincontent-bg-color);
|
||||
|
||||
/* cmdinput colors */
|
||||
--cmdinput-textarea-bg-color: #171717;
|
||||
--cmdinput-bg-color: var(--app-text-bg-color);
|
||||
--cmdinput-text-error-color: var(--term-red);
|
||||
--cmdinput-history-item-error-color: var(--term-bright-red);
|
||||
--cmdinput-history-item-selected-error-color: var(--term-bright-red);
|
||||
--cmdinput-button-bg-color: rgb(88, 193, 66);
|
||||
--cmdinput-comment-button-bg-color: rgb(57, 113, 255);
|
||||
--cmdinput-disabled-icon-color: rgb(76, 81, 75, 1);
|
||||
--cmdinput-history-bg-color: rgb(21, 23, 21, 1);
|
||||
--cmdinput-button-bg-color: var(--tab-green);
|
||||
--cmdinput-disabled-bg-color: var(--app-text-bg-disabled-color);
|
||||
--cmdinput-history-bg-color: var(--app-bg-color);
|
||||
|
||||
/* screen view color */
|
||||
--screen-view-text-caption-color: rgb(139, 145, 138);
|
||||
|
||||
+15
-17
@@ -5,20 +5,20 @@
|
||||
@import url("./term-light.css");
|
||||
|
||||
:root {
|
||||
--app-bg-color: #fefefe;
|
||||
--app-bg-color: rgb(254, 254, 254);
|
||||
--app-accent-color: rgb(75, 166, 57);
|
||||
--app-accent-bg-color: rgba(75, 166, 57, 0.2);
|
||||
--app-text-color: #000;
|
||||
--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-border-color: rgb(139 145 138);
|
||||
--app-panel-bg-color: #e0e0e0;
|
||||
--app-panel-bg-color-dev: #e0e0e0;
|
||||
--app-icon-color: rgb(80, 80, 80);
|
||||
--app-icon-hover-color: rgb(100, 100, 100);
|
||||
--app-panel-bg-color: rgb(224, 224, 224);
|
||||
--app-panel-bg-color-dev: rgb(224, 224, 224);
|
||||
--app-icon-color: rgb(110, 110, 110);
|
||||
--app-icon-hover-color: rgb(80, 80, 80);
|
||||
--app-selected-mask-color: rgba(0, 0, 0, 0.06);
|
||||
|
||||
--input-bg-color: #eeeeee;
|
||||
--input-bg-color: rgb(238, 238, 238);
|
||||
|
||||
/* tab color */
|
||||
--tab-white: rgb(0, 0, 0, 0.6);
|
||||
@@ -30,8 +30,8 @@
|
||||
--table-thead-bg-color: rgba(250, 250, 250, 0.15);
|
||||
--table-tr-border-bottom-color: rgba(0, 0, 0, 0.15);
|
||||
--table-tr-hover-bg-color: rgba(0, 0, 0, 0.15);
|
||||
--table-tr-selected-bg-color: #dddddd;
|
||||
--table-tr-selected-hover-bg-color: #cccccc;
|
||||
--table-tr-selected-bg-color: rgb(221, 221, 221);
|
||||
--table-tr-selected-hover-bg-color: rgb(204, 204, 204);
|
||||
|
||||
/* form colors */
|
||||
--form-element-border-color: rgba(0, 0, 0, 0.3);
|
||||
@@ -41,8 +41,8 @@
|
||||
--form-element-label-color: rgba(0, 0, 0, 0.6);
|
||||
--form-element-secondary-color: rgba(0, 0, 0, 0.09);
|
||||
--form-element-icon-color: rgb(0, 0, 0, 0.6);
|
||||
--form-element-disabled-text-color: #b7b7b7;
|
||||
--form-element-placeholder-color: #b7b7b7;
|
||||
--form-element-disabled-text-color: rgb(183, 183, 183);
|
||||
--form-element-placeholder-color: rgb(183, 183, 183);
|
||||
|
||||
--markdown-bg-color: rgb(0, 0, 0, 0.1);
|
||||
|
||||
@@ -50,8 +50,6 @@
|
||||
--modal-header-bottom-border-color: rgba(0, 0, 0, 0.3);
|
||||
|
||||
/* cmd input */
|
||||
--cmdinput-textarea-bg-color: rgba(0, 0, 0, 0.1);
|
||||
--cmdinput-textarea-border-color: var(--form-element-border-color);
|
||||
|
||||
/* scroll colors */
|
||||
--scrollbar-background-color: var(--app-bg-color);
|
||||
@@ -64,15 +62,15 @@
|
||||
--line-actions-inactive-color: rgba(0, 0, 0, 0.3);
|
||||
--line-actions-active-color: rgba(0, 0, 0, 1);
|
||||
|
||||
--logo-button-hover-bg-color: #f0f0f0;
|
||||
--logo-button-hover-bg-color: rgb(240, 240, 240);
|
||||
|
||||
--xterm-viewport-border-color: rgba(0, 0, 0, 0.3);
|
||||
|
||||
--datepicker-cell-hover-bg-color: #f0f0f0;
|
||||
--datepicker-cell-hover-bg-color: rgb(240, 240, 240);
|
||||
--datepicker-cell-other-text-color: rgba(0, 0, 0, 0.3);
|
||||
--datepicker-header-fade-color: rgba(0, 0, 0, 0.4);
|
||||
--datepicker-year-header-bg-color: #f5f5f5;
|
||||
--datepicker-year-header-border-color: #dcdcdc;
|
||||
--datepicker-year-header-bg-color: rgb(245, 245, 245);
|
||||
--datepicker-year-header-border-color: rgb(220, 220, 220);
|
||||
|
||||
/* toggle colors */
|
||||
--toggle-thumb-color: var(--app-bg-color);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel, getApi } 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 "./clientsettings.less";
|
||||
@@ -73,6 +74,19 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeTermTheme(theme: string): void {
|
||||
// For global terminal theme, the key is global, otherwise it's either
|
||||
// sessionId or screenId.
|
||||
const currTheme = GlobalModel.getTermTheme()["global"];
|
||||
if (currTheme == theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prtn = GlobalCommandRunner.setGlobalTermTheme(theme, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeTelemetry(val: boolean): void {
|
||||
let prtn: Promise<CommandRtnType> = null;
|
||||
@@ -193,6 +207,8 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
const curFontSize = GlobalModel.getTermFontSize();
|
||||
const curFontFamily = GlobalModel.getTermFontFamily();
|
||||
const curTheme = GlobalModel.getThemeSource();
|
||||
const termThemes = getTermThemes(GlobalModel.termThemes, "Wave Default");
|
||||
const currTermTheme = GlobalModel.getTermTheme()["global"] ?? termThemes[0].label;
|
||||
|
||||
return (
|
||||
<MainView className="clientsettings-view" title="Client Settings" onClose={this.handleClose}>
|
||||
@@ -233,6 +249,19 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<If condition={termThemes.length > 0}>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Terminal Theme</div>
|
||||
<div className="settings-input">
|
||||
<Dropdown
|
||||
className="terminal-theme-dropdown"
|
||||
options={termThemes}
|
||||
defaultValue={currTermTheme}
|
||||
onChange={this.handleChangeTermTheme}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Client ID</div>
|
||||
<div className="settings-input">{cdata.clientid}</div>
|
||||
|
||||
@@ -7,8 +7,10 @@ import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import "./markdown.less";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
|
||||
function LinkRenderer(props: any): any {
|
||||
let newUrl = "https://extern?" + encodeURIComponent(props.href);
|
||||
@@ -28,14 +30,17 @@ function CodeRenderer(props: any): any {
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class CodeBlockMarkdown extends React.Component<{ children: React.ReactNode; codeSelectSelectedIndex?: number }, {}> {
|
||||
class CodeBlockMarkdown extends React.Component<
|
||||
{ children: React.ReactNode; codeSelectSelectedIndex?: number; uuid: string },
|
||||
{}
|
||||
> {
|
||||
blockIndex: number;
|
||||
blockRef: React.RefObject<HTMLPreElement>;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.blockRef = React.createRef();
|
||||
this.blockIndex = GlobalModel.inputModel.addCodeBlockToCodeSelect(this.blockRef);
|
||||
this.blockIndex = GlobalModel.inputModel.addCodeBlockToCodeSelect(this.blockRef, this.props.uuid);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -62,9 +67,21 @@ class Markdown extends React.Component<
|
||||
{ text: string; style?: any; extraClassName?: string; codeSelect?: boolean },
|
||||
{}
|
||||
> {
|
||||
CodeBlockRenderer(props: any, codeSelect: boolean, codeSelectIndex: number): any {
|
||||
curUuid: string;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.curUuid = uuidv4();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
CodeBlockRenderer(props: any, codeSelect: boolean, codeSelectIndex: number, curUuid: string): any {
|
||||
if (codeSelect) {
|
||||
return <CodeBlockMarkdown codeSelectSelectedIndex={codeSelectIndex}>{props.children}</CodeBlockMarkdown>;
|
||||
return (
|
||||
<CodeBlockMarkdown codeSelectSelectedIndex={codeSelectIndex} uuid={curUuid}>
|
||||
{props.children}
|
||||
</CodeBlockMarkdown>
|
||||
);
|
||||
} else {
|
||||
const clickHandler = (e: React.MouseEvent<HTMLElement>) => {
|
||||
let blockText = (e.target as HTMLElement).innerText;
|
||||
@@ -90,7 +107,7 @@ class Markdown extends React.Component<
|
||||
h5: (props) => HeaderRenderer(props, 5),
|
||||
h6: (props) => HeaderRenderer(props, 6),
|
||||
code: (props) => CodeRenderer(props),
|
||||
pre: (props) => this.CodeBlockRenderer(props, codeSelect, curCodeSelectIndex),
|
||||
pre: (props) => this.CodeBlockRenderer(props, codeSelect, curCodeSelectIndex, this.curUuid),
|
||||
};
|
||||
return (
|
||||
<div className={cn("markdown content", this.props.extraClassName)} style={this.props.style}>
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
@@ -11,8 +11,9 @@ import { GlobalModel, GlobalCommandRunner, Screen } from "@/models";
|
||||
import { SettingsError, Modal, Dropdown, Tooltip } from "@/elements";
|
||||
import * as util from "@/util/util";
|
||||
import { Button } from "@/elements";
|
||||
import { ReactComponent as GlobeIcon } from "@/assets/icons/globe.svg";
|
||||
import { ReactComponent as StatusCircleIcon } from "@/assets/icons/statuscircle.svg";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { commandRtnHandler } from "@/util/util";
|
||||
import { getTermThemes } from "@/util/themeutil";
|
||||
import {
|
||||
TabColorSelector,
|
||||
TabIconSelector,
|
||||
@@ -153,11 +154,34 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
|
||||
});
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeTermTheme(theme: string): void {
|
||||
const currTheme = GlobalModel.getTermTheme()[this.screenId];
|
||||
if (currTheme == theme) {
|
||||
return;
|
||||
}
|
||||
const prtn = GlobalCommandRunner.setScreenTermTheme(this.screenId, theme, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
selectRemote(cname: string): void {
|
||||
let prtn = GlobalCommandRunner.screenSetRemote(cname, true, false);
|
||||
util.commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
render() {
|
||||
const screen = this.screen;
|
||||
if (screen == null) {
|
||||
return null;
|
||||
}
|
||||
let color: string = null;
|
||||
let icon: string = null;
|
||||
let index: number = 0;
|
||||
const curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
|
||||
const termThemes = getTermThemes(GlobalModel.termThemes);
|
||||
const currTermTheme = GlobalModel.getTermTheme()[this.screenId] ?? termThemes[0].label;
|
||||
|
||||
return (
|
||||
<Modal className="screen-settings-modal">
|
||||
<Modal.Header onClose={this.closeModal} title={`Tab Settings (${screen.name.get()})`} />
|
||||
@@ -186,6 +210,19 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
|
||||
<TabIconSelector screen={screen} errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
</div>
|
||||
<If condition={termThemes.length > 0}>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Terminal Theme</div>
|
||||
<div className="settings-input">
|
||||
<Dropdown
|
||||
className="terminal-theme-dropdown"
|
||||
options={termThemes}
|
||||
defaultValue={currTermTheme}
|
||||
onChange={this.handleChangeTermTheme}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label actions-label">
|
||||
<div>Actions</div>
|
||||
|
||||
@@ -6,8 +6,10 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { GlobalModel, GlobalCommandRunner, Session } from "@/models";
|
||||
import { Button } from "@/elements";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Modal, Tooltip } from "@/elements";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Modal, Tooltip, Button, Dropdown } from "@/elements";
|
||||
import { commandRtnHandler } from "@/util/util";
|
||||
import { getTermThemes } from "@/util/themeutil";
|
||||
import * as util from "@/util/util";
|
||||
|
||||
import "./sessionsettings.less";
|
||||
@@ -76,6 +78,16 @@ class SessionSettingsModal extends React.Component<{}, {}> {
|
||||
});
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeTermTheme(theme: string): void {
|
||||
const currTheme = GlobalModel.getTermTheme()[this.sessionId];
|
||||
if (currTheme == theme) {
|
||||
return;
|
||||
}
|
||||
const prtn = GlobalCommandRunner.setSessionTermTheme(this.sessionId, theme, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
dismissError(): void {
|
||||
mobx.action(() => {
|
||||
@@ -87,6 +99,9 @@ class SessionSettingsModal extends React.Component<{}, {}> {
|
||||
if (this.session == null) {
|
||||
return null;
|
||||
}
|
||||
const termThemes = getTermThemes(GlobalModel.termThemes);
|
||||
const currTermTheme = GlobalModel.getTermTheme()[this.sessionId] ?? termThemes[0].label;
|
||||
|
||||
return (
|
||||
<Modal className="session-settings-modal">
|
||||
<Modal.Header onClose={this.closeModal} title={`Workspace Settings (${this.session.name.get()})`} />
|
||||
@@ -104,6 +119,19 @@ class SessionSettingsModal extends React.Component<{}, {}> {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<If condition={termThemes.length > 0}>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Terminal Theme</div>
|
||||
<div className="settings-input">
|
||||
<Dropdown
|
||||
className="terminal-theme-dropdown"
|
||||
options={termThemes}
|
||||
defaultValue={currTermTheme}
|
||||
onChange={this.handleChangeTermTheme}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
<div>Archived</div>
|
||||
|
||||
@@ -308,7 +308,6 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
hvm.setFromDate(null);
|
||||
return;
|
||||
}
|
||||
console.log;
|
||||
hvm.setFromDate(newDate);
|
||||
}
|
||||
|
||||
@@ -670,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;
|
||||
@@ -710,8 +714,9 @@ class LineContainer extends React.Component<{ historyId: string; width: number }
|
||||
ssStr = sprintf("#%s[%s]", session.name.get(), screen.name.get());
|
||||
canViewInContext = true;
|
||||
}
|
||||
const termRenderVersion = GlobalModel.termRenderVersion.get();
|
||||
return (
|
||||
<div className="line-container">
|
||||
<div className="line-container" key={termRenderVersion}>
|
||||
<If condition={canViewInContext}>
|
||||
<div className="line-context">
|
||||
<div title="View in Context" className="vic-btn" onClick={this.viewInContext}>
|
||||
|
||||
@@ -106,11 +106,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.top-border {
|
||||
border-top: 1px solid #777;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
&:hover .meta .termopts {
|
||||
display: block;
|
||||
}
|
||||
|
||||
+156
-145
@@ -8,7 +8,7 @@ import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { Choose, If, Otherwise, When } from "tsx-control-statements/components";
|
||||
import { GlobalModel, GlobalCommandRunner, Cmd } from "@/models";
|
||||
import { termHeightFromRows } from "@/util/textmeasure";
|
||||
import cn from "classnames";
|
||||
@@ -25,23 +25,22 @@ import * as lineutil from "./lineutil";
|
||||
import { ErrorBoundary } from "@/common/error/errorboundary";
|
||||
import * as appconst from "@/app/appconst";
|
||||
import * as util from "@/util/util";
|
||||
import * as textmeasure from "@/util/textmeasure";
|
||||
|
||||
import "./line.less";
|
||||
import { CenteredIcon, RotateIcon } from "../common/icons/icons";
|
||||
import { RotateIcon } from "../common/icons/icons";
|
||||
|
||||
const DebugHeightProblems = false;
|
||||
const MinLine = 0;
|
||||
const MaxLine = 1000;
|
||||
let heightLog = {};
|
||||
const heightLog = {};
|
||||
(window as any).heightLog = heightLog;
|
||||
(window as any).findHeightProblems = function () {
|
||||
for (let linenum in heightLog) {
|
||||
let lh = heightLog[linenum];
|
||||
for (const linenum in heightLog) {
|
||||
const lh = heightLog[linenum];
|
||||
if (lh.heightArr == null || lh.heightArr.length < 2) {
|
||||
continue;
|
||||
}
|
||||
let firstHeight = lh.heightArr[0];
|
||||
const firstHeight = lh.heightArr[0];
|
||||
for (let i = 1; i < lh.heightArr.length; i++) {
|
||||
if (lh.heightArr[i] != firstHeight) {
|
||||
console.log("line", linenum, "heights", lh.heightArr);
|
||||
@@ -57,7 +56,7 @@ function cmdShouldMarkError(cmd: Cmd): boolean {
|
||||
if (cmd.getStatus() == "error") {
|
||||
return true;
|
||||
}
|
||||
let exitCode = cmd.getExitCode();
|
||||
const exitCode = cmd.getExitCode();
|
||||
// 0, SIGINT, or SIGPIPE
|
||||
if (exitCode == 0 || exitCode == 130 || exitCode == 141) {
|
||||
return false;
|
||||
@@ -142,7 +141,7 @@ class LineActions extends React.Component<{ screen: LineContainerType; line: Lin
|
||||
handleLineSettings(e: any): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
if (line != null) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.lineSettingsModal.set(line.linenum);
|
||||
@@ -152,74 +151,89 @@ class LineActions extends React.Component<{ screen: LineContainerType; line: Lin
|
||||
}
|
||||
|
||||
render() {
|
||||
let { line, screen } = this.props;
|
||||
const { line, screen } = this.props;
|
||||
const isMinimized = line.linestate["wave:min"];
|
||||
const containerType = screen.getContainerType();
|
||||
return (
|
||||
<div className="line-actions">
|
||||
<If condition={containerType == appconst.LineContainer_Main}>
|
||||
<div key="restart" title="Restart Command" className="line-icon" onClick={this.clickRestart}>
|
||||
<i className="fa-sharp fa-regular fa-arrows-rotate fa-fw" />
|
||||
</div>
|
||||
<div key="delete" title="Delete Line (⌘D)" className="line-icon" onClick={this.clickDelete}>
|
||||
<i className="fa-sharp fa-regular fa-trash fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="bookmark"
|
||||
title="Bookmark"
|
||||
className={cn("line-icon", "line-bookmark")}
|
||||
onClick={this.clickBookmark}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-bookmark fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="minimize"
|
||||
title={`${isMinimized ? "Show Output" : "Hide Output"}`}
|
||||
className={cn("line-icon", isMinimized ? "active" : "")}
|
||||
onClick={this.clickMinimize}
|
||||
>
|
||||
<If condition={isMinimized}>
|
||||
<i className="fa-sharp fa-regular fa-circle-plus fa-fw" />
|
||||
</If>
|
||||
<If condition={!isMinimized}>
|
||||
<i className="fa-sharp fa-regular fa-circle-minus fa-fw" />
|
||||
</If>
|
||||
</div>
|
||||
<div className="line-icon line-sidebar" onClick={this.clickMoveToSidebar} title="Move to Sidebar">
|
||||
<i className="fa-sharp fa-solid fa-right-to-line fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="settings"
|
||||
title="Line Settings"
|
||||
className="line-icon line-icon-shrink-left"
|
||||
onClick={this.handleLineSettings}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-ellipsis-vertical fa-fw" />
|
||||
</div>
|
||||
</If>
|
||||
<If condition={containerType == appconst.LineContainer_Sidebar}>
|
||||
<div key="restart" title="Restart Command" className="line-icon" onClick={this.clickRestart}>
|
||||
<i className="fa-sharp fa-regular fa-arrows-rotate fa-fw" />
|
||||
</div>
|
||||
<div key="delete" title="Delete Line (⌘D)" className="line-icon" onClick={this.clickDelete}>
|
||||
<i className="fa-sharp fa-regular fa-trash fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="bookmark"
|
||||
title="Bookmark"
|
||||
className={cn("line-icon", "line-bookmark")}
|
||||
onClick={this.clickBookmark}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-bookmark fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
className="line-icon line-sidebar"
|
||||
onClick={this.clickRemoveFromSidebar}
|
||||
title="Move to Sidebar"
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-left-to-line fa-fw" />
|
||||
</div>
|
||||
</If>
|
||||
<Choose>
|
||||
<When condition={containerType == appconst.LineContainer_Main}>
|
||||
<div key="restart" title="Restart Command" className="line-icon" onClick={this.clickRestart}>
|
||||
<i className="fa-sharp fa-regular fa-arrows-rotate fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="delete"
|
||||
title="Delete Line (⌘D)"
|
||||
className="line-icon"
|
||||
onClick={this.clickDelete}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-trash fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="bookmark"
|
||||
title="Bookmark"
|
||||
className={cn("line-icon", "line-bookmark")}
|
||||
onClick={this.clickBookmark}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-bookmark fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="minimize"
|
||||
title={`${isMinimized ? "Show Output" : "Hide Output"}`}
|
||||
className={cn("line-icon", isMinimized ? "active" : "")}
|
||||
onClick={this.clickMinimize}
|
||||
>
|
||||
<Choose>
|
||||
<When condition={isMinimized}>
|
||||
<i className="fa-sharp fa-regular fa-circle-plus fa-fw" />
|
||||
</When>
|
||||
<Otherwise>
|
||||
<i className="fa-sharp fa-regular fa-circle-minus fa-fw" />
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
</div>
|
||||
<div
|
||||
className="line-icon line-sidebar"
|
||||
onClick={this.clickMoveToSidebar}
|
||||
title="Move to Sidebar"
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-right-to-line fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="settings"
|
||||
title="Line Settings"
|
||||
className="line-icon line-icon-shrink-left"
|
||||
onClick={this.handleLineSettings}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-ellipsis-vertical fa-fw" />
|
||||
</div>
|
||||
</When>
|
||||
<When condition={containerType == appconst.LineContainer_Sidebar}>
|
||||
<div
|
||||
key="delete"
|
||||
title="Delete Line (⌘D)"
|
||||
className="line-icon"
|
||||
onClick={this.clickDelete}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-trash fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="bookmark"
|
||||
title="Bookmark"
|
||||
className={cn("line-icon", "line-bookmark")}
|
||||
onClick={this.clickBookmark}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-bookmark fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
className="line-icon line-sidebar"
|
||||
onClick={this.clickRemoveFromSidebar}
|
||||
title="Move to Sidebar"
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-left-to-line fa-fw" />
|
||||
</div>
|
||||
</When>
|
||||
</Choose>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -254,9 +268,9 @@ class LineHeader extends React.Component<{ screen: LineContainerType; line: Line
|
||||
}
|
||||
|
||||
renderMeta1(cmd: Cmd) {
|
||||
let { line } = this.props;
|
||||
const { line } = this.props;
|
||||
let formattedTime: string = "";
|
||||
let restartTs = cmd.getRestartTs();
|
||||
const restartTs = cmd.getRestartTs();
|
||||
let timeTitle: string = null;
|
||||
if (restartTs != null && restartTs > 0) {
|
||||
formattedTime = "restarted @ " + lineutil.getLineDateTimeStr(restartTs);
|
||||
@@ -264,8 +278,8 @@ class LineHeader extends React.Component<{ screen: LineContainerType; line: Line
|
||||
} else {
|
||||
formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
}
|
||||
let renderer = line.renderer;
|
||||
let durationMs = cmd.getDurationMs();
|
||||
const renderer = line.renderer;
|
||||
const durationMs = cmd.getDurationMs();
|
||||
return (
|
||||
<div key="meta1" className="meta meta-line1">
|
||||
<SmallLineAvatar line={line} cmd={cmd} />
|
||||
@@ -287,7 +301,7 @@ class LineHeader extends React.Component<{ screen: LineContainerType; line: Line
|
||||
}
|
||||
|
||||
render() {
|
||||
let { line, cmd } = this.props;
|
||||
const { line, cmd } = this.props;
|
||||
const hidePrompt = getIsHidePrompt(line);
|
||||
return (
|
||||
<div key="header" className={cn("line-header", { "hide-prompt": hidePrompt })}>
|
||||
@@ -303,7 +317,7 @@ class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRigh
|
||||
render() {
|
||||
const { line, cmd } = this.props;
|
||||
const lineNumStr = (line.linenumtemp ? "~" : "#") + String(line.linenum);
|
||||
let status = cmd != null ? cmd.getStatus() : "done";
|
||||
const status = cmd != null ? cmd.getStatus() : "done";
|
||||
const exitcode = cmd != null ? cmd.getExitCode() : 0;
|
||||
const isComment = line.linetype == "text";
|
||||
let icon = null;
|
||||
@@ -359,7 +373,7 @@ class RtnState extends React.Component<{ cmd: Cmd; line: LineType }> {
|
||||
}
|
||||
|
||||
checkStateDiffLoad(): void {
|
||||
let cmd = this.props.cmd;
|
||||
const cmd = this.props.cmd;
|
||||
if (cmd == null || !cmd.getRtnState() || this.rtnStateDiffFetched) {
|
||||
return;
|
||||
}
|
||||
@@ -406,7 +420,7 @@ class RtnState extends React.Component<{ cmd: Cmd; line: LineType }> {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { cmd } = this.props;
|
||||
const { cmd } = this.props;
|
||||
const rsdiff = this.rtnStateDiff.get();
|
||||
const termFontSize = GlobalModel.getTermFontSize();
|
||||
let rtnStateDiffSize = termFontSize - 2;
|
||||
@@ -488,7 +502,7 @@ class LineCmd extends React.Component<
|
||||
if (elem != null) {
|
||||
curHeight = elem.offsetHeight;
|
||||
}
|
||||
let linenum = line.linenum;
|
||||
const linenum = line.linenum;
|
||||
if (DebugHeightProblems && linenum >= MinLine && linenum <= MaxLine) {
|
||||
heightLog[linenum] = heightLog[linenum] || {};
|
||||
heightLog[linenum].heightArr = heightLog[linenum].heightArr || [];
|
||||
@@ -685,7 +699,7 @@ class LineCmd extends React.Component<
|
||||
const isFocused = mobx
|
||||
.computed(
|
||||
() => {
|
||||
let screenFocusType = screen.getFocusType();
|
||||
const screenFocusType = screen.getFocusType();
|
||||
return isPhysicalFocused && screenFocusType == "cmd";
|
||||
},
|
||||
{ name: "computed-isFocused" }
|
||||
@@ -694,7 +708,7 @@ class LineCmd extends React.Component<
|
||||
const shouldCmdFocus = mobx
|
||||
.computed(
|
||||
() => {
|
||||
let screenFocusType = screen.getFocusType();
|
||||
const screenFocusType = screen.getFocusType();
|
||||
return isSelected && screenFocusType == "cmd";
|
||||
},
|
||||
{ name: "computed-shouldCmdFocus" }
|
||||
@@ -725,16 +739,9 @@ class LineCmd extends React.Component<
|
||||
rendererPlugin = PluginModel.getRendererPluginByName(line.renderer);
|
||||
}
|
||||
const rendererType = lineutil.getRendererType(line);
|
||||
const hidePrompt = rendererPlugin?.hidePrompt;
|
||||
const termFontSize = GlobalModel.getTermFontSize();
|
||||
const containerType = screen.getContainerType();
|
||||
const isMinimized = line.linestate["wave:min"] && containerType == appconst.LineContainer_Main;
|
||||
const lhv: LineChromeHeightVars = {
|
||||
numCmdLines: lineutil.countCmdLines(cmd.getCmdStr()),
|
||||
zeroHeight: isMinimized,
|
||||
hasLine2: !hidePrompt,
|
||||
};
|
||||
const chromeHeight = textmeasure.calcLineChromeHeight(GlobalModel.lineHeightEnv, lhv);
|
||||
return (
|
||||
<div
|
||||
className={mainDivCn}
|
||||
@@ -749,55 +756,64 @@ class LineCmd extends React.Component<
|
||||
</If>
|
||||
<LineActions screen={screen} line={line} cmd={cmd} />
|
||||
<LineHeader screen={screen} line={line} cmd={cmd} />
|
||||
<If condition={!isMinimized && isInSidebar}>
|
||||
<div className="sidebar-message" style={{ fontSize: termFontSize }}>
|
||||
showing in sidebar =>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={!isMinimized && !isInSidebar}>
|
||||
<ErrorBoundary plugin={rendererPlugin?.name} lineContext={lineutil.getRendererContext(line)}>
|
||||
<If condition={rendererPlugin == null && !isNoneRenderer}>
|
||||
<TerminalRenderer
|
||||
screen={screen}
|
||||
line={line}
|
||||
width={width}
|
||||
staticRender={staticRender}
|
||||
visible={visible}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
collapsed={false}
|
||||
/>
|
||||
</If>
|
||||
<If condition={rendererPlugin != null && rendererPlugin.rendererType == "simple"}>
|
||||
<SimpleBlobRenderer
|
||||
rendererContainer={screen}
|
||||
lineId={line.lineid}
|
||||
plugin={rendererPlugin}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
initParams={this.makeRendererModelInitializeParams()}
|
||||
scrollToBringIntoViewport={this.scrollToBringIntoViewport}
|
||||
isSelected={isSelected}
|
||||
shouldFocus={shouldCmdFocus}
|
||||
/>
|
||||
</If>
|
||||
<If condition={rendererPlugin != null && rendererPlugin.rendererType == "full"}>
|
||||
<IncrementalRenderer
|
||||
rendererContainer={screen}
|
||||
lineId={line.lineid}
|
||||
plugin={rendererPlugin}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
initParams={this.makeRendererModelInitializeParams()}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
</If>
|
||||
</ErrorBoundary>
|
||||
<If condition={isRtnState}>
|
||||
<RtnState cmd={cmd} line={line} />
|
||||
</If>
|
||||
<If condition={isSelected && !isFocused && rendererType == "terminal"}>
|
||||
<div className="cmd-hints">
|
||||
<div className="hint-item color-nohover-white">focus line ({renderCmdText("L")})</div>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={!isMinimized}>
|
||||
<Choose>
|
||||
<When condition={isInSidebar}>
|
||||
<div className="sidebar-message" style={{ fontSize: termFontSize }}>
|
||||
showing in sidebar =>
|
||||
</div>
|
||||
</When>
|
||||
<Otherwise>
|
||||
<ErrorBoundary
|
||||
plugin={rendererPlugin?.name}
|
||||
lineContext={lineutil.getRendererContext(line)}
|
||||
>
|
||||
<If condition={rendererPlugin == null && !isNoneRenderer}>
|
||||
<TerminalRenderer
|
||||
screen={screen}
|
||||
line={line}
|
||||
width={width}
|
||||
staticRender={staticRender}
|
||||
visible={visible}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
collapsed={false}
|
||||
/>
|
||||
</If>
|
||||
<If condition={rendererPlugin != null && rendererPlugin.rendererType == "simple"}>
|
||||
<SimpleBlobRenderer
|
||||
rendererContainer={screen}
|
||||
lineId={line.lineid}
|
||||
plugin={rendererPlugin}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
initParams={this.makeRendererModelInitializeParams()}
|
||||
scrollToBringIntoViewport={this.scrollToBringIntoViewport}
|
||||
isSelected={isSelected}
|
||||
shouldFocus={shouldCmdFocus}
|
||||
/>
|
||||
</If>
|
||||
<If condition={rendererPlugin != null && rendererPlugin.rendererType == "full"}>
|
||||
<IncrementalRenderer
|
||||
rendererContainer={screen}
|
||||
lineId={line.lineid}
|
||||
plugin={rendererPlugin}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
initParams={this.makeRendererModelInitializeParams()}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
</If>
|
||||
</ErrorBoundary>
|
||||
<If condition={isRtnState}>
|
||||
<RtnState cmd={cmd} line={line} />
|
||||
</If>
|
||||
<If condition={isSelected && !isFocused && rendererType == "terminal"}>
|
||||
<div className="cmd-hints">
|
||||
<div className="hint-item color-nohover-white">
|
||||
focus line ({renderCmdText("L")})
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
@@ -877,11 +893,6 @@ class LineText extends React.Component<
|
||||
name: "computed-isSelected",
|
||||
})
|
||||
.get();
|
||||
const isFocused = mobx
|
||||
.computed(() => screen.getFocusType() == "cmd", {
|
||||
name: "computed-isFocused",
|
||||
})
|
||||
.get();
|
||||
const mainClass = cn("line", "line-text", "focus-parent", { selected: isSelected });
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -15,6 +15,7 @@ import * as util from "@/util/util";
|
||||
import * as lineutil from "./lineutil";
|
||||
|
||||
import "./lines.less";
|
||||
import { GlobalModel } from "@/models";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||
}
|
||||
|
||||
render() {
|
||||
let mainView = GlobalModel.activeMainView.get();
|
||||
const mainView = GlobalModel.activeMainView.get();
|
||||
const historyActive = mainView == "history";
|
||||
const connectionsActive = mainView == "connections";
|
||||
const settingsActive = mainView == "clientsettings";
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
.cmd-aichat {
|
||||
padding-bottom: 0 !important;
|
||||
.auxview-content {
|
||||
flex-flow: column nowrap;
|
||||
height: 100%;
|
||||
|
||||
.chat-window {
|
||||
overflow-y: auto;
|
||||
margin-bottom: 5px;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||
flex: 1 0 auto;
|
||||
|
||||
.chat-textarea {
|
||||
color: var(--app-text-primary-color);
|
||||
background-color: var(--cmdinput-textarea-bg);
|
||||
resize: none;
|
||||
width: 100%;
|
||||
border: transparent;
|
||||
outline: none;
|
||||
overflow: auto;
|
||||
overflow-wrap: anywhere;
|
||||
font-family: var(--termfontfamily);
|
||||
font-weight: normal;
|
||||
line-height: var(--termlineheight);
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg {
|
||||
margin-top: calc(var(--termpad) * 2);
|
||||
margin-bottom: calc(var(--termpad) * 2);
|
||||
|
||||
.chat-msg-header {
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
|
||||
i {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.chat-username {
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg-assistant {
|
||||
color: var(--app-text-color);
|
||||
}
|
||||
|
||||
.chat-msg-user {
|
||||
.msg-text {
|
||||
font-family: var(--markdown-font);
|
||||
font-size: 14px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg-error {
|
||||
color: var(--cmdinput-text-error);
|
||||
font-family: var(--markdown-font);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,20 @@ 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";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import { Markdown } from "@/elements";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||
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 {
|
||||
let AIChatObject = this.props.AIChatObject;
|
||||
let keybindManager = GlobalModel.keybindManager;
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
const AIChatObject = this.props.AIChatObject;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:confirm", (waveEvent) => {
|
||||
AIChatObject.onEnterKeyPressed();
|
||||
@@ -54,10 +56,10 @@ class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
|
||||
@mobxReact.observer
|
||||
class AIChat extends React.Component<{}, {}> {
|
||||
chatListKeyCount: number = 0;
|
||||
textAreaNumLines: mobx.IObservableValue<number> = mobx.observable.box(1, { name: "textAreaNumLines" });
|
||||
chatWindowScrollRef: React.RefObject<HTMLDivElement>;
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||
isFocused: OV<boolean>;
|
||||
termFontSize: number = 14;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
@@ -69,9 +71,8 @@ class AIChat extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let model = GlobalModel;
|
||||
let inputModel = model.inputModel;
|
||||
if (this.chatWindowScrollRef != null && this.chatWindowScrollRef.current != null) {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (this.chatWindowScrollRef?.current != null) {
|
||||
this.chatWindowScrollRef.current.scrollTop = this.chatWindowScrollRef.current.scrollHeight;
|
||||
}
|
||||
if (this.textAreaRef.current != null) {
|
||||
@@ -79,10 +80,11 @@ class AIChat extends React.Component<{}, {}> {
|
||||
inputModel.setCmdInfoChatRefs(this.textAreaRef, this.chatWindowScrollRef);
|
||||
}
|
||||
this.requestChatUpdate();
|
||||
this.onTextAreaChange(null);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.chatWindowScrollRef != null && this.chatWindowScrollRef.current != null) {
|
||||
if (this.chatWindowScrollRef?.current != null) {
|
||||
this.chatWindowScrollRef.current.scrollTop = this.chatWindowScrollRef.current.scrollHeight;
|
||||
}
|
||||
}
|
||||
@@ -92,20 +94,18 @@ class AIChat extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
submitChatMessage(messageStr: string) {
|
||||
let model = GlobalModel;
|
||||
let inputModel = model.inputModel;
|
||||
let curLine = inputModel.getCurLine();
|
||||
let prtn = GlobalModel.submitChatInfoCommand(messageStr, curLine, false);
|
||||
const curLine = GlobalModel.inputModel.getCurLine();
|
||||
const prtn = GlobalModel.submitChatInfoCommand(messageStr, curLine, false);
|
||||
prtn.then((rtn) => {
|
||||
if (!rtn.success) {
|
||||
console.log("submit chat command error: " + rtn.error);
|
||||
}
|
||||
}).catch((error) => {});
|
||||
}).catch((_) => {});
|
||||
}
|
||||
|
||||
getLinePos(elem: any): { numLines: number; linePos: number } {
|
||||
let numLines = elem.value.split("\n").length;
|
||||
let linePos = elem.value.substr(0, elem.selectionStart).split("\n").length;
|
||||
const numLines = elem.value.split("\n").length;
|
||||
const linePos = elem.value.substr(0, elem.selectionStart).split("\n").length;
|
||||
return { numLines, linePos };
|
||||
}
|
||||
|
||||
@@ -121,22 +121,32 @@ class AIChat extends React.Component<{}, {}> {
|
||||
})();
|
||||
}
|
||||
|
||||
// Adjust the height of the textarea to fit the text
|
||||
onTextAreaChange(e: any) {
|
||||
// set height of textarea based on number of newlines
|
||||
mobx.action(() => {
|
||||
this.textAreaNumLines.set(e.target.value.split(/\n/).length);
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
})();
|
||||
// Calculate the bounding height of the text area
|
||||
const textAreaMaxLines = 4;
|
||||
const textAreaLineHeight = this.termFontSize * 1.5;
|
||||
const textAreaMinHeight = textAreaLineHeight;
|
||||
const textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines;
|
||||
|
||||
// Get the height of the wrapped text area content. Courtesy of https://stackoverflow.com/questions/995168/textarea-to-resize-based-on-content-length
|
||||
this.textAreaRef.current.style.height = "1px";
|
||||
const scrollHeight: number = this.textAreaRef.current.scrollHeight;
|
||||
|
||||
// Set the new height of the text area, bounded by the min and max height.
|
||||
const newHeight = Math.min(Math.max(scrollHeight, textAreaMinHeight), textAreaMaxHeight);
|
||||
this.textAreaRef.current.style.height = newHeight + "px";
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
}
|
||||
|
||||
onEnterKeyPressed() {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
let currentRef = this.textAreaRef.current;
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
const currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return;
|
||||
}
|
||||
if (inputModel.getCodeSelectSelectedIndex() == -1) {
|
||||
let messageStr = currentRef.value;
|
||||
const messageStr = currentRef.value;
|
||||
this.submitChatMessage(messageStr);
|
||||
currentRef.value = "";
|
||||
} else {
|
||||
@@ -145,7 +155,7 @@ class AIChat extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
onExpandInputPressed() {
|
||||
let currentRef = this.textAreaRef.current;
|
||||
const currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return;
|
||||
}
|
||||
@@ -154,7 +164,7 @@ class AIChat extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
onArrowUpPressed(): boolean {
|
||||
let currentRef = this.textAreaRef.current;
|
||||
const currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -168,8 +178,8 @@ class AIChat extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
onArrowDownPressed(): boolean {
|
||||
let currentRef = this.textAreaRef.current;
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
const currentRef = this.textAreaRef.current;
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (currentRef == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -190,10 +200,10 @@ class AIChat extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
renderChatMessage(chatItem: OpenAICmdInfoChatMessageType): any {
|
||||
let curKey = "chatmsg-" + this.chatListKeyCount;
|
||||
const curKey = "chatmsg-" + this.chatListKeyCount;
|
||||
this.chatListKeyCount++;
|
||||
let senderClassName = chatItem.isassistantresponse ? "chat-msg-assistant" : "chat-msg-user";
|
||||
let msgClassName = "chat-msg " + senderClassName;
|
||||
const senderClassName = chatItem.isassistantresponse ? "chat-msg-assistant" : "chat-msg-user";
|
||||
const msgClassName = "chat-msg " + senderClassName;
|
||||
let innerHTML: React.JSX.Element = (
|
||||
<span>
|
||||
<div className="chat-msg-header">
|
||||
@@ -226,68 +236,57 @@ class AIChat extends React.Component<{}, {}> {
|
||||
);
|
||||
}
|
||||
|
||||
renderChatWindow(): any {
|
||||
let model = GlobalModel;
|
||||
let inputModel = model.inputModel;
|
||||
let chatMessageItems = inputModel.AICmdInfoChatItems.slice();
|
||||
let chitem: OpenAICmdInfoChatMessageType = null;
|
||||
return (
|
||||
<div className="chat-window" ref={this.chatWindowScrollRef}>
|
||||
<For each="chitem" index="idx" of={chatMessageItems}>
|
||||
{this.renderChatMessage(chitem)}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let model = GlobalModel;
|
||||
let inputModel = model.inputModel;
|
||||
|
||||
const termFontSize = 14;
|
||||
const textAreaMaxLines = 4;
|
||||
const textAreaLineHeight = termFontSize * 1.5;
|
||||
const textAreaPadding = 2 * 0.5 * termFontSize;
|
||||
let textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines + textAreaPadding;
|
||||
let textAreaInnerHeight = this.textAreaNumLines.get() * textAreaLineHeight + textAreaPadding;
|
||||
let isFocused = this.isFocused.get();
|
||||
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
|
||||
const chitem: OpenAICmdInfoChatMessageType = null;
|
||||
const renderKeybindings = mobx
|
||||
.computed(() => {
|
||||
return (
|
||||
this.isFocused.get() ||
|
||||
GlobalModel.inputModel.hasFocus() ||
|
||||
(GlobalModel.getActiveScreen().getFocusType() == "input" &&
|
||||
GlobalModel.activeMainView.get() == "session")
|
||||
);
|
||||
})
|
||||
.get();
|
||||
|
||||
return (
|
||||
<div className="cmd-aichat">
|
||||
<If condition={isFocused}>
|
||||
<AuxiliaryCmdView
|
||||
title="Wave AI"
|
||||
className="cmd-aichat"
|
||||
onClose={() => GlobalModel.inputModel.closeAIAssistantChat(true)}
|
||||
iconClass="fa-sharp fa-solid fa-sparkles"
|
||||
>
|
||||
<If condition={renderKeybindings}>
|
||||
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
|
||||
</If>
|
||||
<div className="cmdinput-titlebar">
|
||||
<div className="title-icon">
|
||||
<i className="fa-sharp fa-solid fa-sparkles" />
|
||||
</div>
|
||||
<div className="title-string">Wave AI</div>
|
||||
<div className="flex-spacer"></div>
|
||||
<div
|
||||
className="close-button"
|
||||
title="Close (ESC)"
|
||||
onClick={() => inputModel.closeAIAssistantChat(true)}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-xmark-large" />
|
||||
<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"
|
||||
ref={this.textAreaRef}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
id="chat-cmd-input"
|
||||
onFocus={this.onTextAreaFocused.bind(this)}
|
||||
onBlur={this.onTextAreaBlur.bind(this)}
|
||||
onChange={this.onTextAreaChange.bind(this)}
|
||||
onKeyDown={this.onKeyDown}
|
||||
style={{ fontSize: this.termFontSize }}
|
||||
className="chat-textarea"
|
||||
placeholder="Send a Message..."
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="titlebar-spacer" />
|
||||
{this.renderChatWindow()}
|
||||
<textarea
|
||||
key="main"
|
||||
ref={this.textAreaRef}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
id="chat-cmd-input"
|
||||
onFocus={this.onTextAreaFocused.bind(this)}
|
||||
onBlur={this.onTextAreaBlur.bind(this)}
|
||||
onChange={this.onTextAreaChange.bind(this)}
|
||||
onKeyDown={this.onKeyDown}
|
||||
style={{ height: textAreaInnerHeight, maxHeight: textAreaMaxHeight, fontSize: termFontSize }}
|
||||
className={cn("chat-textarea")}
|
||||
placeholder="Send a Message..."
|
||||
></textarea>
|
||||
</div>
|
||||
</AuxiliaryCmdView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// For the additonal views, we want less padding on the top and bottom than we want for the base-cmdinput div
|
||||
.auxview {
|
||||
padding: var(--termpad) calc(var(--termpad) * 2) var(--termpad) calc(var(--termpad) * 3 - 1px);
|
||||
overflow: auto;
|
||||
flex-shrink: 1;
|
||||
width: 100%;
|
||||
|
||||
--auxview-titlebar-height: 18px;
|
||||
|
||||
.auxview-titlebar {
|
||||
position: absolute;
|
||||
z-index: 22;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: var(--app-panel-bg-color);
|
||||
color: var(--term-blue);
|
||||
padding: 6px 10px 6px 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
font: var(--base-font);
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
line-height: var(--auxview-titlebar-height);
|
||||
|
||||
.title-string {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
cursor: pointer;
|
||||
i {
|
||||
color: var(--app-icon-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--app-icon-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div:not(.close-button, .flex-spacer) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.auxview-content {
|
||||
display: flex;
|
||||
padding-top: calc(var(--auxview-titlebar-height) + 6px);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import cn from "classnames";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
|
||||
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;
|
||||
|
||||
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>
|
||||
|
||||
<If condition={titleBarContents != null}>{titleBarContents}</If>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,12 +61,17 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
cmdInputClick(e: any): void {
|
||||
baseCmdInputClick(e: React.MouseEvent): void {
|
||||
if (this.promptRef.current != null) {
|
||||
if (this.promptRef.current.contains(e.target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((e.target as HTMLDivElement).classList.contains("cmd-input-context")) {
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
GlobalModel.inputModel.setHistoryFocus(false);
|
||||
GlobalModel.inputModel.giveFocus();
|
||||
}
|
||||
|
||||
@@ -74,8 +79,12 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
clickAIAction(e: any): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
inputModel.openAIAssistantChat();
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (inputModel.aIChatShow.get()) {
|
||||
inputModel.closeAIAssistantChat(true);
|
||||
} else {
|
||||
inputModel.openAIAssistantChat();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@@ -160,6 +169,9 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
}
|
||||
let shellInitMsg: string = null;
|
||||
let hidePrompt = false;
|
||||
|
||||
const openView = inputModel.getOpenView();
|
||||
const hasOpenView = openView ? `has-${openView}` : null;
|
||||
if (ri == null) {
|
||||
let shellStr = "shell";
|
||||
if (!util.isBlank(remote?.defaultshelltype)) {
|
||||
@@ -172,42 +184,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
ref={this.cmdInputRef}
|
||||
className={cn(
|
||||
"cmd-input",
|
||||
{ "has-info": infoShow },
|
||||
{ "has-aichat": aiChatShow },
|
||||
{ "has-history": historyShow },
|
||||
{ active: focusVal }
|
||||
)}
|
||||
>
|
||||
<div className="cmdinput-actions">
|
||||
<If condition={numRunningLines > 0}>
|
||||
<div
|
||||
key="running"
|
||||
className={cn("cmdinput-icon", "running-cmds", { active: filterRunning })}
|
||||
title="Filter for Running Commands"
|
||||
onClick={() => this.toggleFilter(screen)}
|
||||
>
|
||||
<CenteredIcon>{numRunningLines}</CenteredIcon>{" "}
|
||||
<CenteredIcon>
|
||||
<RotateIcon className="rotate warning spin" />
|
||||
</CenteredIcon>
|
||||
</div>
|
||||
</If>
|
||||
<div key="ai" title="Wave AI (Ctrl-Space)" className="cmdinput-icon" onClick={this.clickAIAction}>
|
||||
<i className="fa-sharp fa-regular fa-sparkles fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="history"
|
||||
title="Tab History (Ctrl-R)"
|
||||
className="cmdinput-icon"
|
||||
onClick={this.clickHistoryAction}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-clock-rotate-left fa-fw" />
|
||||
</div>
|
||||
</div>
|
||||
<div ref={this.cmdInputRef} className={cn("cmd-input", hasOpenView, { active: focusVal })}>
|
||||
<If condition={historyShow}>
|
||||
<div className="cmd-input-grow-spacer"></div>
|
||||
<HistoryInfo />
|
||||
@@ -252,7 +229,38 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
</Button>
|
||||
</div>
|
||||
</If>
|
||||
<div key="base-cmdinput" className="base-cmdinput">
|
||||
<div key="base-cmdinput" className="base-cmdinput" onClick={this.baseCmdInputClick}>
|
||||
<div className="cmdinput-actions">
|
||||
<If condition={numRunningLines > 0}>
|
||||
<div
|
||||
key="running"
|
||||
className={cn("cmdinput-icon", "running-cmds", { active: filterRunning })}
|
||||
title="Filter for Running Commands"
|
||||
onClick={() => this.toggleFilter(screen)}
|
||||
>
|
||||
<CenteredIcon>{numRunningLines}</CenteredIcon>{" "}
|
||||
<CenteredIcon>
|
||||
<RotateIcon className="rotate warning spin" />
|
||||
</CenteredIcon>
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
key="ai"
|
||||
title="Wave AI (Ctrl-Space)"
|
||||
className="cmdinput-icon"
|
||||
onClick={this.clickAIAction}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-sparkles fa-fw" />
|
||||
</div>
|
||||
<div
|
||||
key="history"
|
||||
title="Tab History (Ctrl-R)"
|
||||
className="cmdinput-icon"
|
||||
onClick={this.clickHistoryAction}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-clock-rotate-left fa-fw" />
|
||||
</div>
|
||||
</div>
|
||||
<If condition={!hidePrompt}>
|
||||
<div key="prompt" className="cmd-input-context">
|
||||
<div className="has-text-white">
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
.cmd-history {
|
||||
color: var(--app-text-color);
|
||||
font-family: var(--termfontfamily);
|
||||
font-size: var(--termfontsize);
|
||||
|
||||
.auxview-titlebar {
|
||||
.history-opt {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.history-clickable-opt {
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--app-text-primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.history-items {
|
||||
color: var(--app-text-color);
|
||||
width: 100%;
|
||||
|
||||
.history-item {
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
|
||||
.history-line {
|
||||
white-space: pre;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--table-tr-hover-bg-color);
|
||||
}
|
||||
|
||||
&.history-haderror {
|
||||
color: var(--cmdinput-history-item-error-color);
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
font-weight: bold;
|
||||
color: var(--app-text-primary-color);
|
||||
background-color: var(--table-tr-selected-bg-color);
|
||||
&:hover {
|
||||
background-color: var(--table-tr-selected-hover-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-selected.history-haderror {
|
||||
color: var(--cmdinput-history-item-selected-error-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,10 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { isBlank } from "@/util/util";
|
||||
|
||||
import "./historyinfo.less";
|
||||
import { AuxiliaryCmdView } from "./auxview";
|
||||
import { ScrollbarsComponent } from "@/app/common/elements/scrollbars";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
const TDots = "â‹®";
|
||||
@@ -43,7 +47,7 @@ class HItem extends React.Component<
|
||||
if (hitem.remote == null || isBlank(hitem.remote.remoteid)) {
|
||||
return sprintf("%-15s ", "");
|
||||
}
|
||||
let r = GlobalModel.getRemote(hitem.remote.remoteid);
|
||||
const r = GlobalModel.getRemote(hitem.remote.remoteid);
|
||||
if (r == null) {
|
||||
return sprintf("%-15s ", "???");
|
||||
}
|
||||
@@ -71,15 +75,15 @@ class HItem extends React.Component<
|
||||
if (!opts.limitRemote) {
|
||||
remoteStr = this.renderRemote(hitem);
|
||||
}
|
||||
let selectedStr = isSelected ? "*" : " ";
|
||||
let lineNumStr = hitem.linenum > 0 ? "(" + hitem.linenum + ")" : "";
|
||||
const selectedStr = isSelected ? "*" : " ";
|
||||
const lineNumStr = hitem.linenum > 0 ? "(" + hitem.linenum + ")" : "";
|
||||
if (isBlank(opts.queryType) || opts.queryType == "screen") {
|
||||
return selectedStr + sprintf("%7s", lineNumStr) + " " + remoteStr;
|
||||
}
|
||||
if (opts.queryType == "session") {
|
||||
let screenStr = "";
|
||||
if (!isBlank(hitem.screenid)) {
|
||||
let scrName = scrNames[hitem.screenid];
|
||||
const scrName = scrNames[hitem.screenid];
|
||||
if (scrName != null) {
|
||||
screenStr = "[" + truncateWithTDots(scrName, 15) + "]";
|
||||
}
|
||||
@@ -89,19 +93,18 @@ class HItem extends React.Component<
|
||||
if (opts.queryType == "global") {
|
||||
let sessionStr = "";
|
||||
if (!isBlank(hitem.sessionid)) {
|
||||
let sessionName = snames[hitem.sessionid];
|
||||
const sessionName = snames[hitem.sessionid];
|
||||
if (sessionName != null) {
|
||||
sessionStr = "#" + truncateWithTDots(sessionName, 15);
|
||||
}
|
||||
}
|
||||
let screenStr = "";
|
||||
if (!isBlank(hitem.screenid)) {
|
||||
let scrName = scrNames[hitem.screenid];
|
||||
const scrName = scrNames[hitem.screenid];
|
||||
if (scrName != null) {
|
||||
screenStr = "[" + truncateWithTDots(scrName, 13) + "]";
|
||||
}
|
||||
}
|
||||
let ssStr = sessionStr + screenStr;
|
||||
return (
|
||||
selectedStr +
|
||||
sprintf("%15s ", sessionStr) +
|
||||
@@ -116,12 +119,12 @@ class HItem extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
let { hitem, isSelected, opts, snames, scrNames } = this.props;
|
||||
let lines = hitem.cmdstr.split("\n");
|
||||
const { hitem, isSelected, opts, snames, scrNames } = this.props;
|
||||
const lines = hitem.cmdstr.split("\n");
|
||||
let line: string = "";
|
||||
let idx = 0;
|
||||
let infoText = this.renderHInfoText(hitem, opts, isSelected, snames, scrNames);
|
||||
let infoTextSpacer = sprintf("%" + infoText.length + "s", "");
|
||||
const infoText = this.renderHInfoText(hitem, opts, isSelected, snames, scrNames);
|
||||
const infoTextSpacer = sprintf("%" + infoText.length + "s", "");
|
||||
return (
|
||||
<div
|
||||
key={hitem.historynum}
|
||||
@@ -151,9 +154,10 @@ 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() {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
let hitem = inputModel.getHistorySelectedItem();
|
||||
if (hitem == null) {
|
||||
hitem = inputModel.getFirstHistoryItem();
|
||||
@@ -170,15 +174,15 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
|
||||
@boundMethod
|
||||
handleItemClick(hitem: HistoryItem) {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
let selItem = inputModel.getHistorySelectedItem();
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
const selItem = inputModel.getHistorySelectedItem();
|
||||
inputModel.setHistoryFocus(true);
|
||||
if (this.lastClickHNum == hitem.historynum && selItem != null && selItem.historynum == hitem.historynum) {
|
||||
inputModel.grabSelectedHistoryItem();
|
||||
return;
|
||||
}
|
||||
inputModel.giveFocus();
|
||||
inputModel.setHistorySelectionNum(hitem.historynum);
|
||||
let now = Date.now();
|
||||
const now = Date.now();
|
||||
this.lastClickHNum = hitem.historynum;
|
||||
this.lastClickTs = now;
|
||||
setTimeout(() => {
|
||||
@@ -191,24 +195,41 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
|
||||
@boundMethod
|
||||
handleClickType() {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
inputModel.setHistoryFocus(true);
|
||||
inputModel.toggleHistoryType();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleClickRemote() {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
inputModel.setHistoryFocus(true);
|
||||
inputModel.toggleRemoteType();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
getTitleBarContents(): React.ReactElement[] {
|
||||
const opts = GlobalModel.inputModel.historyQueryOpts.get();
|
||||
|
||||
return [
|
||||
<div className="history-opt history-clickable-opt" key="screen" onClick={this.handleClickType}>
|
||||
[for {opts.queryType} ⌘S]
|
||||
</div>,
|
||||
<div className="history-opt" key="query-str" title="type to search">
|
||||
[containing '{opts.queryStr}']
|
||||
</div>,
|
||||
<div className="history-opt history-clickable-opt" key="remote" onClick={this.handleClickRemote}>
|
||||
[{opts.limitRemote ? "this" : "any"} remote ⌘R]
|
||||
</div>,
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
let idx: number = 0;
|
||||
let selItem = inputModel.getHistorySelectedItem();
|
||||
let hitems = inputModel.getFilteredHistoryItems();
|
||||
hitems = hitems.slice().reverse();
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
const selItem = inputModel.getHistorySelectedItem();
|
||||
const hitems = inputModel.getFilteredHistoryItems().slice().reverse();
|
||||
const opts = inputModel.historyQueryOpts.get();
|
||||
let hitem: HistoryItem = null;
|
||||
let opts = inputModel.historyQueryOpts.get();
|
||||
let snames: Record<string, string> = {};
|
||||
let scrNames: Record<string, string> = {};
|
||||
if (opts.queryType == "global") {
|
||||
@@ -218,53 +239,42 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
scrNames = GlobalModel.getScreenNames();
|
||||
}
|
||||
return (
|
||||
<div className="cmd-history hide-scrollbar">
|
||||
<div className="cmdinput-titlebar history-title">
|
||||
<div className="title-icon">
|
||||
<i className="fa-sharp fa-solid fa-clock-rotate-left" />
|
||||
</div>
|
||||
<div className="title-string">History</div>
|
||||
<div className="spacer"></div>
|
||||
<div className="history-opt history-clickable-opt" onClick={this.handleClickType}>
|
||||
[for {opts.queryType} ⌘S]
|
||||
</div>
|
||||
<div className="spacer"></div>
|
||||
<div className="history-opt" title="type to search">
|
||||
[containing '{opts.queryStr}']
|
||||
</div>
|
||||
<div className="spacer"></div>
|
||||
<div className="history-opt history-clickable-opt" onClick={this.handleClickRemote}>
|
||||
[{opts.limitRemote ? "this" : "any"} remote ⌘R]
|
||||
</div>
|
||||
<div className="flex-spacer"></div>
|
||||
<div className="close-button" title="Close (ESC)">
|
||||
<i className="fa-sharp fa-solid fa-xmark-large" onClick={this.handleClose}></i>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"history-items",
|
||||
{ "show-remotes": !opts.limitRemote },
|
||||
{ "show-sessions": opts.queryType == "global" }
|
||||
)}
|
||||
<AuxiliaryCmdView
|
||||
title="History"
|
||||
className="cmd-history"
|
||||
onClose={this.handleClose}
|
||||
titleBarContents={this.getTitleBarContents()}
|
||||
iconClass="fa-sharp fa-solid fa-clock-rotate-left"
|
||||
>
|
||||
<ScrollbarsComponent
|
||||
childrenRef={this.historyItemsRef}
|
||||
options={{ scrollbars: { autoHide: "scroll" } }}
|
||||
>
|
||||
<div className="titlebar-spacer" />
|
||||
<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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user