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 {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import cn from "classnames"; import {debounce, throttle} from "throttle-debounce"; import {v4 as uuidv4} from "uuid"; import dayjs from "dayjs"; import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType, ContextMenuOpts, BookmarkType, RenderModeType, ClientMigrationInfo, LineFactoryProps} from "./types"; import type * as T from "./types"; import localizedFormat from 'dayjs/plugin/localizedFormat'; import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, riToRPtr, TabColors, RemoteColors} from "./model"; import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./textmeasure"; import {isModKeyPress, boundInt} 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"; import {ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal} from "./settings"; import {renderCmdText} from "./elements"; import {LinesView} from "./linesview"; dayjs.extend(localizedFormat) const RemotePtyRows = 8; const RemotePtyCols = 80; const PasswordUnchangedSentinel = "--unchanged--"; const LinesVisiblePadding = 500; const TDots = "⋮"; type OV = mobx.IObservableValue; type OArr = mobx.IObservableArray; type OMap = mobx.ObservableMap; type VisType = "visible" | ""; type InterObsValue = { sessionid : string, lineid : string, cmdid : string, visible : mobx.IObservableValue, timeoutid? : any, }; function isBlank(s : string) : boolean { return (s == null || s == ""); } function makeExternLink(url : string) : string { return "https://extern?" + encodeURIComponent(url); } 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 truncateWithTDots(str : string, maxLen : number) : string { if (str == null) { return null; } if (str.length <= maxLen) { return str; } return str.slice(0, maxLen-1) + TDots; } 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 activeScreen = GlobalModel.getActiveScreen(); if (activeScreen != null) { let focusType = activeScreen.focusType.get(); if (focusType == "input") { this.setFocus(); } this.lastFocusType = focusType; } this.checkHeight(false); } componentDidUpdate() { let activeScreen = GlobalModel.getActiveScreen(); if (activeScreen != null) { let focusType = activeScreen.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.getScreenLinesForActiveScreen(); 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.getScreenLinesForActiveScreen(); let activeScreen = GlobalModel.getActiveScreen(); if (activeScreen != null && activeWindow != null && activeWindow.lines.length > 0) { activeScreen.setSelectedLine(0); GlobalCommandRunner.screenSelectLine("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 == "KeyU" && e.getModifierState("Control")) { e.preventDefault(); this.controlU(); return; } if (e.code == "KeyP" && e.getModifierState("Control")) { e.preventDefault(); this.controlP(); return; } if (e.code == "KeyN" && e.getModifierState("Control")) { e.preventDefault(); this.controlN(); return; } if (e.code == "KeyW" && e.getModifierState("Control")) { e.preventDefault(); this.controlW(); return; } if (e.code == "KeyY" && e.getModifierState("Control")) { e.preventDefault(); this.controlY(); 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, "screen"); } 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 == "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 == "screen") { htype = "session"; } else if (htype == "session") { htype = "global"; } else { htype = "screen"; } 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; } if (e.code == "KeyP" && e.getModifierState("Control")) { e.preventDefault(); inputModel.moveHistorySelection(1); return; } if (e.code == "KeyN" && e.getModifierState("Control")) { e.preventDefault(); inputModel.moveHistorySelection(-1); return; } } @boundMethod controlU() { if (this.mainInputRef.current == null) { return; } let selStart = this.mainInputRef.current.selectionStart; let value = this.mainInputRef.current.value; if (selStart > value.length) { return; } let cutValue = value.substr(0, selStart); let restValue = value.substr(selStart); let cmdLineUpdate = {cmdline: restValue, cursorpos: 0}; console.log("ss", selStart, value, "[" + cutValue + "]", "[" + restValue + "]"); navigator.clipboard.writeText(cutValue); GlobalModel.inputModel.updateCmdLine(cmdLineUpdate); } @boundMethod controlP() { let inputModel = GlobalModel.inputModel; if (!inputModel.isHistoryLoaded()) { this.lastHistoryUpDown = true; inputModel.loadHistory(false, 1, "screen"); return; } inputModel.moveHistorySelection(1); this.lastHistoryUpDown = true; } @boundMethod controlN() { let inputModel = GlobalModel.inputModel; inputModel.moveHistorySelection(-1); this.lastHistoryUpDown = true; } @boundMethod controlW() { if (this.mainInputRef.current == null) { return; } let selStart = this.mainInputRef.current.selectionStart; let value = this.mainInputRef.current.value; if (selStart > value.length) { return; } let cutSpot = selStart-1; let initial = true; for (;cutSpot>=0; cutSpot--) { let ch = value[cutSpot]; console.log(cutSpot, "[" + ch + "]"); if (ch == " " && initial) { continue; } initial = false; if (ch == " ") { cutSpot++; break; } } let cutValue = value.slice(cutSpot, selStart); let prevValue = value.slice(0, cutSpot); let restValue = value.slice(selStart); let cmdLineUpdate = {cmdline: prevValue + restValue, cursorpos: prevValue.length}; console.log("ss", selStart, value, "prev[" + prevValue + "]", "cut[" + cutValue + "]", "rest[" + restValue + "]"); console.log(" ", cmdLineUpdate); navigator.clipboard.writeText(cutValue); GlobalModel.inputModel.updateCmdLine(cmdLineUpdate); } @boundMethod controlY() { if (this.mainInputRef.current == null) { return; } let pastePromise = navigator.clipboard.readText(); pastePromise.then((clipText) => { clipText = clipText ?? ""; let selStart = this.mainInputRef.current.selectionStart; let selEnd = this.mainInputRef.current.selectionEnd; let value = this.mainInputRef.current.value; if (selStart > value.length || selEnd > value.length) { return; } let newValue = value.substr(0, selStart) + clipText + value.substr(selEnd); let cmdLineUpdate = {cmdline: newValue, cursorpos: selStart+clipText.length}; GlobalModel.inputModel.updateCmdLine(cmdLineUpdate); }); } @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 activeScreen = GlobalModel.getActiveScreen(); if (activeScreen != null) { activeScreen.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"; } if (this.portStr.get() != "" && this.portStr.get() != "22") { kwargs["port"] = this.portStr.get(); } } 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"; 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); } } let activeScreen = model.getActiveScreen(); return (
{titleStr}
{infoMsg.infomsg}
started sharing screen at [link]
{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 ", "[" + truncateWithTDots(rname, 13) + "]") return rtn; } renderHInfoText(hitem : HistoryItem, opts : HistoryQueryOpts, isSelected : boolean, snames : Record, scrNames : Record) : string { let remoteStr = ""; if (!opts.limitRemote) { remoteStr = this.renderRemote(hitem); } let selectedStr = (isSelected ? "*" : " "); let lineNumStr = (hitem.linenum > 0 ? "(" + hitem.linenum + ")" : ""); if (isBlank(opts.queryType) || opts.queryType == "screen") { return selectedStr + sprintf("%7s", lineNumStr) + " " + remoteStr; } if (opts.queryType == "session") { let screenStr = ""; if (!isBlank(hitem.screenid)) { let scrName = scrNames[hitem.screenid]; if (scrName != null) { screenStr = "[" + truncateWithTDots(scrName, 15) + "]"; } } return selectedStr + sprintf("%17s", screenStr) + sprintf("%7s", lineNumStr) + " " + remoteStr; } if (opts.queryType == "global") { let sessionStr = ""; if (!isBlank(hitem.sessionid)) { let sessionName = snames[hitem.sessionid]; if (sessionName != null) { sessionStr = "#" + truncateWithTDots(sessionName, 15); } } let screenStr = ""; if (!isBlank(hitem.screenid)) { let scrName = scrNames[hitem.screenid]; if (scrName != null) { screenStr = "[" + truncateWithTDots(scrName, 13) + "]"; } } let ssStr = sessionStr + screenStr; return selectedStr + sprintf("%15s ", sessionStr) + " " + sprintf("%15s", screenStr) + sprintf("%7s", lineNumStr) + " " + remoteStr; } return "-"; } renderHItem(hitem : HistoryItem, opts : HistoryQueryOpts, isSelected : boolean, snames : Record, scrNames : Record) : any { let lines = hitem.cmdstr.split("\n"); let line : string = ""; let idx = 0; let infoText = this.renderHInfoText(hitem, opts, isSelected, snames, scrNames); let infoTextSpacer = sprintf("%" + infoText.length + "s", ""); return (
this.handleItemClick(hitem)}>
{infoText} {lines[0]}
{infoTextSpacer} {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(); let snames : Record = {}; let scrNames : Record = {}; if (opts.queryType == "global") { scrNames = GlobalModel.getScreenNames(); snames = GlobalModel.getSessionNames(); } else if (opts.queryType == "session") { scrNames = GlobalModel.getScreenNames(); } return (
history
[for {opts.queryType} ⌘S]
[containing '{opts.queryStr}']
[{opts.limitRemote ? "this" : "any"} remote ⌘R]
(ESC)
[no history] 0}> {this.renderHItem(hitem, opts, (hitem == selItem), snames, scrNames)}
); } } @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(); } @boundMethod clickFocusInputHint() : void { GlobalModel.inputModel.giveFocus(); } @boundMethod clickHistoryHint(e : any) : void { e.preventDefault(); e.stopPropagation(); let inputModel = GlobalModel.inputModel; if (inputModel.historyShow.get()) { inputModel.resetHistory(); } else { inputModel.openHistory(); } } @boundMethod clickConnectRemote(remoteId : string) : void { GlobalCommandRunner.connectRemote(remoteId); } render() { let model = GlobalModel; let inputModel = model.inputModel; let screen = GlobalModel.getActiveScreen(); let ri : RemoteInstanceType = null; let rptr : RemotePtrType = null; if (screen != null) { ri = screen.getCurRemoteInstance(); rptr = screen.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 (
WARNING: [{GlobalModel.resolveRemoteIdToFullRef(remote.remoteid)}] is {remote.status}
this.clickConnectRemote(remote.remoteid)}>connect now
{inputMode}
focus input ({renderCmdText("I")})
{historyShow ? "close history (esc)" : "show history (ctrl-r)"}
); } } // screen is not null @mobxReact.observer class ScreenWindowView extends React.Component<{screen : Screen}, {}> { 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"}); shareCopied : OV = mobx.observable.box(false, {name: "sw-shareCopied"}); 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 {screen} = this.props; if (screen == null) { return; } if (width == null || height == null || width == 0 || height == 0) { return; } mobx.action(() => { this.width.set(width); this.height.set(height); screen.screenSizeCallback({height: height, width: width}); })(); } componentDidMount() { let wvElem = this.windowViewRef.current; if (wvElem != null) { let width = wvElem.offsetWidth; let height = wvElem.offsetHeight; mobx.action(() => { 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; mobx.action(() => { this.setSize_debounced(width, height); })(); } getScreenLines() : ScreenLines { let {screen} = this.props; let win = GlobalModel.getScreenLinesById(screen.screenId); if (win == null) { win = GlobalModel.loadScreenLines(screen.screenId); } 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 {screen} = this.props; return (
{message}
); } @boundMethod copyShareLink() : void { let {screen} = this.props; let shareLink = screen.getWebShareUrl(); if (shareLink == null) { return; } navigator.clipboard.writeText(shareLink); mobx.action(() => { this.shareCopied.set(true); })(); setTimeout(() => { mobx.action(() => { this.shareCopied.set(false); })(); }, 600) } @boundMethod buildLineComponent(lineProps : LineFactoryProps) : JSX.Element { let {screen} = this.props; let {line, ...restProps} = lineProps; let realLine : LineType = (line as LineType); return ( ); } render() { let {screen} = this.props; let win = this.getScreenLines(); 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 session = GlobalModel.getSessionById(screen.sessionId); let isActive = screen.isActive(); let selectedLine = screen.getSelectedLine(); let lines = win.getNonArchivedLines(); let renderMode = this.renderMode.get(); return (
web shared
copy link
0}>
[session="{session.name.get()}" screen="{screen.name.get()}"]
); } } @mobxReact.observer class ScreenView extends React.Component<{screen : Screen}, {}> { render() { let {screen} = this.props; if (screen == null) { return (
(no screen found)
); } let fontSize = GlobalModel.termFontSize.get(); 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); } 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(); } @boundMethod openScreenSettings(e : any, screen : Screen) : void { e.preventDefault(); e.stopPropagation(); mobx.action(() => { GlobalModel.screenSettingsModal.set({sessionId: screen.sessionId, screenId: screen.screenId}); })(); } render() { let {session} = this.props; if (session == null) { return null; } let screen : Screen = null; let index = 0; let showingScreens = []; let activeScreenId = session.activeScreenId.get(); let screens = GlobalModel.getSessionScreens(session.sessionId); for (let screen of screens) { if (!screen.archived.get() || activeScreenId == screen.screenId) { showingScreens.push(screen); } } showingScreens.sort((a, b) => { let aidx = a.screenIdx.get(); let bidx = b.screenIdx.get(); if (aidx < bidx) { return -1; } if (aidx > bidx) { return 1; } return 0; }); return (
this.handleSwitchScreen(screen.screenId)} onContextMenu={(event) => this.openScreenSettings(event, screen)}> {screen.name.get()}
{renderCmdText(String(index+1))}
this.openScreenSettings(e, screen)} title="Settings" className="tab-gear">
9}>
this.openScreenSettings(e, screen)} title="Settings" className="tab-gear">
move left {renderCmdText("[")}
move right {renderCmdText("]")}
new tab {renderCmdText("T")}
); } } @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 == "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(); } clickLinks() { mobx.action(() => { GlobalModel.showLinks.set(!GlobalModel.showLinks.get()); })(); } 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; } GlobalModel.historyViewModel.reSearch(); } @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(); } @boundMethod handleWelcomeClick() : void { mobx.action(() => { GlobalModel.welcomeModalOpen.set(true); })(); } @boundMethod handleSettingsClick() : void { mobx.action(() => { GlobalModel.clientSettingsModal.set(true); })(); } @boundMethod openSessionSettings(e : any, session : Session) : void { e.preventDefault(); e.stopPropagation(); mobx.action(() => { GlobalModel.sessionSettingsModal.set(session.sessionId); })(); } render() { let model = GlobalModel; let activeSessionId = model.activeSessionId.get(); let activeWindow = model.getScreenLinesForActiveScreen(); let activeScreen = model.getActiveScreen(); let activeRemoteId : string = null; if (activeScreen != null) { let rptr = activeScreen.curRemote.get(); if (rptr != null && !isBlank(rptr.remoteid)) { activeRemoteId = rptr.remoteid; } } 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 ClientStopModal extends React.Component<{}, {}> { @boundMethod refreshClient() { GlobalModel.refreshClient(); } render() { let model = GlobalModel; let cdata = model.clientData.get(); let mdata : ClientMigrationInfo = (cdata != null ? cdata.migration : null); let title = "Client Not Ready"; if (mdata != null) { title = "Migrating Data"; } return (
[prompt] {title}
Cannot get client data.
Client database is being migrated to the latest version, please wait.
{mdata.migrationpos}
{mdata.migrationpos}/{mdata.migrationtotal}
); } } @mobxReact.observer class LoadingSpinner extends React.Component<{}, {}> { render() { return (
); } } @mobxReact.observer class AlertModal extends React.Component<{}, {}> { @boundMethod closeModal() : void { GlobalModel.cancelAlert(); } @boundMethod handleOK() : void { GlobalModel.confirmAlert(); } render() { let message = GlobalModel.alertMessage.get(); if (message == null) { return null; } let title = message.title ?? "Alert"; let isConfirm = message.confirm; return (

{title}

{message.message}

Cancel
OK
OK
); } } @mobxReact.observer class WelcomeModal extends React.Component<{}, {}> { totalPages : number = 3; pageNum : OV = mobx.observable.box(1, {name: "welcome-pagenum"}); @boundMethod closeModal() : void { mobx.action(() => { GlobalModel.welcomeModalOpen.set(false); })(); } @boundMethod goNext() : void { mobx.action(() => { this.pageNum.set(this.pageNum.get() + 1); })(); } @boundMethod goPrev() : void { mobx.action(() => { this.pageNum.set(this.pageNum.get() - 1); })(); } renderDot(num : number) : any { if (num == this.pageNum.get()) { return ; } return ; } renderDots() : any { let elems : any = []; for (let i=1; i<=this.totalPages; i++) { let elem = this.renderDot(i); elems.push(elem); } return elems; } render() { let pageNum = this.pageNum.get(); return (
welcome to [prompt]

Prompt is a new terminal to help save you time and keep your command-line life organized. Here's a couple quick tips to get your started!

1}>
{this.renderDots()}
); } } @mobxReact.observer class Main extends React.Component<{}, {}> { dcWait : OV = mobx.observable.box(false, {name: "dcWait"}); 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); } } } @boundMethod updateDcWait(val : boolean) : void { mobx.action(() => { this.dcWait.set(val); })(); } render() { let screenSettingsModal = GlobalModel.screenSettingsModal.get(); let sessionSettingsModal = GlobalModel.sessionSettingsModal.get(); let lineSettingsModal = GlobalModel.lineSettingsModal.get(); let clientSettingsModal = GlobalModel.clientSettingsModal.get(); let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.localServerRunning.get(); let hasClientStop = GlobalModel.getHasClientStop(); let dcWait = this.dcWait.get(); if (disconnected || hasClientStop) { if (!dcWait) { setTimeout(() => this.updateDcWait(true), 1500); } return (
); } if (dcWait) { setTimeout(() => this.updateDcWait(false), 0); } return (
); } } export {Main};