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, ContextMenuOpts, BookmarkType, RenderModeType, 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, getMonoFontSize} from "./textmeasure"; import {isModKeyPress, boundInt, sortAndFilterRemotes, makeExternLink, isBlank, hasNoModifiers} from "./util"; import {BookmarksView} from "./bookmarks"; import {WebShareView} from "./webshare-client-view"; import {HistoryView} from "./history"; import {Line, Prompt} from "./linecomps"; import {ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal} from "./settings"; import {RemotesModal} from "./remotes"; import {renderCmdText, RemoteStatusLight, Markdown} from "./elements"; import {LinesView} from "./linesview"; import {TosModal} from "./modals"; 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, visible : mobx.IObservableValue, timeoutid? : any, }; 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(); } } getTextAreaMaxCols() : number { let taElem = this.mainInputRef.current; if (taElem == null) { return 0; } let cs = window.getComputedStyle(taElem); let padding = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight); let borders = parseFloat(cs.borderLeft) + parseFloat(cs.borderRight); let contentWidth = taElem.clientWidth - padding - borders; let fontSize = getMonoFontSize(parseInt(cs.fontSize)); let maxCols = Math.floor(contentWidth / fontSize.width); return maxCols; } 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))(); } if (inputModel.forceInputFocus) { inputModel.forceInputFocus = false; this.setFocus(); } 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 == "KeyE" && e.getModifierState("Meta")) { e.preventDefault(); e.stopPropagation(); let inputModel = GlobalModel.inputModel; inputModel.toggleExpandInput(); } 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.code == "ArrowDown") && hasNoModifiers(e)) { 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 displayLines = 1; let numLines = curLine.split("\n").length; let maxCols = this.getTextAreaMaxCols(); let longLine = false; if (maxCols != 0 && curLine.length >= maxCols - 4) { longLine = true; } if (numLines > 1 || longLine || inputModel.inputExpanded.get()) { displayLines = 5; } let disabled = inputModel.historyShow.get(); if (disabled) { displayLines = 1; } let activeScreen = GlobalModel.getActiveScreen(); if (activeScreen != null) { activeScreen.focusType.get(); // for reaction } let computedHeight = (displayLines*24)+14+2; // 24 = height of line, 14 = padding, 2 = border return (
); } } @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; } 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 feState : Record = null; if (ri != null) { remote = GlobalModel.getRemote(ri.remoteid); feState = ri.festate; } let infoShow = inputModel.infoShow.get(); let historyShow = !infoShow && inputModel.historyShow.get(); let infoMsg = inputModel.infoMsg.get(); let hasInfo = (infoMsg != null); let focusVal = inputModel.physicalInputFocused.get(); let inputMode : string = inputModel.inputMode.get(); let textAreaInputKey = (screen == null ? "null" : screen.screenId); return (
WARNING: [{GlobalModel.resolveRemoteIdToFullRef(remote.remoteid)}] is {remote.status}
this.clickConnectRemote(remote.remoteid)}>connect now
{inputMode}
{inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")})
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; 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 openScreenSettings() : void { let {screen} = this.props; mobx.action(() => { GlobalModel.screenSettingsModal.set({sessionId: screen.sessionId, screenId: screen.screenId}); })(); } @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
open settings
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}); })(); } renderTab(screen : Screen, activeScreenId : string, index : number) : any { let tabIndex = null; if (index+1 <= 9) { tabIndex = (
{renderCmdText(String(index+1))}
); } let settings = (
this.openScreenSettings(e, screen)} title="Settings" className="tab-gear">
); let archived = (screen.archived.get() ? () : null); let webShared = (screen.isWebShared() ? () : null); return (
this.handleSwitchScreen(screen.screenId)} onContextMenu={(event) => this.openScreenSettings(event, screen)}>
{archived} {webShared} {screen.name.get()}
{tabIndex} {settings}
); } 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.renderTab(screen, activeScreenId, index)}
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 (
); } } @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(); } 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") { GlobalModel.showSessionView(); return; } GlobalCommandRunner.bookmarksView(); } @boundMethod handleWebSharingClick() : void { if (GlobalModel.activeMainView.get() == "webshare") { GlobalModel.showSessionView(); return; } GlobalModel.showWebShareView(); } @boundMethod handleWelcomeClick() : void { mobx.action(() => { GlobalModel.welcomeModalOpen.set(true); })(); } @boundMethod handleSettingsClick() : void { mobx.action(() => { GlobalModel.clientSettingsModal.set(true); })(); } @boundMethod handleConnectionsClick() : void { GlobalModel.remotesModalModel.openModal(); } @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 ( ); } } @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 title = "Client Not Ready"; return (
[prompt] {title}
Cannot get client data.
); } } @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 ?? (message.confirm ? "Confirm" : "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 remotesModal = GlobalModel.remotesModalModel.isOpen(); 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};