mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
cmdinput actions (filter, ai, and history) (#395)
* clientsettings fixups -- border top, padding, and remove theme dropdown for prod * new cmdinput actions, get the filter commands showing, titlebars for info history and info aichat, toggles for history
This commit is contained in:
@@ -46,6 +46,7 @@
|
||||
/* global colors */
|
||||
--app-bg-color: black;
|
||||
--app-accent-color: rgb(88, 193, 66);
|
||||
--app-accent-bg-color: rgba(88, 193, 66, 0.2);
|
||||
--app-error-color: rgb(204, 0, 0);
|
||||
--app-warning-color: rgb(255, 165, 0);
|
||||
--app-success-color: rgb(38, 97, 26);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.clientsettings-view {
|
||||
.content {
|
||||
padding: 0 18px 0 30px;
|
||||
padding: 14px 18px 0 30px;
|
||||
border-top: 1px solid var(--app-border-color);
|
||||
}
|
||||
|
||||
.wave-dropdown {
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel, getApi } from "@/models";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "@/common/elements";
|
||||
@@ -191,17 +192,19 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Theme</div>
|
||||
<div className="settings-input">
|
||||
<Dropdown
|
||||
className="theme-dropdown"
|
||||
options={this.getThemes()}
|
||||
defaultValue={curTheme}
|
||||
onChange={this.handleChangeTheme}
|
||||
/>
|
||||
<If condition={GlobalModel.isDev}>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Theme</div>
|
||||
<div className="settings-input">
|
||||
<Dropdown
|
||||
className="theme-dropdown"
|
||||
options={this.getThemes()}
|
||||
defaultValue={curTheme}
|
||||
onChange={this.handleChangeTheme}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Client ID</div>
|
||||
<div className="settings-input">{cdata.clientid}</div>
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--line-actions-active-color);
|
||||
color: var(--app-icon-hover-color);
|
||||
}
|
||||
|
||||
padding: 4px 4px;
|
||||
|
||||
@@ -152,8 +152,8 @@ class AIChat extends React.Component<{}, {}> {
|
||||
innerHTML = (
|
||||
<span>
|
||||
<div className="chat-msg-header">
|
||||
<i className="fa-sharp fa-solid fa-headset"></i>
|
||||
<div className="chat-username">ChatGPT</div>
|
||||
<i className="fa-sharp fa-solid fa-sparkles"></i>
|
||||
<div className="chat-username">AI Assistant</div>
|
||||
</div>
|
||||
<Markdown text={chatItem.assistantresponse.message} codeSelect />
|
||||
</span>
|
||||
@@ -195,6 +195,21 @@ class AIChat extends React.Component<{}, {}> {
|
||||
|
||||
return (
|
||||
<div className="cmd-aichat">
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="titlebar-spacer" />
|
||||
{this.renderChatWindow()}
|
||||
<textarea
|
||||
key="main"
|
||||
@@ -205,7 +220,7 @@ class AIChat extends React.Component<{}, {}> {
|
||||
onKeyDown={this.onKeyDown}
|
||||
style={{ height: textAreaInnerHeight, maxHeight: textAreaMaxHeight, fontSize: termFontSize }}
|
||||
className={cn("chat-textarea")}
|
||||
placeholder="Send a Message to ChatGPT..."
|
||||
placeholder="Send a Message..."
|
||||
></textarea>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,99 @@
|
||||
&.active {
|
||||
}
|
||||
|
||||
&.has-info,
|
||||
&.has-aichat,
|
||||
&.has-history {
|
||||
.cmdinput-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.titlebar-spacer {
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
.cmdinput-actions {
|
||||
position: absolute;
|
||||
border-radius: 4px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
font-size: calc(var(--termfontsize) + 2px);
|
||||
line-height: 1.2;
|
||||
|
||||
// we want to align to 2nd line of meta. that's 2xPad + 1xLineHightSm
|
||||
// height of actions is 1xLineHeight + 8px (2x2px padding on icons)
|
||||
top: calc(var(--termpad));
|
||||
right: calc(var(--termpad) * 2);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.cmdinput-icon {
|
||||
color: var(--app-icon-hover-color);
|
||||
opacity: 0.5;
|
||||
|
||||
&.running-cmds i {
|
||||
color: var(--app-warning-color);
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.line-icon + .line-icon:not(.line-icon-shrink-left) {
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.cmdinput-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%;
|
||||
overflow-x: auto;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
font: var(--base-font);
|
||||
|
||||
.title-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.title-string {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
cursor: pointer;
|
||||
i {
|
||||
color: var(--app-icon-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--app-icon-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-info {
|
||||
padding-top: var(--termpad);
|
||||
}
|
||||
@@ -23,23 +116,20 @@
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
&.has-history {
|
||||
&.has-history,
|
||||
&.has-aichat {
|
||||
padding-top: var(--termpad);
|
||||
height: max(300px, 40%);
|
||||
height: max(300px, 70%);
|
||||
}
|
||||
|
||||
&.has-remote {
|
||||
max-height: max(300px, 70%);
|
||||
}
|
||||
|
||||
&.has-aichat {
|
||||
max-height: max(300px, 70%);
|
||||
}
|
||||
|
||||
.remote-status-warning {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: var(--term-yellow);
|
||||
color: var(--app-warning-color);
|
||||
align-items: center;
|
||||
|
||||
.button {
|
||||
@@ -300,19 +390,6 @@
|
||||
flex-shrink: 1;
|
||||
|
||||
.history-title {
|
||||
position: absolute;
|
||||
z-index: 102;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
background-color: var(--app-bg-color);
|
||||
color: var(--term-blue);
|
||||
padding-bottom: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
|
||||
div:first-child {
|
||||
margin-left: var(--termpad);
|
||||
}
|
||||
@@ -321,22 +398,21 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.history-clickable-opt {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--app-text-primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.history-clickable-opt {
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.grow-spacer {
|
||||
flex: 1 0 10px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.history-items {
|
||||
margin-top: 24px;
|
||||
color: var(--app-text-color);
|
||||
|
||||
padding-bottom: 6px;
|
||||
|
||||
@@ -70,7 +70,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickAIHint(e: any): void {
|
||||
clickAIAction(e: any): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
@@ -78,7 +78,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickHistoryHint(e: any): void {
|
||||
clickHistoryAction(e: any): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -131,6 +131,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
const inputMode: string = inputModel.inputMode.get();
|
||||
const textAreaInputKey = screen == null ? "null" : screen.screenId;
|
||||
const win = GlobalModel.getScreenLinesById(screen.screenId);
|
||||
const filterRunning = screen.filterRunning.get();
|
||||
let numRunningLines = 0;
|
||||
if (win != null) {
|
||||
numRunningLines = mobx.computed(() => win.getRunningCmdLines().length).get();
|
||||
@@ -142,9 +143,33 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
"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)}
|
||||
>
|
||||
{numRunningLines} <i className="fa-sharp fa-regular fa-rotate fa-spin fa-fw" />
|
||||
</div>
|
||||
</If>
|
||||
<div key="ai" title="Wave AI (Cmd-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={historyShow}>
|
||||
<div className="cmd-input-grow-spacer"></div>
|
||||
<HistoryInfo />
|
||||
@@ -188,14 +213,6 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
<Prompt rptr={rptr} festate={feState} color={true} />
|
||||
</span>
|
||||
</div>
|
||||
<If condition={numRunningLines > 0}>
|
||||
<div onClick={() => this.toggleFilter(screen)} className="cmd-input-filter">
|
||||
{numRunningLines}
|
||||
<div className="avatar">
|
||||
<RotateIcon className="warning spin" />
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
<div
|
||||
key="input"
|
||||
|
||||
@@ -189,6 +189,18 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleClickType() {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
inputModel.toggleHistoryType();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleClickRemote() {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
inputModel.toggleRemoteType();
|
||||
}
|
||||
|
||||
render() {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
let idx: number = 0;
|
||||
@@ -207,19 +219,27 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
}
|
||||
return (
|
||||
<div className="cmd-history hide-scrollbar">
|
||||
<div className="history-title">
|
||||
<div className="history-label">history</div>
|
||||
<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">[for {opts.queryType} ⌘S]</div>
|
||||
<div className="spacer"></div>
|
||||
<div className="history-opt">[containing '{opts.queryStr}']</div>
|
||||
<div className="spacer"></div>
|
||||
<div className="history-opt">[{opts.limitRemote ? "this" : "any"} remote ⌘R]</div>
|
||||
<div className="grow-spacer"></div>
|
||||
<div className="history-clickable-opt" onClick={this.handleClose}>
|
||||
(ESC)
|
||||
<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(
|
||||
@@ -228,6 +248,7 @@ class HistoryInfo extends React.Component<{}, {}> {
|
||||
{ "show-sessions": opts.queryType == "global" }
|
||||
)}
|
||||
>
|
||||
<div className="titlebar-spacer" />
|
||||
<If condition={hitems.length == 0}>[no history]</If>
|
||||
<If condition={hitems.length > 0}>
|
||||
<For each="hitem" index="idx" of={hitems}>
|
||||
|
||||
@@ -362,29 +362,14 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "Cmd:r") || checkKeyPressed(waveEvent, "Ctrl:r")) {
|
||||
e.preventDefault();
|
||||
let opts = mobx.toJS(inputModel.historyQueryOpts.get());
|
||||
if (opts.limitRemote) {
|
||||
opts.limitRemote = false;
|
||||
opts.limitRemoteInstance = false;
|
||||
} else {
|
||||
opts.limitRemote = true;
|
||||
opts.limitRemoteInstance = true;
|
||||
}
|
||||
inputModel.setHistoryQueryOpts(opts);
|
||||
e.stopPropagation();
|
||||
inputModel.toggleRemoteType();
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "Cmd:s") || checkKeyPressed(waveEvent, "Ctrl:s")) {
|
||||
e.preventDefault();
|
||||
let opts = mobx.toJS(inputModel.historyQueryOpts.get());
|
||||
let htype = opts.queryType;
|
||||
if (htype == "screen") {
|
||||
htype = "session";
|
||||
} else if (htype == "session") {
|
||||
htype = "global";
|
||||
} else {
|
||||
htype = "screen";
|
||||
}
|
||||
inputModel.setHistoryType(htype);
|
||||
e.stopPropagation();
|
||||
inputModel.toggleHistoryType();
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "Tab")) {
|
||||
|
||||
@@ -181,8 +181,32 @@
|
||||
}
|
||||
|
||||
.filter-running {
|
||||
margin: auto 1rem 0 1rem;
|
||||
align-self: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
border-top: 1px solid var(--app-border-color);
|
||||
padding: calc(var(--termpad) + 2px) var(--termpad) calc(var(--termpad) + 2px) var(--termpad);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.filter-content {
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
color: var(--app-text-primary-color);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.filter-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--app-accent-bg-color);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -763,15 +763,11 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
|
||||
</If>
|
||||
<If condition={screen.filterRunning.get()}>
|
||||
<div className="filter-running">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="color-yellow"
|
||||
style={{ borderRadius: "999px" }}
|
||||
onClick={this.disableFilter}
|
||||
>
|
||||
<div className="filter-mask" />
|
||||
<div className="filter-content" onClick={this.disableFilter}>
|
||||
Showing Running Commands
|
||||
<i className="fa-sharp fa-solid fa-xmark" />
|
||||
</Button>
|
||||
<i className="fa-sharp fa-solid fa-xmark-large" />
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
|
||||
@@ -88,6 +88,31 @@ class InputModel {
|
||||
})();
|
||||
}
|
||||
|
||||
toggleHistoryType(): void {
|
||||
let opts = mobx.toJS(this.historyQueryOpts.get());
|
||||
let htype = opts.queryType;
|
||||
if (htype == "screen") {
|
||||
htype = "session";
|
||||
} else if (htype == "session") {
|
||||
htype = "global";
|
||||
} else {
|
||||
htype = "screen";
|
||||
}
|
||||
this.setHistoryType(htype);
|
||||
}
|
||||
|
||||
toggleRemoteType(): void {
|
||||
let opts = mobx.toJS(this.historyQueryOpts.get());
|
||||
if (opts.limitRemote) {
|
||||
opts.limitRemote = false;
|
||||
opts.limitRemoteInstance = false;
|
||||
} else {
|
||||
opts.limitRemote = true;
|
||||
opts.limitRemoteInstance = true;
|
||||
}
|
||||
this.setHistoryQueryOpts(opts);
|
||||
}
|
||||
|
||||
onInputFocus(isFocused: boolean): void {
|
||||
mobx.action(() => {
|
||||
if (isFocused) {
|
||||
|
||||
Reference in New Issue
Block a user