import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; import {debounce, throttle} from "throttle-debounce"; import {v4 as uuidv4} from "uuid"; import dayjs from "dayjs"; import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import cn from "classnames"; import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType, ContextMenuOpts, BookmarkType, RenderModeType} from "./types"; import type * as T from "./types"; import localizedFormat from 'dayjs/plugin/localizedFormat'; import {GlobalModel, GlobalCommandRunner, Session, Cmd, Window, Screen, ScreenWindow, riToRPtr, windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./model"; import {isModKeyPress} from "./util"; import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import {BookmarksView} from "./bookmarks"; import {HistoryView} from "./history"; import {Line, Prompt} from "./linecomps"; dayjs.extend(localizedFormat) const RemotePtyRows = 8; const RemotePtyCols = 80; const PasswordUnchangedSentinel = "--unchanged--"; const LinesVisiblePadding = 500; const RemoteColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"]; type OV = mobx.IObservableValue; type OArr = mobx.IObservableArray; type OMap = mobx.ObservableMap; type VisType = "visible" | ""; type InterObsValue = { sessionid : string, windowid : string, lineid : string, cmdid : string, visible : mobx.IObservableValue, timeoutid? : any, }; function isBlank(s : string) : boolean { return (s == null || s == ""); } function getTodayStr() : string { return getDateStr(new Date()); } function getYesterdayStr() : string { let d = new Date(); d.setDate(d.getDate()-1); return getDateStr(d); } function scrollDiv(div : any, amt : number) { if (div == null) { return; } let newScrollTop = div.scrollTop + amt; if (newScrollTop < 0) { newScrollTop = 0; } div.scrollTo({top: newScrollTop, behavior: "smooth"}); } function pageSize(div : any) : number { if (div == null) { return 300; } let size = div.clientHeight; if (size > 500) { size = size - 100; } else if (size > 200) { size = size - 30; } return size; } @mobxReact.observer class TextAreaInput extends React.Component<{onHeightChange : () => void}, {}> { lastTab : boolean = false; lastHistoryUpDown : boolean = false; lastTabCurLine : mobx.IObservableValue = mobx.observable.box(null); lastFocusType : string = null; mainInputRef : React.RefObject; historyInputRef : React.RefObject; controlRef : React.RefObject; lastHeight : number = 0; constructor(props) { super(props); this.mainInputRef = React.createRef(); this.historyInputRef = React.createRef(); this.controlRef = React.createRef(); } setFocus() : void { let inputModel = GlobalModel.inputModel; if (inputModel.historyShow.get()) { this.historyInputRef.current.focus(); } else { this.mainInputRef.current.focus(); } } checkHeight(shouldFire : boolean) : void { let elem = this.controlRef.current; if (elem == null) { return; } let curHeight = elem.offsetHeight; if (this.lastHeight == curHeight) { return; } this.lastHeight = curHeight; if (shouldFire && this.props.onHeightChange != null) { this.props.onHeightChange(); } } componentDidMount() { let activeSW = GlobalModel.getActiveSW(); if (activeSW != null) { let focusType = activeSW.focusType.get(); if (focusType == "input") { this.setFocus(); } this.lastFocusType = focusType; } this.checkHeight(false); } componentDidUpdate() { let activeSW = GlobalModel.getActiveSW(); if (activeSW != null) { let focusType = activeSW.focusType.get(); if (this.lastFocusType != focusType && focusType == "input") { this.setFocus(); } this.lastFocusType = focusType; } let inputModel = GlobalModel.inputModel; if (inputModel.forceCursorPos.get() != null) { if (this.mainInputRef.current != null) { this.mainInputRef.current.selectionStart = inputModel.forceCursorPos.get(); this.mainInputRef.current.selectionEnd = inputModel.forceCursorPos.get(); } mobx.action(() => inputModel.forceCursorPos.set(null))(); } this.checkHeight(true); } 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; return {numLines, linePos}; } @mobx.action @boundMethod onKeyDown(e : any) { mobx.action(() => { if (isModKeyPress(e)) { return; } let model = GlobalModel; let inputModel = model.inputModel; let win = model.getActiveWindow(); let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift"); let curLine = inputModel.getCurLine(); let lastTab = this.lastTab; this.lastTab = (e.code == "Tab"); let lastHist = this.lastHistoryUpDown; this.lastHistoryUpDown = false; if (e.code == "Tab") { e.preventDefault(); if (lastTab) { GlobalModel.submitCommand("_compgen", null, [curLine], {"comppos": String(curLine.length), "compshow": "1", "nohist": "1"}, true); return; } else { GlobalModel.submitCommand("_compgen", null, [curLine], {"comppos": String(curLine.length), "nohist": "1"}, true); return; } } if (e.code == "Enter") { e.preventDefault(); if (!ctrlMod) { if (GlobalModel.inputModel.isEmpty()) { let activeWindow = GlobalModel.getActiveWindow(); let activeSW = GlobalModel.getActiveSW(); if (activeSW != null && activeWindow != null && activeWindow.lines.length > 0) { activeSW.setSelectedLine(0); GlobalCommandRunner.swSelectLine("E"); } return; } else { setTimeout(() => GlobalModel.inputModel.uiSubmitCommand(), 0); return; } } e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end"); GlobalModel.inputModel.setCurLine(e.target.value); return; } if (e.code == "Escape") { e.preventDefault(); e.stopPropagation(); let inputModel = GlobalModel.inputModel; inputModel.toggleInfoMsg(); if (inputModel.inputMode.get() != null) { inputModel.resetInputMode(); } return; } if (e.code == "KeyC" && e.getModifierState("Control")) { e.preventDefault(); inputModel.resetInput(); return; } if (e.code == "KeyR" && e.getModifierState("Control")) { e.preventDefault(); inputModel.openHistory(); return; } if (e.code == "ArrowUp" && e.getModifierState("Shift")) { e.preventDefault(); inputModel.openHistory(); return; } if (e.code == "ArrowUp" || e.code == "ArrowDown") { if (!inputModel.isHistoryLoaded()) { if (e.code == "ArrowUp") { this.lastHistoryUpDown = true; inputModel.loadHistory(false, 1, "window"); } return; } // invisible history movement let linePos = this.getLinePos(e.target); if (e.code == "ArrowUp") { if (!lastHist && linePos.linePos > 1) { // regular arrow return; } e.preventDefault(); inputModel.moveHistorySelection(1); this.lastHistoryUpDown = true; return; } if (e.code == "ArrowDown") { if (!lastHist && linePos.linePos < linePos.numLines) { // regular arrow return; } e.preventDefault(); inputModel.moveHistorySelection(-1); this.lastHistoryUpDown = true; return; } } if (e.code == "PageUp" || e.code == "PageDown") { e.preventDefault(); let infoScroll = inputModel.hasScrollingInfoMsg(); if (infoScroll) { let div = document.querySelector(".cmd-input-info"); let amt = pageSize(div); scrollDiv(div, (e.code == "PageUp" ? -amt : amt)); } } // console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e); })(); } @boundMethod onChange(e : any) { mobx.action(() => { GlobalModel.inputModel.setCurLine(e.target.value); })(); } @boundMethod onHistoryKeyDown(e : any) { let inputModel = GlobalModel.inputModel; if (e.code == "Escape") { e.preventDefault(); inputModel.resetHistory(); return; } if (e.code == "Enter") { e.preventDefault(); inputModel.grabSelectedHistoryItem(); return; } if (e.code == "KeyG" && e.getModifierState("Control")) { e.preventDefault(); inputModel.resetInput(); return; } if (e.code == "KeyC" && e.getModifierState("Control")) { e.preventDefault(); inputModel.resetInput(); return; } if (e.code == "KeyM" && (e.getModifierState("Meta") || e.getModifierState("Control"))) { e.preventDefault(); let opts = mobx.toJS(inputModel.historyQueryOpts.get()); opts.includeMeta = !opts.includeMeta; inputModel.setHistoryQueryOpts(opts); return; } if (e.code == "KeyR" && ((e.getModifierState("Meta") || e.getModifierState("Control")) && !e.getModifierState("Shift"))) { 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 == "KeyS" && (e.getModifierState("Meta") || e.getModifierState("Control"))) { e.preventDefault(); let opts = mobx.toJS(inputModel.historyQueryOpts.get()); let htype = opts.queryType; if (htype == "window") { htype = "session"; } else if (htype == "session") { htype = "global"; } else { htype = "window"; } inputModel.setHistoryType(htype); return; } if (e.code == "Tab") { e.preventDefault(); return; } if (e.code == "ArrowUp" || e.code == "ArrowDown") { e.preventDefault(); inputModel.moveHistorySelection(e.code == "ArrowUp" ? 1 : -1); return; } if (e.code == "PageUp" || e.code == "PageDown") { e.preventDefault(); inputModel.moveHistorySelection(e.code == "PageUp" ? 10 : -10); return; } } @boundMethod handleHistoryInput(e : any) { let inputModel = GlobalModel.inputModel; mobx.action(() => { let opts = mobx.toJS(inputModel.historyQueryOpts.get()); opts.queryStr = e.target.value; inputModel.setHistoryQueryOpts(opts); })(); } @boundMethod handleMainFocus(e : any) { let inputModel = GlobalModel.inputModel; if (inputModel.historyShow.get()) { e.preventDefault(); if (this.historyInputRef.current != null) { this.historyInputRef.current.focus(); } return; } inputModel.setPhysicalInputFocused(true); } @boundMethod handleMainBlur(e : any) { if (document.activeElement == this.mainInputRef.current) { return; } GlobalModel.inputModel.setPhysicalInputFocused(false); } @boundMethod handleHistoryFocus(e : any) { let inputModel = GlobalModel.inputModel; if (!inputModel.historyShow.get()) { e.preventDefault(); if (this.mainInputRef.current != null) { this.mainInputRef.current.focus(); } return; } inputModel.setPhysicalInputFocused(true); } @boundMethod handleHistoryBlur(e : any) { if (document.activeElement == this.historyInputRef.current) { return; } GlobalModel.inputModel.setPhysicalInputFocused(false); } render() { let model = GlobalModel; let inputModel = model.inputModel; let curLine = inputModel.getCurLine(); let fcp = inputModel.forceCursorPos.get(); // for reaction let numLines = curLine.split("\n").length; let displayLines = numLines; if (displayLines > 5) { displayLines = 5; } let disabled = inputModel.historyShow.get(); if (disabled) { displayLines = 1; } let activeSW = GlobalModel.getActiveSW(); if (activeSW != null) { activeSW.focusType.get(); // for reaction } return (
); } } @mobxReact.observer class InfoRemoteShowAll extends React.Component<{}, {}> { clickRow(remoteId : string) : void { GlobalCommandRunner.showRemote(remoteId); } render() { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); if (infoMsg == null || !infoMsg.remoteshowall) { return null; } let remotes = GlobalModel.remotes ?? []; let remote : RemoteType = null; let idx : number = 0; remotes = sortAndFilterRemotes(remotes); return (
show all remotes
this.clickRow(remote.remoteid)}>
status id alias user@host connectmode
{remote.status}
{remote.remoteid.substr(0, 8)} {isBlank(remote.remotealias) ? "-" : remote.remotealias} {remote.remotecanonicalname} {remote.connectmode}
); } } @mobxReact.observer class InfoRemoteShow extends React.Component<{}, {}> { getRemoteTypeStr(remote : RemoteType) : string { let mshellStr = ""; if (!isBlank(remote.mshellversion)) { mshellStr = "mshell=" + remote.mshellversion; } if (!isBlank(remote.uname)) { if (mshellStr != "") { mshellStr += " "; } mshellStr += "uname=\"" + remote.uname + "\""; } if (mshellStr == "") { return remote.remotetype; } return remote.remotetype + " (" + mshellStr + ")"; } @boundMethod connectRemote(remoteId : string) { GlobalCommandRunner.connectRemote(remoteId); } @boundMethod disconnectRemote(remoteId : string) { GlobalCommandRunner.disconnectRemote(remoteId); } @boundMethod installRemote(remoteId : string) { GlobalCommandRunner.installRemote(remoteId); } @boundMethod cancelInstall(remoteId : string) { GlobalCommandRunner.installCancelRemote(remoteId); } @boundMethod editRemote(remoteId : string) { GlobalCommandRunner.openEditRemote(remoteId); } renderConnectButton(remote : RemoteType) : any { if (remote.status == "connected" || remote.status == "connecting") { return
this.disconnectRemote(remote.remoteid)} className="text-button disconnect-button">[disconnect remote]
} else { return
this.connectRemote(remote.remoteid)} className="text-button connect-button">[connect remote]
} } renderEditButton(remote : RemoteType) : any { return
this.editRemote(remote.remoteid)} className="text-button">[edit remote]
} renderInstallButton(remote : RemoteType) : any { if (remote.status == "connected" || remote.status == "connecting") { return null; } if (remote.installstatus == "disconnected" || remote.installstatus == "error") { return
this.installRemote(remote.remoteid)} className="text-button connect-button">[run install]
} if (remote.installstatus == "connecting") { return
this.cancelInstall(remote.remoteid)} className="text-button disconnect-button">[cancel install]
} return null; } renderInstallStatus(remote : RemoteType) : any { let statusStr : string = null; if (remote.installstatus == "disconnected") { if (remote.needsmshellupgrade) { statusStr = "mshell " + remote.mshellversion + " (needs upgrade)"; } else if (isBlank(remote.mshellversion)) { statusStr = "mshell unknown"; } else { statusStr = "mshell " + remote.mshellversion + " (current)"; } } else { statusStr = remote.installstatus; } if (statusStr == null) { return null; } let installButton = this.renderInstallButton(remote); return (
install-status
{statusStr} | {this.renderInstallButton(remote)}
); } @boundMethod clickTermBlock(e : any) { let inputModel = GlobalModel.inputModel; if (inputModel.remoteTermWrap != null) { inputModel.remoteTermWrap.giveFocus(); } } getCanonicalNameDisplayWithPort(remote : RemoteType) { if (isBlank(remote.remotevars.port) || remote.remotevars.port == "22") { return remote.remotecanonicalname; } return remote.remotecanonicalname + " (port " + remote.remotevars.port + ")"; } render() { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); let ptyRemoteId = (infoMsg == null ? null : infoMsg.ptyremoteid); let isTermFocused = (inputModel.remoteTermWrap == null ? false : inputModel.remoteTermWrapFocus.get()); let remote : RemoteType; if (ptyRemoteId != null) { remote = GlobalModel.getRemote(ptyRemoteId); } if (ptyRemoteId == null || remote == null) { return ( <>
); } let termFontSize = GlobalModel.termFontSize.get(); return ( <>
show remote [{remote.remotecanonicalname}]
remoteid
{remote.remoteid} | {this.renderEditButton(remote)}
type
{this.getRemoteTypeStr(remote)}
canonicalname
{this.getCanonicalNameDisplayWithPort(remote)}
alias
{isBlank(remote.remotealias) ? "-" : remote.remotealias}
connectmode
{remote.connectmode}
status
{remote.status} | {this.renderConnectButton(remote)}
error
{remote.errorstr}
{this.renderInstallStatus(remote)}
install error
{remote.installerrorstr}
input is only allowed while status is 'connecting'
); } } @mobxReact.observer class InfoRemoteEdit extends React.Component<{}, {}> { alias : mobx.IObservableValue; hostName : mobx.IObservableValue; keyStr : mobx.IObservableValue; portStr : mobx.IObservableValue; passwordStr : mobx.IObservableValue; colorStr : mobx.IObservableValue; connectMode : mobx.IObservableValue; sudoBool : mobx.IObservableValue; autoInstallBool : mobx.IObservableValue; authMode : mobx.IObservableValue; archiveConfirm : mobx.IObservableValue = mobx.observable.box(false); constructor(props) { super(props); this.resetForm(); } getEditAuthMode(redit : RemoteEditType) : string { if (!isBlank(redit.keystr) && redit.haspassword) { return "key+pw"; } else if (!isBlank(redit.keystr)) { return "key"; } else if (redit.haspassword) { return "pw"; } else { return "none"; } } resetForm() { let redit = this.getRemoteEdit(); let remote = this.getEditingRemote(); if (redit == null) { return; } let isEditMode = !isBlank(redit.remoteid); if (isEditMode && remote == null) { return; } // not editable this.hostName = mobx.observable.box(""); this.portStr = mobx.observable.box(""); this.sudoBool = mobx.observable.box(false); // editable if (isEditMode) { this.authMode = mobx.observable.box(this.getEditAuthMode(redit)); this.alias = mobx.observable.box(remote.remotealias ?? ""); this.passwordStr = mobx.observable.box(redit.haspassword ? PasswordUnchangedSentinel : ""); this.keyStr = mobx.observable.box(redit.keystr ?? ""); this.colorStr = mobx.observable.box(remote.remotevars["color"] ?? ""); this.connectMode = mobx.observable.box(remote.connectmode); this.autoInstallBool = mobx.observable.box(remote.autoinstall); } else { this.authMode = mobx.observable.box("none"); this.alias = mobx.observable.box(""); this.passwordStr = mobx.observable.box(""); this.keyStr = mobx.observable.box(""); this.colorStr = mobx.observable.box(""); this.connectMode = mobx.observable.box("startup"); this.autoInstallBool = mobx.observable.box(true); } } canResetPw() : boolean { let redit = this.getRemoteEdit(); if (redit == null) { return false; } return redit.haspassword && this.passwordStr.get() != PasswordUnchangedSentinel; } @boundMethod resetPw() : void { mobx.action(() => { this.passwordStr.set(PasswordUnchangedSentinel); })(); } @boundMethod updateArchiveConfirm(e : any) : void { mobx.action(() => { this.archiveConfirm.set(e.target.checked); })(); } @boundMethod doArchiveRemote(e : any) { e.preventDefault(); if (!this.archiveConfirm.get()) { return; } let redit = this.getRemoteEdit(); if (redit == null || isBlank(redit.remoteid)) { return; } GlobalCommandRunner.archiveRemote(redit.remoteid); } @boundMethod doSubmitRemote() { let redit = this.getRemoteEdit(); let isEditing = !isBlank(redit.remoteid); let cname = this.hostName.get(); let kwargs : Record = {}; let authMode = this.authMode.get(); if (!isEditing) { if (this.sudoBool.get()) { kwargs["sudo"] = "1"; } } kwargs["alias"] = this.alias.get(); kwargs["color"] = this.colorStr.get(); if (authMode == "key" || authMode == "key+pw") { kwargs["key"] = this.keyStr.get(); } else { kwargs["key"] = ""; } if (authMode == "pw" || authMode == "key+pw") { kwargs["password"] = this.passwordStr.get(); } else { kwargs["password"] = "" } kwargs["connectmode"] = this.connectMode.get(); kwargs["autoinstall"] = (this.autoInstallBool.get() ? "1" : "0"); kwargs["visual"] = "1"; kwargs["submit"] = "1"; console.log("submit remote", (isEditing ? redit.remoteid : cname), kwargs); mobx.action(() => { if (isEditing) { GlobalCommandRunner.editRemote(redit.remoteid, kwargs); } else { GlobalCommandRunner.createRemote(cname, kwargs); } })(); } @boundMethod doCancel() { mobx.action(() => { this.resetForm(); GlobalModel.inputModel.clearInfoMsg(true); })(); } @boundMethod keyDownCreateRemote(e : any) { if (e.code == "Enter") { this.doSubmitRemote(); } } @boundMethod keyDownCancel(e : any) { if (e.code == "Enter") { this.doCancel(); } } @boundMethod onChangeAlias(e : any) { mobx.action(() => { this.alias.set(e.target.value); })(); } @boundMethod onChangeHostName(e : any) { mobx.action(() => { this.hostName.set(e.target.value); })(); } @boundMethod onChangeKeyStr(e : any) { mobx.action(() => { this.keyStr.set(e.target.value); })(); } @boundMethod onChangePortStr(e : any) { mobx.action(() => { this.portStr.set(e.target.value); })(); } @boundMethod onChangePasswordStr(e : any) { mobx.action(() => { this.passwordStr.set(e.target.value); })(); } @boundMethod onFocusPasswordStr(e : any) { if (this.passwordStr.get() == PasswordUnchangedSentinel) { e.target.select(); } } @boundMethod onChangeColorStr(e : any) { mobx.action(() => { this.colorStr.set(e.target.value); })(); } @boundMethod onChangeConnectMode(e : any) { mobx.action(() => { this.connectMode.set(e.target.value); })(); } @boundMethod onChangeAuthMode(e : any) { mobx.action(() => { this.authMode.set(e.target.value); })(); } @boundMethod onChangeSudo(e : any) { mobx.action(() => { this.sudoBool.set(e.target.checked); })(); } @boundMethod onChangeAutoInstall(e : any) { mobx.action(() => { this.autoInstallBool.set(e.target.checked); })(); } getRemoteEdit() : RemoteEditType { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); if (infoMsg == null) { return null; } return infoMsg.remoteedit; } getEditingRemote() : RemoteType { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); if (infoMsg == null) { return null; } let redit = infoMsg.remoteedit; if (redit == null || isBlank(redit.remoteid)) { return null; } let remote = GlobalModel.getRemote(redit.remoteid); return remote; } remoteCName() : string { let redit = this.getRemoteEdit(); if (isBlank(redit.remoteid)) { // new-mode let hostName = this.hostName.get(); if (hostName == "") { return "[no host]"; } if (hostName.indexOf("@") == -1) { hostName = "[no user]@" + hostName; } if (!hostName.startsWith("sudo@") && this.sudoBool.get()) { return "sudo@" + hostName; } return hostName; } else { let remote = this.getEditingRemote(); if (remote == null) { return "[no remote]"; } return remote.remotecanonicalname; } } render() { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); if (infoMsg == null || !infoMsg.remoteedit) { return null; } let redit = infoMsg.remoteedit; if (!redit.remoteedit) { return null; } let isEditMode = !isBlank(redit.remoteid); let remote = this.getEditingRemote(); if (isEditMode && remote == null) { return (
cannot edit, remote {redit.remoteid} not found
); } let colorStr : string = null; return (
add new remote '{this.remoteCName()}' edit remote '{this.remoteCName()}'
type
ssh
user@host
port
user@host
{remote.remotecanonicalname}  (port {remote.remotevars.port})
alias
authmode
ssh keyfile
ssh password
sudo
connectmode
autoinstall
color
{redit.errorstr}
{redit.infostr}
); } } @mobxReact.observer class InfoMsg extends React.Component<{}, {}> { getAfterSlash(s : string) : string { if (s.startsWith("^/")) { return s.substr(1); } let slashIdx = s.lastIndexOf("/"); if (slashIdx == s.length-1) { slashIdx = s.lastIndexOf("/", slashIdx-1); } if (slashIdx == -1) { return s; } return s.substr(slashIdx+1); } hasSpace(s : string) : boolean { return s.indexOf(" ") != -1; } handleCompClick(s : string) : void { // TODO -> complete to this completion } render() { let model = GlobalModel; let inputModel = model.inputModel; let infoMsg = inputModel.infoMsg.get(); let infoShow = inputModel.infoShow.get(); let line : string = null; let istr : string = null; let idx : number = 0; let titleStr = null; let remoteEditKey = "inforemoteedit"; if (infoMsg != null) { titleStr = infoMsg.infotitle; if (infoMsg.remoteedit != null) { remoteEditKey += (infoMsg.remoteedit.remoteid == null ? "-new" : "-" + infoMsg.remoteedit.remoteid); } } return (
{titleStr}
{infoMsg.infomsg}
{line == "" ? " " : line}
0}>
this.handleCompClick(istr)} key={idx} className={cn("info-comp", {"has-space": this.hasSpace(istr)}, {"metacmd-comp": istr.startsWith("^")})}> {this.getAfterSlash(istr)}
...
[error] {infoMsg.infoerror}
); } } @mobxReact.observer class HistoryInfo extends React.Component<{}, {}> { lastClickHNum : string = null; lastClickTs : number = 0; containingText : mobx.IObservableValue = mobx.observable.box(""); componentDidMount() { let inputModel = GlobalModel.inputModel; let hitem = inputModel.getHistorySelectedItem(); if (hitem == null) { hitem = inputModel.getFirstHistoryItem(); } if (hitem != null) { inputModel.scrollHistoryItemIntoView(hitem.historynum); } } @boundMethod handleItemClick(hitem : HistoryItem) { let inputModel = GlobalModel.inputModel; let selItem = inputModel.getHistorySelectedItem(); if (this.lastClickHNum == hitem.historynum && selItem != null && selItem.historynum == hitem.historynum) { inputModel.grabSelectedHistoryItem(); return; } inputModel.giveFocus(); inputModel.setHistorySelectionNum(hitem.historynum); let now = Date.now(); this.lastClickHNum = hitem.historynum; this.lastClickTs = now; setTimeout(() => { if (this.lastClickTs == now) { this.lastClickHNum = null; this.lastClickTs = 0; } }, 3000); } 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; let sessionStr = ""; if (opts.queryType == "global") { if (!isBlank(hitem.sessionid)) { let s = GlobalModel.getSessionById(hitem.sessionid); if (s != null) { sessionStr = s.name.get(); if (sessionStr.indexOf(" ") != -1) { sessionStr = "[" + sessionStr + "]"; } sessionStr = sprintf("#%-15s ", sessionStr); } } } return (
this.handleItemClick(hitem)}>
{(isSelected ? "*" : " ")}{sprintf("%5s", hitem.historynum)} {opts.queryType == "global" ? sessionStr : ""}{!limitRemote ? this.renderRemote(hitem) : ""} {lines[0]}
{line}
); } @boundMethod handleClose() { GlobalModel.inputModel.toggleInfoMsg(); } render() { let inputModel = GlobalModel.inputModel; let idx : number = 0; let selItem = inputModel.getHistorySelectedItem(); let hitems = inputModel.getFilteredHistoryItems(); hitems = hitems.slice().reverse(); let hitem : HistoryItem = null; let opts = inputModel.historyQueryOpts.get(); return (
history
[for {opts.queryType} ⌘S]
[containing '{opts.queryStr}']
[{opts.limitRemote ? "this" : "any"} remote ⌘R]
[{opts.includeMeta ? "" : "no "}metacmds ⌘M]
(ESC)
[no history] 0}> {this.renderHItem(hitem, opts, (hitem == selItem))}
); } } @mobxReact.observer class CmdInput extends React.Component<{}, {}> { cmdInputRef : React.RefObject = React.createRef(); @boundMethod onInfoToggle() : void { GlobalModel.inputModel.toggleInfoMsg(); return; } componentDidMount() { this.updateCmdInputHeight(); } updateCmdInputHeight() { let elem = this.cmdInputRef.current; if (elem == null) { return; } let height = elem.offsetHeight; if (height == GlobalModel.inputModel.cmdInputHeight) { return; } mobx.action(() => { GlobalModel.inputModel.cmdInputHeight.set(height); })(); } componentDidUpdate(prevProps, prevState, snapshot : {}) : void { this.updateCmdInputHeight(); } @boundMethod handleInnerHeightUpdate() : void { this.updateCmdInputHeight(); } render() { let model = GlobalModel; let inputModel = model.inputModel; let win = GlobalModel.getActiveWindow(); let ri : RemoteInstanceType = null; let rptr : RemotePtrType = null; if (win != null) { ri = win.getCurRemoteInstance(); rptr = win.curRemote.get(); } let remote : RemoteType = null; let remoteState : FeStateType = null; if (ri != null) { remote = GlobalModel.getRemote(ri.remoteid); remoteState = ri.festate; } let infoShow = inputModel.infoShow.get(); let historyShow = !infoShow && inputModel.historyShow.get(); let infoMsg = inputModel.infoMsg.get(); let hasInfo = (infoMsg != null); let remoteShow = (infoMsg != null && !isBlank(infoMsg.ptyremoteid)); let focusVal = inputModel.physicalInputFocused.get(); let inputMode : string = inputModel.inputMode.get(); return (
{inputMode}
); } } const DOW_STRS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; function getDateStr(d : Date) : string { let yearStr = String(d.getFullYear()); let monthStr = String(d.getMonth()+1); if (monthStr.length == 1) { monthStr = "0" + monthStr; } let dayStr = String(d.getDate()); if (dayStr.length == 1) { dayStr = "0" + dayStr; } let dowStr = DOW_STRS[d.getDay()]; return dowStr + " " + yearStr + "-" + monthStr + "-" + dayStr; } function getLineDateStr(todayDate : string, yesterdayDate : string, ts : number) : string { let lineDate = new Date(ts); let dateStr = getDateStr(lineDate); if (dateStr == todayDate) { return "today"; } if (dateStr == yesterdayDate) { return "yesterday"; } return dateStr; } @mobxReact.observer class LinesView extends React.Component<{sw : ScreenWindow, width : number, lines : LineType[], renderMode : RenderModeType}, {}> { rszObs : any; linesRef : React.RefObject; staticRender : OV = mobx.observable.box(true, {name: "static-render"}); lastOffsetHeight : number = 0; lastOffsetWidth : number = 0; ignoreNextScroll : boolean = false; visibleMap : Map>; // lineid => OV collapsedMap : Map>; // lineid => OV lastLinesLength : number = 0; lastSelectedLine : number = 0; computeAnchorLine_throttled : () => void; computeVisibleMap_debounced : () => void; constructor(props) { super(props); this.linesRef = React.createRef(); this.computeAnchorLine_throttled = throttle(100, this.computeAnchorLine.bind(this), {noLeading: true, noTrailing: false}); this.visibleMap = new Map(); this.collapsedMap = new Map(); this.computeVisibleMap_debounced = debounce(1000, this.computeVisibleMap.bind(this)); } @boundMethod scrollHandler() { // console.log("scroll", this.linesRef.current.scrollTop); this.computeVisibleMap_debounced(); // always do this if (this.ignoreNextScroll) { this.ignoreNextScroll = false; return; } this.computeAnchorLine_throttled(); // only do this when we're not ignoring the scroll } computeAnchorLine() : void { let {sw} = this.props; let linesElem = this.linesRef.current; if (linesElem == null) { sw.setAnchorFields(null, 0, "no-lines"); return; } let lineElemArr = linesElem.querySelectorAll(".line"); if (lineElemArr == null || lineElemArr.length == 0) { sw.setAnchorFields(null, 0, "no-line"); return; } let scrollTop = linesElem.scrollTop; let height = linesElem.clientHeight; let containerBottom = scrollTop + height; let anchorElem : HTMLElement = null; for (let i=lineElemArr.length-1; i >= 0; i--) { let lineElem = lineElemArr[i]; let bottomPos = lineElem.offsetTop + lineElem.offsetHeight; if (anchorElem == null && (bottomPos <= containerBottom || lineElem.offsetTop <= scrollTop)) { anchorElem = lineElem; } } if (anchorElem == null) { anchorElem = lineElemArr[0]; } sw.setAnchorFields(parseInt(anchorElem.dataset.linenum), containerBottom - (anchorElem.offsetTop + anchorElem.offsetHeight), "computeAnchorLine"); } computeVisibleMap() : void { let linesElem = this.linesRef.current; if (linesElem == null) { return; } let lineElemArr = linesElem.querySelectorAll(".line"); if (lineElemArr == null) { return; } let containerTop = linesElem.scrollTop - LinesVisiblePadding; let containerBot = linesElem.scrollTop + linesElem.clientHeight + LinesVisiblePadding; let newMap = new Map(); // console.log("computevismap", linesElem.scrollTop, linesElem.clientHeight, containerTop + "-" + containerBot); for (let i=0; i= containerTop && lineTop <= containerBot) { isVis = true; } if (lineBot >= containerTop && lineBot <= containerBot) { isVis = true } newMap.set(lineElem.dataset.linenum, isVis); // console.log("setvis", sprintf("%4d %4d-%4d (%4d) %s", lineElem.dataset.linenum, lineTop, lineBot, lineElem.offsetHeight, isVis)); } mobx.action(() => { for (let [k, v] of newMap) { let oldVal = this.visibleMap.get(k); if (oldVal == null) { oldVal = mobx.observable.box(v, {name: "lines-vis-map"}); this.visibleMap.set(k, oldVal); } if (oldVal.get() != v) { oldVal.set(v); } } for (let [k, v] of this.visibleMap) { if (!newMap.has(k)) { this.visibleMap.delete(k); } } })(); } restoreAnchorOffset(reason : string) : void { let {sw} = this.props; let linesElem = this.linesRef.current; if (linesElem == null) { return; } if (sw.anchorLine == null || sw.anchorLine == 0) { return; } let anchorElem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", sw.anchorLine)); if (anchorElem == null) { return; } let isLastLine = sw.isLastLine(sw.anchorLine); let scrollTop = linesElem.scrollTop; let height = linesElem.clientHeight; let containerBottom = scrollTop + height; let curAnchorOffset = containerBottom - (anchorElem.offsetTop + anchorElem.offsetHeight); let newAnchorOffset = sw.anchorOffset; if (isLastLine && newAnchorOffset == 0) { newAnchorOffset = 10; } if (curAnchorOffset != newAnchorOffset) { let offsetDiff = curAnchorOffset - newAnchorOffset; let newScrollTop = scrollTop - offsetDiff; // console.log("update scrolltop", reason, "line=" + sw.anchorLine, -offsetDiff, linesElem.scrollTop, "=>", newScrollTop); linesElem.scrollTop = newScrollTop; this.ignoreNextScroll = true; } } componentDidMount() : void { let {sw, lines} = this.props; if (sw.anchorLine == null) { this.computeAnchorLine(); } else { this.restoreAnchorOffset("re-mount"); } this.lastSelectedLine = sw.getSelectedLine(); this.lastLinesLength = lines.length; let linesElem = this.linesRef.current; if (linesElem != null) { this.lastOffsetHeight = linesElem.offsetHeight; this.lastOffsetWidth = linesElem.offsetWidth; this.rszObs = new ResizeObserver(this.handleResize.bind(this)); this.rszObs.observe(linesElem); } mobx.action(() => { this.staticRender.set(false) this.computeVisibleMap(); })(); } getLineElem(lineNum : number) : HTMLElement { let linesElem = this.linesRef.current; if (linesElem == null) { return null; } let elem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", lineNum)); return elem; } getLineViewInfo(lineNum : number) : {height: number, topOffset: number, botOffset: number, anchorOffset: number} { let linesElem = this.linesRef.current; if (linesElem == null) { return null; } let lineElem = this.getLineElem(lineNum); if (lineElem == null) { return null; } let rtn = { height: lineElem.offsetHeight, topOffset: 0, botOffset: 0, anchorOffset: 0, }; let containerTop = linesElem.scrollTop; let containerBot = linesElem.scrollTop + linesElem.clientHeight; let lineTop = lineElem.offsetTop; let lineBot = lineElem.offsetTop + lineElem.offsetHeight; if (lineTop < containerTop) { rtn.topOffset = lineTop - containerTop; } else if (lineTop > containerBot) { rtn.topOffset = lineTop - containerBot; } if (lineBot < containerTop) { rtn.botOffset = lineBot - containerTop; } else if (lineBot > containerBot) { rtn.botOffset = lineBot - containerBot; } rtn.anchorOffset = containerBot - lineBot; return rtn; } updateSelectedLine() : void { let {sw, lines} = this.props; let linesElem = this.linesRef.current; if (linesElem == null) { return null; } let newLine = sw.getSelectedLine(); if (newLine == 0) { return; } this.setLineVisible(newLine, true); // console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", sw.anchorLine, sw.anchorOffset)); let viewInfo = this.getLineViewInfo(newLine); if (viewInfo == null) { return; } sw.setAnchorFields(newLine, viewInfo.anchorOffset, "updateSelectedLine"); let isFirst = (newLine == lines[0].linenum); let isLast = (newLine == lines[lines.length-1].linenum); if (viewInfo.botOffset > 0) { linesElem.scrollTop = linesElem.scrollTop + viewInfo.botOffset + (isLast ? 10 : 0); this.ignoreNextScroll = true; sw.anchorOffset = (isLast ? 10 : 0); } else if (viewInfo.topOffset < 0) { linesElem.scrollTop = linesElem.scrollTop + viewInfo.topOffset + (isFirst ? -10 : 0); this.ignoreNextScroll = true; sw.anchorOffset = linesElem.clientHeight - viewInfo.height; } // console.log("new anchor", sw.getAnchorStr()); } setLineVisible(lineNum : number, vis : boolean) : void { mobx.action(() => { let key = String(lineNum); let visObj = this.visibleMap.get(key); if (visObj == null) { visObj = mobx.observable.box(true, {name: "lines-vis-map"}); this.visibleMap.set(key, visObj); } else { visObj.set(true); } })(); } componentDidUpdate(prevProps, prevState, snapshot) : void { let {sw, lines} = this.props; if (sw.getSelectedLine() != this.lastSelectedLine) { this.updateSelectedLine(); this.lastSelectedLine = sw.getSelectedLine(); } else if (lines.length != this.lastLinesLength) { this.restoreAnchorOffset("line-length-change"); } } componentWillUnmount() : void { if (this.rszObs != null) { this.rszObs.disconnect(); } } handleResize(entries : any) { let linesElem = this.linesRef.current; if (linesElem == null) { return; } let heightDiff = linesElem.offsetHeight - this.lastOffsetHeight; if (heightDiff != 0) { linesElem.scrollTop = linesElem.scrollTop - heightDiff; this.lastOffsetHeight = linesElem.offsetHeight; this.ignoreNextScroll = true; } if (this.lastOffsetWidth != linesElem.offsetWidth) { this.restoreAnchorOffset("resize-width"); this.lastOffsetWidth = linesElem.offsetWidth; } this.computeVisibleMap_debounced(); } @boundMethod onHeightChange(lineNum : number, newHeight : number, oldHeight : number) : void { // console.log("height-change", lineNum, oldHeight, "=>", newHeight); this.restoreAnchorOffset("height-change"); this.computeVisibleMap_debounced(); } hasTopBorder(lines : LineType[], idx : number) : boolean { if (idx == 0) { return false; } let curLineNumStr = String(lines[idx].linenum); let prevLineNumStr = String(lines[idx-1].linenum); return !this.collapsedMap.get(curLineNumStr).get() || !this.collapsedMap.get(prevLineNumStr).get(); } getDateSepStr(lines : LineType[], idx : number, prevStr : string, todayStr : string, yesterdayStr : string) : string { let curLineDate = new Date(lines[idx].ts); let curLineFormat = dayjs(curLineDate).format("ddd YYYY-MM-DD"); if (idx == 0) { return ; } let prevLineDate = new Date(lines[idx].ts); let prevLineFormat = dayjs(prevLineDate).format("YYYY-MM-DD"); return null; } render() { let {sw, width, lines, renderMode} = this.props; let selectedLine = sw.getSelectedLine(); // for re-rendering let line : LineType = null; for (let i=0; i{dateSepStr}
lineElements.push(sepElem); } let topBorder = (dateSepStr == null) && this.hasTopBorder(lines, idx); let lineElem = ; lineElements.push(lineElem); } return (
{lineElements}
); } } // sw is not null @mobxReact.observer class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { rszObs : any; windowViewRef : React.RefObject; width : mobx.IObservableValue = mobx.observable.box(0, {name: "sw-view-width"}); height : mobx.IObservableValue = mobx.observable.box(0, {name: "sw-view-height"}); setSize_debounced : (width : number, height : number) => void; renderMode : OV = mobx.observable.box("normal", {name: "renderMode"}); constructor(props : any) { super(props); this.setSize_debounced = debounce(1000, this.setSize.bind(this)); this.windowViewRef = React.createRef(); } setSize(width : number, height : number) : void { let {sw} = this.props; if (sw == null) { return; } mobx.action(() => { this.width.set(width); this.height.set(height); let cols = windowWidthToCols(width, GlobalModel.termFontSize.get()); let rows = windowHeightToRows(height, GlobalModel.termFontSize.get()); if (cols == 0 || rows == 0) { console.log("cannot set sw size", rows, cols); return; } sw.termSizeCallback(rows, cols); })(); } componentDidMount() { let wvElem = this.windowViewRef.current; if (wvElem != null) { let width = wvElem.offsetWidth; let height = wvElem.offsetHeight; this.setSize(width, height); this.rszObs = new ResizeObserver(this.handleResize.bind(this)); this.rszObs.observe(wvElem); } } componentWillUnmount() { if (this.rszObs) { this.rszObs.disconnect(); } } handleResize(entries : any) { if (entries.length == 0) { return; } let entry = entries[0]; let width = entry.target.offsetWidth; let height = entry.target.offsetHeight; this.setSize_debounced(width, height); } getWindow() : Window { let {sw} = this.props; let win = GlobalModel.getWindowById(sw.sessionId, sw.windowId); if (win == null) { win = GlobalModel.loadWindow(sw.sessionId, sw.windowId); } return win; } getWindowViewStyle() : any { return {position: "absolute", width: "100%", height: "100%", overflowX: "hidden"}; } @boundMethod toggleRenderMode() { let renderMode = this.renderMode.get(); mobx.action(() => { this.renderMode.set(renderMode == "normal" ? "collapsed" : "normal"); })(); } renderError(message : string, fade : boolean) { let {sw} = this.props; return (
{sw.name.get()}
{message}
); } render() { let {sw} = this.props; let win = this.getWindow(); if (win == null || !win.loaded.get()) { return this.renderError("...", true); } if (win.loadError.get() != null) { return this.renderError(sprintf("(%s)", win.loadError.get()), false); } if (this.width.get() == 0) { return this.renderError("", false); } let cdata = GlobalModel.clientData.get(); if (cdata == null) { return this.renderError("loading client data", true); } let idx = 0; let line : LineType = null; let screen = GlobalModel.getScreenById(sw.sessionId, sw.screenId); let session = GlobalModel.getSessionById(sw.sessionId); let isActive = sw.isActive(); let selectedLine = sw.getSelectedLine(); let lines = win.getNonArchivedLines(); let renderMode = this.renderMode.get(); return (
{sw.name.get()}
0}>
[session="{session.name.get()}" screen="{screen.name.get()}" window="{sw.name.get()}"]
); } } @mobxReact.observer class ScreenView extends React.Component<{screen : Screen}, {}> { render() { let {screen} = this.props; let sw : ScreenWindow = null; if (screen != null) { sw = screen.getActiveSW(); } if (screen == null || sw == null) { return (
(no screen or window)
); } let fontSize = GlobalModel.termFontSize.get(); let swKey = sw.windowId + "-fs" + fontSize; return (
); } } @mobxReact.observer class ScreenTabs extends React.Component<{session : Session}, {}> { tabsRef : React.RefObject = React.createRef(); lastActiveScreenId : string = null; scrolling : OV = mobx.observable.box(false, {name: "screentabs-scrolling"}); stopScrolling_debounced : () => void; constructor(props : any) { super(props); this.stopScrolling_debounced = debounce(1500, this.stopScrolling.bind(this)); } @boundMethod handleNewScreen() { let {session} = this.props; GlobalCommandRunner.createNewScreen(); } @boundMethod handleSwitchScreen(screenId : string) { let {session} = this.props; if (session == null) { return; } if (session.activeScreenId.get() == screenId) { return; } let screen = session.getScreenById(screenId); if (screen == null) { return; } GlobalCommandRunner.switchScreen(screenId); } handleContextMenu(e : any, screenId : string) : void { e.preventDefault(); console.log("handle context menu!", screenId); } componentDidMount() : void { this.componentDidUpdate(); } componentDidUpdate() : void { let {session} = this.props; let activeScreenId = session.activeScreenId.get(); if (activeScreenId != this.lastActiveScreenId && this.tabsRef.current) { let tabElem = this.tabsRef.current.querySelector(sprintf(".screen-tab[data-screenid=\"%s\"]", activeScreenId)); if (tabElem != null) { tabElem.scrollIntoView(); } } this.lastActiveScreenId = activeScreenId; } stopScrolling() : void { mobx.action(() => { this.scrolling.set(false); })(); } @boundMethod handleScroll() { if (!this.scrolling.get()) { mobx.action(() => { this.scrolling.set(true); })(); } this.stopScrolling_debounced(); } render() { let {session} = this.props; if (session == null) { return null; } let screen : Screen = null; let index = 0; let showingScreens = []; let activeScreenId = session.activeScreenId.get(); for (let screen of session.screens) { if (!screen.archived.get() || activeScreenId == screen.screenId) { showingScreens.push(screen); } } return (
this.handleSwitchScreen(screen.screenId)} onContextMenu={(event) => this.handleContextMenu(event, screen.screenId)}> {screen.name.get()}
⌘{index+1}
+
); } } @mobxReact.observer class SessionView extends React.Component<{}, {}> { render() { let model = GlobalModel; let session = model.getActiveSession(); if (session == null) { return
(no active session)
; } let activeScreen = session.getActiveScreen(); let cmdInputHeight = model.inputModel.cmdInputHeight.get(); if (cmdInputHeight == 0) { cmdInputHeight = 110; } let isHidden = (GlobalModel.activeMainView.get() != "session"); return (
); } } function getConnVal(r : RemoteType) : number { if (r.status == "connected") { return 1; } if (r.status == "init" || r.status == "disconnected") { return 2; } if (r.status == "error") { return 3; } return 4; } @mobxReact.observer class RemoteStatusLight extends React.Component<{remote : RemoteType}, {}> { render() { let remote = this.props.remote; let status = "error"; let wfp = false; if (remote != null) { status = remote.status; wfp = remote.waitingforpassword; } let icon = "fa-sharp fa-solid fa-circle" if (status == "connecting") { icon = (wfp ? "fa-sharp fa-solid fa-key" : "fa-sharp fa-solid fa-rotate"); } return ( ); } } @mobxReact.observer class MainSideBar extends React.Component<{}, {}> { collapsed : mobx.IObservableValue = mobx.observable.box(false); @boundMethod toggleCollapsed() { mobx.action(() => { this.collapsed.set(!this.collapsed.get()); })(); } handleSessionClick(sessionId : string) { GlobalCommandRunner.switchSession(sessionId); } handleNewSession() { GlobalCommandRunner.createNewSession(); } handleNewSharedSession() { GlobalCommandRunner.openSharedSession(); } clickRemotes() { GlobalCommandRunner.showAllRemotes(); } remoteDisplayName(remote : RemoteType) : any { if (!isBlank(remote.remotealias)) { return ( <> {remote.remotealias} {remote.remotecanonicalname} ); } return ({remote.remotecanonicalname}); } clickRemote(remote : RemoteType) { GlobalCommandRunner.showRemote(remote.remoteid); } @boundMethod handleAddRemote() : void { GlobalCommandRunner.openCreateRemote(); } @boundMethod handleHistoryClick() : void { if (GlobalModel.activeMainView.get() == "history") { mobx.action(() => { GlobalModel.activeMainView.set("session"); })(); return; } GlobalCommandRunner.historyView({}); } @boundMethod handlePlaybookClick() : void { console.log("playbook click"); return; } @boundMethod handleBookmarksClick() : void { if (GlobalModel.activeMainView.get() == "bookmarks") { mobx.action(() => { GlobalModel.activeMainView.set("session"); })(); return; } GlobalCommandRunner.bookmarksView(); } render() { let model = GlobalModel; let activeSessionId = model.activeSessionId.get(); let activeWindow = model.getActiveWindow(); let activeRemoteId : string = null; if (activeWindow != null) { let rptr = activeWindow.curRemote.get(); if (rptr != null && !isBlank(rptr.remoteid)) { activeRemoteId = rptr.remoteid; } } let sw : ScreenWindow = null; if (GlobalModel.debugSW.get()) { sw = GlobalModel.getActiveSW(); } let session : Session = null; let remotes = model.remotes ?? []; let remote : RemoteType = null; let idx : number = 0; remotes = sortAndFilterRemotes(remotes); let sessionList = []; for (let session of model.sessionList) { if (!session.archived.get() || session.sessionId == activeSessionId) { sessionList.push(session); } } let isCollapsed = this.collapsed.get(); let mainView = GlobalModel.activeMainView.get(); let activePlaybookId : string = null; return ( ); } } function sortAndFilterRemotes(origRemotes : RemoteType[]) : RemoteType[] { let remotes = origRemotes.filter((r) => !r.archived); remotes.sort((a, b) => { let connValA = getConnVal(a); let connValB = getConnVal(b); if (connValA != connValB) { return connValA - connValB; } return a.remoteidx - b.remoteidx; }); return remotes; } @mobxReact.observer class DisconnectedModal extends React.Component<{}, {}> { logRef : any = React.createRef(); showLog : mobx.IObservableValue = mobx.observable.box(false) @boundMethod restartServer() { GlobalModel.restartLocalServer(); } @boundMethod tryReconnect() { GlobalModel.ws.connectNow("manual"); } componentDidMount() { if (this.logRef.current != null) { this.logRef.current.scrollTop = this.logRef.current.scrollHeight; } } componentDidUpdate() { if (this.logRef.current != null) { this.logRef.current.scrollTop = this.logRef.current.scrollHeight; } } @boundMethod handleShowLog() : void { mobx.action(() => { this.showLog.set(!this.showLog.get()); })(); } render() { let model = GlobalModel; let logLine : string = null; let idx : number = 0; return (

Prompt Client Disconnected

{logLine}
Show Log Hide Log
); } } @mobxReact.observer class LoadingSpinner extends React.Component<{}, {}> { render() { return (
); } } @mobxReact.observer class AlertModal extends React.Component<{}, {}> { closeModal() : void { mobx.action(() => { GlobalModel.alertMessage.set(null); })(); } render() { let message = GlobalModel.alertMessage.get(); if (message == null) { return null; } let title = message.title ?? "Alert"; return (

{title}

{message.message}

); } } @mobxReact.observer class Main extends React.Component<{}, {}> { constructor(props : any) { super(props); } @boundMethod handleContextMenu(e : any) { let isInNonTermInput = false; let activeElem = document.activeElement; if (activeElem != null && activeElem.nodeName == "TEXTAREA") { if (!activeElem.classList.contains("xterm-helper-textarea")) { isInNonTermInput = true; } } if (activeElem != null && activeElem.nodeName == "INPUT" && activeElem.getAttribute("type") == "text") { isInNonTermInput = true; } let opts : ContextMenuOpts = {}; if (isInNonTermInput) { opts.showCut = true; } let sel = window.getSelection(); if (!isBlank(sel.toString())) { GlobalModel.contextEditMenu(e, opts); } else { if (isInNonTermInput) { GlobalModel.contextEditMenu(e, opts); } } } render() { return (
); } } export {Main};