mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-08 19:38:51 +01:00
implement info panel, more control in cmd-input, completions, errors
This commit is contained in:
parent
1f4ac87a9a
commit
5082330dcf
226
src/main.tsx
226
src/main.tsx
@ -18,6 +18,51 @@ function getLineId(line : LineType) : string {
|
|||||||
return sprintf("%s-%s-%s", line.sessionid, line.windowid, line.lineid);
|
return sprintf("%s-%s-%s", line.sessionid, line.windowid, line.lineid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRemoteStr(remote : RemoteType) : string {
|
||||||
|
if (remote == null) {
|
||||||
|
return "(no remote)";
|
||||||
|
}
|
||||||
|
if (remote.remotevars.local) {
|
||||||
|
return sprintf("%s@%s", remote.remotevars.remoteuser, "local")
|
||||||
|
}
|
||||||
|
else if (remote.remotevars.remotehost) {
|
||||||
|
return sprintf("%s@%s", remote.remotevars.remoteuser, remote.remotevars.remotehost);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let host = remote.remotevars.host || "unknown";
|
||||||
|
if (remote.remotevars.user) {
|
||||||
|
return sprintf("%s@%s", remote.remotevars.user, host)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceHomePath(path : string, homeDir : string) : string {
|
||||||
|
if (path == homeDir) {
|
||||||
|
return "~";
|
||||||
|
}
|
||||||
|
if (path.startsWith(homeDir + "/")) {
|
||||||
|
return "~" + path.substr(homeDir.length);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCwdStr(remote : RemoteType, state : RemoteStateType) : string {
|
||||||
|
if ((state == null || state.cwd == null) && remote != null) {
|
||||||
|
return "~";
|
||||||
|
}
|
||||||
|
let cwd = "(unknown)";
|
||||||
|
if (state && state.cwd) {
|
||||||
|
cwd = state.cwd;
|
||||||
|
}
|
||||||
|
if (remote && remote.remotevars.home) {
|
||||||
|
cwd = replaceHomePath(cwd, remote.remotevars.home)
|
||||||
|
}
|
||||||
|
return cwd;
|
||||||
|
}
|
||||||
|
|
||||||
function getLineDateStr(ts : number) : string {
|
function getLineDateStr(ts : number) : string {
|
||||||
let lineDate = new Date(ts);
|
let lineDate = new Date(ts);
|
||||||
let nowDate = new Date();
|
let nowDate = new Date();
|
||||||
@ -107,16 +152,6 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceHomePath(path : string, homeDir : string) : string {
|
|
||||||
if (path == homeDir) {
|
|
||||||
return "~";
|
|
||||||
}
|
|
||||||
if (path.startsWith(homeDir + "/")) {
|
|
||||||
return "~" + path.substr(homeDir.length);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCmdText(cmd : Cmd, remote : RemoteType) : any {
|
renderCmdText(cmd : Cmd, remote : RemoteType) : any {
|
||||||
if (cmd == null) {
|
if (cmd == null) {
|
||||||
return (
|
return (
|
||||||
@ -125,30 +160,8 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let promptStr = "";
|
let promptStr = getRemoteStr(remote);
|
||||||
if (remote.remotevars.local) {
|
let cwd = getCwdStr(remote, cmd.getRemoteState());
|
||||||
promptStr = sprintf("%s@%s", remote.remotevars.remoteuser, "local")
|
|
||||||
}
|
|
||||||
else if (remote.remotevars.remotehost) {
|
|
||||||
promptStr = sprintf("%s@%s", remote.remotevars.remoteuser, remote.remotevars.remotehost)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let host = remote.remotevars.host || "unknown";
|
|
||||||
if (remote.remotevars.user) {
|
|
||||||
promptStr = sprintf("%s@%s", remote.remotevars.user, host)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
promptStr = host;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let cwd = "(unknown)";
|
|
||||||
let remoteState = cmd.getRemoteState();
|
|
||||||
if (remoteState && remoteState.cwd) {
|
|
||||||
cwd = remoteState.cwd;
|
|
||||||
}
|
|
||||||
if (remote.remotevars.home) {
|
|
||||||
cwd = this.replaceHomePath(cwd, remote.remotevars.home)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className="metapart-mono cmdtext">
|
<div className="metapart-mono cmdtext">
|
||||||
<span className="term-bright-green">[{promptStr} {cwd}]</span> {cmd.getSingleLineCmdText()}
|
<span className="term-bright-green">[{promptStr} {cwd}]</span> {cmd.getSingleLineCmdText()}
|
||||||
@ -221,110 +234,143 @@ class Line extends React.Component<{sw : ScreenWindow, line : LineType, width :
|
|||||||
|
|
||||||
@mobxReact.observer
|
@mobxReact.observer
|
||||||
class CmdInput extends React.Component<{}, {}> {
|
class CmdInput extends React.Component<{}, {}> {
|
||||||
historyIndex : mobx.IObservableValue<number> = mobx.observable.box(0, {name: "history-index"});
|
lastTabCurLine : mobx.IObservableValue<string> = mobx.observable.box(null);
|
||||||
modHistory : mobx.IObservableArray<string> = mobx.observable.array([""], {name: "mod-history"});
|
|
||||||
|
|
||||||
@mobx.action @boundMethod
|
@mobx.action @boundMethod
|
||||||
onKeyDown(e : any) {
|
onKeyDown(e : any) {
|
||||||
mobx.action(() => {
|
mobx.action(() => {
|
||||||
let model = GlobalModel;
|
let model = GlobalModel;
|
||||||
|
let inputModel = model.inputModel;
|
||||||
let win = model.getActiveWindow();
|
let win = model.getActiveWindow();
|
||||||
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
|
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
|
||||||
|
let curLine = inputModel.getCurLine();
|
||||||
|
let ltCurLine = this.lastTabCurLine.get();
|
||||||
|
if (e.code == "Tab") {
|
||||||
|
e.preventDefault();
|
||||||
|
let lastTab = (ltCurLine != null && curLine == ltCurLine);
|
||||||
|
if (lastTab) {
|
||||||
|
GlobalModel.submitCommand("compgen", null, [curLine], {"comppos": String(curLine.length), "compshow": "1"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.lastTabCurLine.set(curLine);
|
||||||
|
GlobalModel.submitCommand("compgen", null, [curLine], {"comppos": String(curLine.length)});
|
||||||
|
GlobalModel.clearInfoMsg(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ltCurLine != null && curLine != ltCurLine) {
|
||||||
|
this.lastTabCurLine.set(null);
|
||||||
|
}
|
||||||
if (e.code == "Enter" && !ctrlMod) {
|
if (e.code == "Enter" && !ctrlMod) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setTimeout(() => this.doSubmitCmd(), 0);
|
setTimeout(() => this.doSubmitCmd(), 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.code == "Tab") {
|
if (e.code == "Escape") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setCurLine(this.getCurLine() + "[tab]");
|
GlobalModel.toggleInfoMsg();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.code == "KeyC" && e.getModifierState("Control")) {
|
||||||
|
e.preventDefault();
|
||||||
|
inputModel.clearCurLine();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.code == "ArrowUp") {
|
if (e.code == "ArrowUp") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let hidx = this.historyIndex.get();
|
inputModel.prevHistoryItem();
|
||||||
hidx += 1;
|
|
||||||
if (hidx > win.getNumHistoryItems()) {
|
|
||||||
hidx = win.getNumHistoryItems();
|
|
||||||
}
|
|
||||||
this.historyIndex.set(hidx);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.code == "ArrowDown") {
|
if (e.code == "ArrowDown") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let hidx = this.historyIndex.get();
|
inputModel.nextHistoryItem();
|
||||||
hidx -= 1;
|
|
||||||
if (hidx < 0) {
|
|
||||||
hidx = 0;
|
|
||||||
}
|
|
||||||
this.historyIndex.set(hidx);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e);
|
// console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e);
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
|
||||||
clearCurLine() {
|
|
||||||
mobx.action(() => {
|
|
||||||
this.historyIndex.set(0);
|
|
||||||
this.modHistory.clear();
|
|
||||||
this.modHistory[0] = "";
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
@boundMethod
|
|
||||||
getCurLine() : string {
|
|
||||||
let model = GlobalModel;
|
|
||||||
let hidx = this.historyIndex.get();
|
|
||||||
if (hidx < this.modHistory.length && this.modHistory[hidx] != null) {
|
|
||||||
return this.modHistory[hidx];
|
|
||||||
}
|
|
||||||
let win = model.getActiveWindow();
|
|
||||||
if (win == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
let hitem = win.getHistoryItem(-hidx);
|
|
||||||
if (hitem == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return hitem.cmdtext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@boundMethod
|
|
||||||
setCurLine(val : string) {
|
|
||||||
let hidx = this.historyIndex.get();
|
|
||||||
this.modHistory[hidx] = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
onChange(e : any) {
|
onChange(e : any) {
|
||||||
mobx.action(() => {
|
mobx.action(() => {
|
||||||
this.setCurLine(e.target.value);
|
GlobalModel.inputModel.setCurLine(e.target.value);
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
doSubmitCmd() {
|
doSubmitCmd() {
|
||||||
let model = GlobalModel;
|
let model = GlobalModel;
|
||||||
let commandStr = this.getCurLine();
|
let inputModel = model.inputModel;
|
||||||
|
let commandStr = inputModel.getCurLine();
|
||||||
let hitem = {cmdtext: commandStr};
|
let hitem = {cmdtext: commandStr};
|
||||||
this.clearCurLine();
|
inputModel.clearCurLine();
|
||||||
|
GlobalModel.clearInfoMsg(true);
|
||||||
model.submitRawCommand(commandStr);
|
model.submitRawCommand(commandStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let curLine = this.getCurLine();
|
let model = GlobalModel;
|
||||||
|
let inputModel = model.inputModel;
|
||||||
|
let curLine = inputModel.getCurLine();
|
||||||
|
let win = GlobalModel.getActiveWindow();
|
||||||
|
let ri : RemoteInstanceType = null;
|
||||||
|
if (win != null) {
|
||||||
|
ri = win.getCurRemoteInstance();
|
||||||
|
}
|
||||||
|
let remote : RemoteType = null;
|
||||||
|
let remoteState : RemoteStateType = null;
|
||||||
|
if (ri != null) {
|
||||||
|
remote = GlobalModel.getRemote(ri.remoteid);
|
||||||
|
remoteState = ri.state;
|
||||||
|
}
|
||||||
|
let promptStr = getRemoteStr(remote);
|
||||||
|
let cwdStr = getCwdStr(remote, remoteState);
|
||||||
|
let infoMsg = GlobalModel.infoMsg.get();
|
||||||
|
let infoShow = GlobalModel.infoShow.get();
|
||||||
|
let istr : string = null;
|
||||||
|
let istrIdx : number = 0;
|
||||||
return (
|
return (
|
||||||
<div className="box cmd-input has-background-black">
|
<div className={cn("box cmd-input has-background-black", {"has-info": infoShow})}>
|
||||||
|
<div className="cmd-input-info" style={{display: (infoShow ? "block" : "none")}}>
|
||||||
|
<If condition={infoMsg && infoMsg.infotitle != null}>
|
||||||
|
<div className="info-title">
|
||||||
|
{infoMsg.infotitle}
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<If condition={infoMsg && infoMsg.infomsg != null}>
|
||||||
|
<div className="info-msg">
|
||||||
|
{infoMsg.infomsg}
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<If condition={infoMsg && infoMsg.infostrings != null && infoMsg.infostrings.length > 0}>
|
||||||
|
<div className="info-strings">
|
||||||
|
<For each="istr" index="istrIdx" of={infoMsg.infostrings}>
|
||||||
|
<div key={istrIdx} className="info-string">
|
||||||
|
{istr}
|
||||||
|
</div>
|
||||||
|
</For>
|
||||||
|
<If condition={infoMsg.infostringsmore}>
|
||||||
|
<div key="more" className="info-string">
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<If condition={infoMsg && infoMsg.infoerror != null}>
|
||||||
|
<div className="info-error">
|
||||||
|
{infoMsg.infoerror}
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
<div className="cmd-input-context">
|
<div className="cmd-input-context">
|
||||||
<div className="has-text-white">
|
<div className="has-text-white">
|
||||||
<span className="bold term-bright-green">[mike@local ~]</span>
|
<span className="bold term-bright-green">[{promptStr} {cwdStr}]</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="cmd-input-field field has-addons">
|
<div className="cmd-input-field field has-addons">
|
||||||
<div className="control cmd-quick-context">
|
<div className="control cmd-quick-context">
|
||||||
<div className="button is-static">mike@local</div>
|
<div className="button is-static">{promptStr}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="control cmd-input-control is-expanded">
|
<div className="control cmd-input-control is-expanded">
|
||||||
<textarea id="main-cmd-input" value={curLine} onKeyDown={this.onKeyDown} onChange={this.onChange} className="input"></textarea>
|
<textarea id="main-cmd-input" value={curLine} onKeyDown={this.onKeyDown} onChange={this.onChange} className="input"></textarea>
|
||||||
|
178
src/model.ts
178
src/model.ts
@ -1,7 +1,7 @@
|
|||||||
import * as mobx from "mobx";
|
import * as mobx from "mobx";
|
||||||
import {sprintf} from "sprintf-js";
|
import {sprintf} from "sprintf-js";
|
||||||
import {boundMethod} from "autobind-decorator";
|
import {boundMethod} from "autobind-decorator";
|
||||||
import {handleJsonFetchResponse, base64ToArray, genMergeData} from "./util";
|
import {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeSimpleData} from "./util";
|
||||||
import {TermWrap} from "./term";
|
import {TermWrap} from "./term";
|
||||||
import {v4 as uuidv4} from "uuid";
|
import {v4 as uuidv4} from "uuid";
|
||||||
import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, SessionUpdateType, WindowUpdateType, UpdateMessage, LineCmdUpdateType} from "./types";
|
import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, SessionUpdateType, WindowUpdateType, UpdateMessage, LineCmdUpdateType} from "./types";
|
||||||
@ -296,12 +296,13 @@ class Window {
|
|||||||
if (load) {
|
if (load) {
|
||||||
this.loaded.set(true);
|
this.loaded.set(true);
|
||||||
}
|
}
|
||||||
this.lines.replace(win.lines || []);
|
genMergeSimpleData(this.lines, win.lines, (l) => String(l.lineid), (l) => l.lineid);
|
||||||
this.history = win.history || [];
|
this.history = win.history || [];
|
||||||
let cmds = win.cmds || [];
|
let cmds = win.cmds || [];
|
||||||
for (let i=0; i<cmds.length; i++) {
|
for (let i=0; i<cmds.length; i++) {
|
||||||
this.cmds[cmds[i].cmdid] = new Cmd(cmds[i]);
|
this.cmds[cmds[i].cmdid] = new Cmd(cmds[i]);
|
||||||
}
|
}
|
||||||
|
genMergeSimpleData(this.remoteInstances, win.remotes, (r) => r.riid, null);
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,6 +322,9 @@ class Window {
|
|||||||
|
|
||||||
getCurRemoteInstance() : RemoteInstanceType {
|
getCurRemoteInstance() : RemoteInstanceType {
|
||||||
let rname = this.curRemote.get();
|
let rname = this.curRemote.get();
|
||||||
|
if (rname == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
let sessionScope = false;
|
let sessionScope = false;
|
||||||
if (rname.startsWith("^")) {
|
if (rname.startsWith("^")) {
|
||||||
rname = rname.substr(1);
|
rname = rname.substr(1);
|
||||||
@ -385,7 +389,7 @@ class Session {
|
|||||||
sessionIdx : OV<number>;
|
sessionIdx : OV<number>;
|
||||||
screens : OArr<Screen>;
|
screens : OArr<Screen>;
|
||||||
notifyNum : OV<number> = mobx.observable.box(0);
|
notifyNum : OV<number> = mobx.observable.box(0);
|
||||||
remoteInstances : OArr<RemoteInstanceType> = mobx.observable.array([]);
|
remoteInstances : OArr<RemoteInstanceType>;
|
||||||
|
|
||||||
constructor(sdata : SessionDataType) {
|
constructor(sdata : SessionDataType) {
|
||||||
this.sessionId = sdata.sessionid;
|
this.sessionId = sdata.sessionid;
|
||||||
@ -399,6 +403,8 @@ class Session {
|
|||||||
}
|
}
|
||||||
this.screens = mobx.observable.array(screens, {deep: false});
|
this.screens = mobx.observable.array(screens, {deep: false});
|
||||||
this.activeScreenId = mobx.observable.box(ces(sdata.activescreenid));
|
this.activeScreenId = mobx.observable.box(ces(sdata.activescreenid));
|
||||||
|
let remotes = sdata.remotes || [];
|
||||||
|
this.remoteInstances = mobx.observable.array(remotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() : void {
|
dispose() : void {
|
||||||
@ -492,6 +498,91 @@ class Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InfoType = {
|
||||||
|
infotitle : string;
|
||||||
|
infomsg : string;
|
||||||
|
infoerror : string;
|
||||||
|
infostrings : string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type CmdLineUpdateType = {
|
||||||
|
insertchars : string,
|
||||||
|
insertpos : number,
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputModel {
|
||||||
|
historyIndex : mobx.IObservableValue<number> = mobx.observable.box(0, {name: "history-index"});
|
||||||
|
modHistory : mobx.IObservableArray<string> = mobx.observable.array([""], {name: "mod-history"});
|
||||||
|
|
||||||
|
updateCmdLine(cmdLine : CmdLineUpdateType) {
|
||||||
|
mobx.action(() => {
|
||||||
|
let curLine = this.getCurLine();
|
||||||
|
if (curLine.length < cmdLine.insertpos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pos = cmdLine.insertpos;
|
||||||
|
curLine = curLine.substr(0, pos) + cmdLine.insertchars + curLine.substr(pos);
|
||||||
|
this.setCurLine(curLine);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurLine(val : string) {
|
||||||
|
let hidx = this.historyIndex.get();
|
||||||
|
this.modHistory[hidx] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCurLine() {
|
||||||
|
mobx.action(() => {
|
||||||
|
this.historyIndex.set(0);
|
||||||
|
this.modHistory.clear();
|
||||||
|
this.modHistory[0] = "";
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurLine() : string {
|
||||||
|
let model = GlobalModel;
|
||||||
|
let hidx = this.historyIndex.get();
|
||||||
|
if (hidx < this.modHistory.length && this.modHistory[hidx] != null) {
|
||||||
|
return this.modHistory[hidx];
|
||||||
|
}
|
||||||
|
let win = model.getActiveWindow();
|
||||||
|
if (win == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
let hitem = win.getHistoryItem(-hidx);
|
||||||
|
if (hitem == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return hitem.cmdtext;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevHistoryItem() : void {
|
||||||
|
let model = GlobalModel;
|
||||||
|
let win = model.getActiveWindow();
|
||||||
|
let hidx = this.historyIndex.get();
|
||||||
|
hidx += 1;
|
||||||
|
if (hidx > win.getNumHistoryItems()) {
|
||||||
|
hidx = win.getNumHistoryItems();
|
||||||
|
}
|
||||||
|
mobx.action(() => {
|
||||||
|
this.historyIndex.set(hidx);
|
||||||
|
})();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextHistoryItem() : void {
|
||||||
|
let hidx = this.historyIndex.get();
|
||||||
|
hidx -= 1;
|
||||||
|
if (hidx < 0) {
|
||||||
|
hidx = 0;
|
||||||
|
}
|
||||||
|
mobx.action(() => {
|
||||||
|
this.historyIndex.set(hidx);
|
||||||
|
})();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class Model {
|
class Model {
|
||||||
clientId : string;
|
clientId : string;
|
||||||
activeSessionId : OV<string> = mobx.observable.box(null);
|
activeSessionId : OV<string> = mobx.observable.box(null);
|
||||||
@ -501,6 +592,10 @@ class Model {
|
|||||||
remotes : OArr<RemoteType> = mobx.observable.array([], {deep: false});
|
remotes : OArr<RemoteType> = mobx.observable.array([], {deep: false});
|
||||||
remotesLoaded : OV<boolean> = mobx.observable.box(false);
|
remotesLoaded : OV<boolean> = mobx.observable.box(false);
|
||||||
windows : OMap<string, Window> = mobx.observable.map({}, {deep: false});
|
windows : OMap<string, Window> = mobx.observable.map({}, {deep: false});
|
||||||
|
infoShow : OV<boolean> = mobx.observable.box(false);
|
||||||
|
infoMsg : OV<InfoType> = mobx.observable.box(null);
|
||||||
|
infoTimeoutId : any = null;
|
||||||
|
inputModel : InputModel;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.clientId = getApi().getId();
|
this.clientId = getApi().getId();
|
||||||
@ -508,6 +603,7 @@ class Model {
|
|||||||
this.loadSessionList();
|
this.loadSessionList();
|
||||||
this.ws = new WSControl(this.clientId, (message : any) => this.runUpdate(message, false));
|
this.ws = new WSControl(this.clientId, (message : any) => this.runUpdate(message, false));
|
||||||
this.ws.reconnect();
|
this.ws.reconnect();
|
||||||
|
this.inputModel = new InputModel();
|
||||||
getApi().onTCmd(this.onTCmd.bind(this));
|
getApi().onTCmd(this.onTCmd.bind(this));
|
||||||
getApi().onICmd(this.onICmd.bind(this));
|
getApi().onICmd(this.onICmd.bind(this));
|
||||||
getApi().onBracketCmd(this.onBracketCmd.bind(this));
|
getApi().onBracketCmd(this.onBracketCmd.bind(this));
|
||||||
@ -519,6 +615,47 @@ class Model {
|
|||||||
getApi().contextScreen({screenId: screenId}, {x: e.x, y: e.y});
|
getApi().contextScreen({screenId: screenId}, {x: e.x, y: e.y});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flashInfoMsg(info : InfoType, timeoutMs : number) {
|
||||||
|
if (this.infoTimeoutId != null) {
|
||||||
|
clearTimeout(this.infoTimeoutId);
|
||||||
|
this.infoTimeoutId = null;
|
||||||
|
}
|
||||||
|
mobx.action(() => {
|
||||||
|
this.infoMsg.set(info);
|
||||||
|
this.infoShow.set(info != null);
|
||||||
|
})();
|
||||||
|
if (info != null && timeoutMs) {
|
||||||
|
this.infoTimeoutId = setTimeout(() => {
|
||||||
|
this.clearInfoMsg(false);
|
||||||
|
}, timeoutMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInfoMsg(setNull : boolean) {
|
||||||
|
this.infoTimeoutId = null;
|
||||||
|
mobx.action(() => {
|
||||||
|
this.infoShow.set(false);
|
||||||
|
if (setNull) {
|
||||||
|
this.infoMsg.set(null);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleInfoMsg() {
|
||||||
|
this.infoTimeoutId = null;
|
||||||
|
mobx.action(() => {
|
||||||
|
let isShowing = this.infoShow.get();
|
||||||
|
if (isShowing) {
|
||||||
|
this.infoShow.set(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.infoMsg.get() != null) {
|
||||||
|
this.infoShow.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
onTCmd(mods : KeyModsType) {
|
onTCmd(mods : KeyModsType) {
|
||||||
console.log("got cmd-t", mods);
|
console.log("got cmd-t", mods);
|
||||||
GlobalInput.createNewScreen();
|
GlobalInput.createNewScreen();
|
||||||
@ -561,7 +698,6 @@ class Model {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
activeScreen.updatePtyData(ptyMsg);
|
activeScreen.updatePtyData(ptyMsg);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if ("sessions" in update) {
|
if ("sessions" in update) {
|
||||||
let sessionUpdateMsg : SessionUpdateType = update;
|
let sessionUpdateMsg : SessionUpdateType = update;
|
||||||
@ -588,11 +724,18 @@ class Model {
|
|||||||
let lineMsg : LineCmdUpdateType = update;
|
let lineMsg : LineCmdUpdateType = update;
|
||||||
this.addLineCmd(lineMsg.line, lineMsg.cmd, interactive);
|
this.addLineCmd(lineMsg.line, lineMsg.cmd, interactive);
|
||||||
}
|
}
|
||||||
console.log("run-update>", interactive, update);
|
if ("window" in update) {
|
||||||
|
let winMsg : WindowUpdateType = update;
|
||||||
|
this.updateWindow(winMsg.window, false);
|
||||||
}
|
}
|
||||||
|
if ("info" in update) {
|
||||||
removeSession(sessionId : string) {
|
let info = update.info;
|
||||||
console.log("removeSession not implemented");
|
this.flashInfoMsg(info, info.timeoutms);
|
||||||
|
}
|
||||||
|
if ("cmdline" in update) {
|
||||||
|
this.inputModel.updateCmdLine(update.cmdline);
|
||||||
|
}
|
||||||
|
console.log("run-update>", interactive, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveSession() : Session {
|
getActiveSession() : Session {
|
||||||
@ -631,7 +774,7 @@ class Model {
|
|||||||
let existingWin = this.windows.get(winKey);
|
let existingWin = this.windows.get(winKey);
|
||||||
if (existingWin == null) {
|
if (existingWin == null) {
|
||||||
if (!load) {
|
if (!load) {
|
||||||
console.log("cannot update window that does not exist");
|
console.log("cannot update window that does not exist", winKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let newWindow = new Window(win.sessionid, win.windowid);
|
let newWindow = new Window(win.sessionid, win.windowid);
|
||||||
@ -707,7 +850,7 @@ class Model {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.errorHandler("calling run-command", err);
|
this.errorHandler("calling run-command", err, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -756,7 +899,7 @@ class Model {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.errorHandler("getting session list", err);
|
this.errorHandler("getting session list", err, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -805,7 +948,7 @@ class Model {
|
|||||||
this.updateWindow(data.data, true);
|
this.updateWindow(data.data, true);
|
||||||
return;
|
return;
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.errorHandler(sprintf("getting window=%s", windowId), err);
|
this.errorHandler(sprintf("getting window=%s", windowId), err, false);
|
||||||
});
|
});
|
||||||
return newWin;
|
return newWin;
|
||||||
}
|
}
|
||||||
@ -818,7 +961,7 @@ class Model {
|
|||||||
this.remotesLoaded.set(true);
|
this.remotesLoaded.set(true);
|
||||||
})();
|
})();
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.errorHandler("calling get-remotes", err)
|
this.errorHandler("calling get-remotes", err, false)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -852,8 +995,15 @@ class Model {
|
|||||||
return window.getCmd(line.cmdid);
|
return window.getCmd(line.cmdid);
|
||||||
}
|
}
|
||||||
|
|
||||||
errorHandler(str : string, err : any) {
|
errorHandler(str : string, err : any, interactive : boolean) {
|
||||||
console.log("[error]", str, err);
|
console.log("[error]", str, err);
|
||||||
|
if (interactive) {
|
||||||
|
let errMsg = "error running command";
|
||||||
|
if (err != null && err.message) {
|
||||||
|
errMsg = err.message;
|
||||||
|
}
|
||||||
|
this.flashInfoMsg({infoerror: errMsg}, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendInputPacket(inputPacket : any) {
|
sendInputPacket(inputPacket : any) {
|
||||||
|
42
src/sh2.less
42
src/sh2.less
@ -464,6 +464,10 @@ body .xterm .xterm-viewport {
|
|||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
border-bottom-right-radius: 10px;
|
border-bottom-right-radius: 10px;
|
||||||
|
|
||||||
|
&.has-info {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.cmd-input-context {
|
.cmd-input-context {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
@ -491,6 +495,44 @@ body .xterm .xterm-viewport {
|
|||||||
color: #d3d7cf;
|
color: #d3d7cf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cmd-input-info {
|
||||||
|
.info-msg {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #729fcf;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-title {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #729fcf;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-strings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
|
||||||
|
.info-string {
|
||||||
|
min-width: 200px;
|
||||||
|
color: #d3d7cf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-error {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #cc0000;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bold {
|
.bold {
|
||||||
|
@ -87,6 +87,8 @@ type RemoteInstanceType = {
|
|||||||
remoteid : string,
|
remoteid : string,
|
||||||
sessionscope : boolean,
|
sessionscope : boolean,
|
||||||
state : RemoteStateType,
|
state : RemoteStateType,
|
||||||
|
|
||||||
|
remove? : boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindowDataType = {
|
type WindowDataType = {
|
||||||
@ -185,7 +187,6 @@ type UpdateMessage = PtyDataUpdateType | SessionUpdateType | LineCmdUpdateType;
|
|||||||
|
|
||||||
type WindowUpdateType = {
|
type WindowUpdateType = {
|
||||||
window: WindowDataType,
|
window: WindowDataType,
|
||||||
remove: boolean,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDonePacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, SessionUpdateType, WindowUpdateType, UpdateMessage, LineCmdUpdateType};
|
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDonePacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, SessionUpdateType, WindowUpdateType, UpdateMessage, LineCmdUpdateType};
|
||||||
|
36
src/util.ts
36
src/util.ts
@ -58,6 +58,40 @@ interface IObjType<DataType> {
|
|||||||
mergeData : (data : DataType) => void,
|
mergeData : (data : DataType) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ISimpleDataType {
|
||||||
|
remove? : boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function genMergeSimpleData<T extends ISimpleDataType>(objs : mobx.IObservableArray<T>, dataArr : T, idFn : (obj : T) => string, sortIdxFn : (obj : T) => number) {
|
||||||
|
if (dataArr == null || dataArr.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let objMap : Record<string, T> = {};
|
||||||
|
for (let i=0; i<objs.length; i++) {
|
||||||
|
let obj = objs[i];
|
||||||
|
let id = idFn(obj);
|
||||||
|
objMap[id] = obj;
|
||||||
|
}
|
||||||
|
for (let i=0; i<dataArr.length; i++) {
|
||||||
|
let dataItem = dataArr[i];
|
||||||
|
let id = idFn(dataItem);
|
||||||
|
if (dataItem.remove) {
|
||||||
|
delete objMap[id];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
objMap[id] = dataItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let newObjs = Object.values(objMap);
|
||||||
|
if (sortIdxFn) {
|
||||||
|
newObjs.sort((a, b) => {
|
||||||
|
return sortIdxFn(a) - sortIdxFn(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
objs.replace(newObjs);
|
||||||
|
}
|
||||||
|
|
||||||
function genMergeData<ObjType extends IObjType<DataType>, DataType extends IDataType>(
|
function genMergeData<ObjType extends IObjType<DataType>, DataType extends IDataType>(
|
||||||
objs : mobx.IObservableArray<ObjType>,
|
objs : mobx.IObservableArray<ObjType>,
|
||||||
dataArr : DataType[],
|
dataArr : DataType[],
|
||||||
@ -106,4 +140,4 @@ function genMergeData<ObjType extends IObjType<DataType>, DataType extends IData
|
|||||||
objs.replace(newObjs);
|
objs.replace(newObjs);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {handleJsonFetchResponse, base64ToArray, genMergeData};
|
export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeSimpleData};
|
||||||
|
Loading…
Reference in New Issue
Block a user