filter history by remote

This commit is contained in:
sawka 2022-08-31 12:00:53 -07:00
parent e2212ad661
commit bdf5e0fc09
4 changed files with 120 additions and 34 deletions

View File

@ -8,7 +8,7 @@ import dayjs from 'dayjs'
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
import cn from "classnames"
import {TermWrap} from "./term";
import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem} from "./types";
import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts} from "./types";
import localizedFormat from 'dayjs/plugin/localizedFormat';
import {GlobalModel, GlobalCommandRunner, Session, Cmd, Window, Screen, ScreenWindow, riToRPtr} from "./model";
@ -547,6 +547,22 @@ class TextAreaInput extends React.Component<{}, {}> {
let opts = mobx.toJS(inputModel.historyQueryOpts.get());
opts.includeMeta = !opts.includeMeta;
inputModel.setHistoryQueryOpts(opts);
return;
}
if (e.code == "KeyR" && (e.getModifierState("Meta") && !e.getModifierState("Shift"))) {
console.log("meta-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);
return;
}
if (e.code == "Tab") {
e.preventDefault();
@ -602,6 +618,9 @@ class TextAreaInput extends React.Component<{}, {}> {
displayLines = 5;
}
let disabled = inputModel.historyShow.get();
if (disabled) {
displayLines = 1;
}
return (
<div className="control cmd-input-control is-expanded">
<textarea spellCheck="false" id="main-cmd-input" onFocus={this.handleMainFocus} rows={displayLines} value={curLine} onKeyDown={this.onKeyDown} onChange={this.onChange} className={cn("textarea", {"display-disabled": disabled})}></textarea>
@ -649,13 +668,36 @@ class HistoryInfo extends React.Component<{}, {}> {
}, 3000);
}
renderHItem(hitem : HistoryItem, isSelected : boolean) : any {
renderRemote(hitem : HistoryItem) : any {
if (hitem.remote == null || isBlank(hitem.remote.remoteid)) {
return sprintf("%-15s ", "")
}
let r = GlobalModel.getRemote(hitem.remote.remoteid);
if (r == null) {
return sprintf("%-15s ", "???")
}
let rname = "";
if (!isBlank(r.remotealias)) {
rname = r.remotealias;
}
else {
rname = r.remotecanonicalname;
}
if (!isBlank(hitem.remote.name)) {
rname = rname + ":" + hitem.remote.name;
}
let rtn = sprintf("%-15s ", "[" + rname + "]")
return rtn;
}
renderHItem(hitem : HistoryItem, opts : HistoryQueryOpts, isSelected : boolean) : any {
let lines = hitem.cmdstr.split("\n");
let line : string = "";
let idx = 0;
let limitRemote = opts.limitRemote;
return (
<div key={hitem.historynum} className={cn("history-item", {"is-selected": isSelected}, {"history-haderror": hitem.haderror}, "hnum-" + hitem.historynum)} onClick={() => this.handleItemClick(hitem)}>
<div className="history-line">{(isSelected ? "*" : " ")}{sprintf("%5s", hitem.historynum)} {lines[0]}</div>
<div className="history-line">{(isSelected ? "*" : " ")}{sprintf("%5s", hitem.historynum)} {!limitRemote ? this.renderRemote(hitem) : ""}{lines[0]}</div>
<For each="line" index="index" of={lines.slice(1)}>
<div key={idx} className="history-line">{line}</div>
</For>
@ -679,26 +721,26 @@ class HistoryInfo extends React.Component<{}, {}> {
return (
<div className="cmd-history">
<div className="history-title">
history
{" "}
<span className="history-opt">[containing '{opts.queryStr}']</span>
{" "}
<span className="history-opt">[this session &#x2318;S]</span>
{" "}
<span className="history-opt">[this window &#x2318;W]</span>
{" "}
<span className="history-opt">[this remote &#x2318;R]</span>
{" "}
<span className="history-opt">[{opts.includeMeta ? "including" : "excluding"} metacmds &#x2318;M]</span>
{" "} <span className="history-clickable-opt" onClick={this.handleClose}>(close ESC)</span>
<div>history</div>
<div className="spacer"></div>
<div className="history-opt">[for window &#x2318;W]</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 &#x2318;R]</div>
<div className="spacer"></div>
<div className="history-opt">[{opts.includeMeta ? "" : "no "}metacmds &#x2318;M]</div>
<div className="grow-spacer"></div>
<div className="history-clickable-opt" onClick={this.handleClose}>(ESC)</div>
<div className="spacer"></div>
</div>
<div className="history-items">
<div className={cn("history-items", {"show-remotes": !opts.limitRemote})}>
<If condition={hitems.length == 0}>
[no history]
</If>
<If condition={hitems.length > 0}>
<For each="hitem" index="idx" of={hitems}>
{this.renderHItem(hitem, (hitem == selItem))}
{this.renderHItem(hitem, opts, (hitem == selItem))}
</For>
</If>
</div>
@ -749,8 +791,9 @@ class CmdInput extends React.Component<{}, {}> {
let line : string = null;
let idx : number = 0;
return (
<div className={cn("box cmd-input has-background-black", {"has-info": infoShow || historyShow})}>
<div className={cn("box cmd-input has-background-black", {"has-info": infoShow}, {"has-history": historyShow})}>
<If condition={historyShow}>
<div className="cmd-input-grow-spacer"></div>
<HistoryInfo/>
</If>
<div className="cmd-input-info" style={{display: (infoShow ? "block" : "none")}}>

View File

@ -4,7 +4,7 @@ import {boundMethod} from "autobind-decorator";
import {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeSimpleData} from "./util";
import {TermWrap} from "./term";
import {v4 as uuidv4} from "uuid";
import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType} from "./types";
import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts} from "./types";
import {WSControl} from "./ws";
var GlobalUser = "sawka";
@ -560,17 +560,6 @@ class Session {
}
}
type HistoryQueryOpts = {
queryType : "global" | "session" | "window";
limitRemote : boolean,
limitRemoteInstance : boolean,
limitUser : boolean,
queryStr : string,
maxItems : number,
includeMeta : boolean,
fromTs : number,
};
function getDefaultHistoryQueryOpts() : HistoryQueryOpts {
return {
queryType: "window",
@ -591,6 +580,7 @@ class InputModel {
historyLoading : mobx.IObservableValue<boolean> = mobx.observable.box(false);
historyAfterLoadIndex : number = 0;
historyItems : mobx.IObservableValue<HistoryItem[]> = mobx.observable.box(null, {name: "history-items", deep: false}); // sorted in reverse (most recent is index 0)
filteredHistoryItems : mobx.IComputedValue<HistoryItem[]> = null;
historyIndex : mobx.IObservableValue<number> = mobx.observable.box(0, {name: "history-index"}); // 1-indexed (because 0 is current)
modHistory : mobx.IObservableArray<string> = mobx.observable.array([""], {name: "mod-history"});
historyQueryOpts : OV<HistoryQueryOpts> = mobx.observable.box(getDefaultHistoryQueryOpts());
@ -598,6 +588,12 @@ class InputModel {
infoMsg : OV<InfoType> = mobx.observable.box(null);
infoTimeoutId : any = null;
constructor() {
this.filteredHistoryItems = mobx.computed(() => {
return this._getFilteredHistoryItems();
});
}
_focusCmdInput() : void {
let elem = document.getElementById("main-cmd-input");
if (elem != null) {
@ -776,6 +772,10 @@ class InputModel {
}
getFilteredHistoryItems() : HistoryItem[] {
return this.filteredHistoryItems.get();
}
_getFilteredHistoryItems() : HistoryItem[] {
let hitems : HistoryItem[] = this.historyItems.get() ?? [];
let rtn : HistoryItem[] = [];
let opts = mobx.toJS(this.historyQueryOpts.get());
@ -784,6 +784,7 @@ class InputModel {
if (curRemote == null) {
curRemote : RemotePtrType = {ownerid: "", name: "", remoteid: ""};
}
curRemote = mobx.toJS(curRemote);
for (let i=0; i<hitems.length; i++) {
let hitem = hitems[i];
if (hitem.ismetacmd) {
@ -838,6 +839,10 @@ class InputModel {
}
let buffer = 15;
let titleHeight = 24;
let titleDiv = document.querySelector(".cmd-history .history-title");
if (titleDiv != null) {
titleHeight = titleDiv.offsetHeight + 2;
}
let elemOffset = elem.offsetTop;
let elemHeight = elem.clientHeight;
let topPos = historyDiv.scrollTop;

View File

@ -585,6 +585,15 @@ body .xterm .xterm-viewport {
padding-top: 5px;
}
&.has-history {
padding-top: 5px;
height: max(300px, 40%);
}
.cmd-input-grow-spacer {
flex-grow: 1;
}
.cmd-input-context {
color: #fff;
font-family: 'JetBrains Mono', monospace;
@ -643,16 +652,30 @@ body .xterm .xterm-viewport {
background-color: black;
font-family: 'JetBrains Mono', monospace;
font-weight: 600;
font-size: 14px;
font-size: 12px;
color: #729fcf;
padding-bottom: 4px;
display: flex;
flex-direction: row;
width: calc(100% - 40px);
overflow-x: auto;
.history-opt {
white-space: nowrap;
}
.history-clickable-opt {
white-space: nowrap;
cursor: pointer;
}
.grow-spacer {
flex: 1 0 10px;
}
.spacer {
flex: 0 0 10px;
}
}
.history-items {
@ -664,7 +687,11 @@ body .xterm .xterm-viewport {
.history-line {
white-space: pre;
margin-left: 58px;
margin-left: 58px; // 8px per char
}
&.show-remotes .history-line {
margin-left: 174px;
}
.history-item.history-haderror {
@ -672,7 +699,7 @@ body .xterm .xterm-viewport {
}
.history-line:first-child {
margin-left: 0;
margin-left: 0 !important;
}
.history-item {

View File

@ -241,6 +241,17 @@ type InfoType = {
timeoutms? : number,
};
type HistoryQueryOpts = {
queryType : "global" | "session" | "window";
limitRemote : boolean,
limitRemoteInstance : boolean,
limitUser : boolean,
queryStr : string,
maxItems : number,
includeMeta : boolean,
fromTs : number,
};
type UpdateMessage = PtyDataUpdateType | ModelUpdateType;
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDonePacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType};
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDonePacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts};