2022-06-08 02:25:35 +02:00
|
|
|
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";
|
2022-10-08 03:25:47 +02:00
|
|
|
import {debounce, throttle} from "throttle-debounce";
|
2022-07-14 08:11:45 +02:00
|
|
|
import {v4 as uuidv4} from "uuid";
|
2022-09-06 02:21:31 +02:00
|
|
|
import dayjs from "dayjs";
|
2022-06-08 02:25:35 +02:00
|
|
|
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
2022-09-06 02:21:31 +02:00
|
|
|
import cn from "classnames";
|
2022-06-17 01:34:46 +02:00
|
|
|
import {TermWrap} from "./term";
|
2022-11-29 03:08:19 +01:00
|
|
|
import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType} from "./types";
|
2022-06-18 02:54:14 +02:00
|
|
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
2022-11-23 23:34:05 +01:00
|
|
|
import {GlobalModel, GlobalCommandRunner, Session, Cmd, Window, Screen, ScreenWindow, riToRPtr, widthToCols, termWidthFromCols, termHeightFromRows, termRowsFromHeight} from "./model";
|
2022-10-14 03:58:21 +02:00
|
|
|
import {isModKeyPress} from "./util";
|
2022-06-18 02:54:14 +02:00
|
|
|
|
|
|
|
dayjs.extend(localizedFormat)
|
2022-06-13 20:12:39 +02:00
|
|
|
|
2022-09-16 02:10:02 +02:00
|
|
|
const RemotePtyRows = 8;
|
2022-09-15 09:18:20 +02:00
|
|
|
const RemotePtyCols = 80;
|
2022-10-04 04:05:52 +02:00
|
|
|
const PasswordUnchangedSentinel = "--unchanged--";
|
2022-10-08 03:25:47 +02:00
|
|
|
const LinesVisiblePadding = 500;
|
2022-09-15 09:18:20 +02:00
|
|
|
|
2022-09-30 23:57:23 +02:00
|
|
|
const RemoteColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"];
|
|
|
|
|
2022-10-08 03:25:47 +02:00
|
|
|
type OV<V> = mobx.IObservableValue<V>;
|
|
|
|
type OArr<V> = mobx.IObservableArray<V>;
|
|
|
|
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
|
|
|
|
|
|
|
type HeightChangeCallbackType = (lineNum : number, newHeight : number, oldHeight : number) => void;
|
|
|
|
|
2022-08-13 03:34:56 +02:00
|
|
|
type InterObsValue = {
|
|
|
|
sessionid : string,
|
|
|
|
windowid : string,
|
|
|
|
lineid : string,
|
|
|
|
cmdid : string,
|
|
|
|
visible : mobx.IObservableValue<boolean>,
|
|
|
|
timeoutid? : any,
|
|
|
|
};
|
|
|
|
|
2022-08-18 09:39:06 +02:00
|
|
|
function isBlank(s : string) : boolean {
|
|
|
|
return (s == null || s == "");
|
|
|
|
}
|
|
|
|
|
2022-08-29 22:54:11 +02:00
|
|
|
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"});
|
|
|
|
}
|
|
|
|
|
2022-08-30 00:42:50 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-07-12 02:55:03 +02:00
|
|
|
function getLineId(line : LineType) : string {
|
|
|
|
return sprintf("%s-%s-%s", line.sessionid, line.windowid, line.lineid);
|
|
|
|
}
|
|
|
|
|
2022-08-24 22:19:59 +02:00
|
|
|
function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string) : string {
|
|
|
|
if (isBlank(ownerName) && isBlank(name)) {
|
|
|
|
return remoteRef;
|
2022-08-11 03:35:18 +02:00
|
|
|
}
|
2022-08-24 22:19:59 +02:00
|
|
|
if (!isBlank(ownerName) && isBlank(name)) {
|
|
|
|
return ownerName + ":" + remoteRef;
|
2022-08-11 03:35:18 +02:00
|
|
|
}
|
2022-08-24 22:19:59 +02:00
|
|
|
if (isBlank(ownerName) && !isBlank(name)) {
|
|
|
|
return remoteRef + ":" + name;
|
2022-08-11 03:35:18 +02:00
|
|
|
}
|
2022-08-24 22:19:59 +02:00
|
|
|
return ownerName + ":" + remoteRef + ":" + name;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getRemoteStr(rptr : RemotePtrType) : string {
|
|
|
|
if (rptr == null || isBlank(rptr.remoteid)) {
|
|
|
|
return "(invalid remote)";
|
2022-08-17 22:06:47 +02:00
|
|
|
}
|
2022-08-24 22:19:59 +02:00
|
|
|
let username = (isBlank(rptr.ownerid) ? null : GlobalModel.resolveUserIdToName(rptr.ownerid));
|
|
|
|
let remoteRef = GlobalModel.resolveRemoteIdToRef(rptr.remoteid);
|
|
|
|
let fullRef = makeFullRemoteRef(username, remoteRef, rptr.name);
|
|
|
|
return fullRef;
|
2022-08-11 03:35:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function replaceHomePath(path : string, homeDir : string) : string {
|
|
|
|
if (path == homeDir) {
|
|
|
|
return "~";
|
|
|
|
}
|
|
|
|
if (path.startsWith(homeDir + "/")) {
|
|
|
|
return "~" + path.substr(homeDir.length);
|
|
|
|
}
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2022-11-29 03:08:19 +01:00
|
|
|
function getCwdStr(remote : RemoteType, state : FeStateType) : string {
|
2022-08-11 03:35:18 +02:00
|
|
|
if ((state == null || state.cwd == null) && remote != null) {
|
|
|
|
return "~";
|
|
|
|
}
|
|
|
|
let cwd = "(unknown)";
|
|
|
|
if (state && state.cwd) {
|
|
|
|
cwd = state.cwd;
|
|
|
|
}
|
|
|
|
if (remote && remote.remotevars.home) {
|
2022-11-29 03:08:19 +01:00
|
|
|
cwd = replaceHomePath(cwd, remote.remotevars.cwd)
|
2022-08-11 03:35:18 +02:00
|
|
|
}
|
|
|
|
return cwd;
|
|
|
|
}
|
|
|
|
|
2022-06-18 02:54:14 +02:00
|
|
|
function getLineDateStr(ts : number) : string {
|
|
|
|
let lineDate = new Date(ts);
|
|
|
|
let nowDate = new Date();
|
|
|
|
if (nowDate.getFullYear() != lineDate.getFullYear()) {
|
|
|
|
return dayjs(lineDate).format("ddd L LTS");
|
|
|
|
}
|
|
|
|
else if (nowDate.getMonth() != lineDate.getMonth() || nowDate.getDate() != lineDate.getDate()) {
|
|
|
|
let yesterdayDate = (new Date());
|
|
|
|
yesterdayDate.setDate(yesterdayDate.getDate()-1);
|
|
|
|
if (yesterdayDate.getMonth() == lineDate.getMonth() && yesterdayDate.getDate() == lineDate.getDate()) {
|
|
|
|
return "Yesterday " + dayjs(lineDate).format("LTS");;
|
|
|
|
}
|
|
|
|
return dayjs(lineDate).format("ddd L LTS");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return dayjs(ts).format("LTS");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-28 00:25:58 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class LineAvatar extends React.Component<{line : LineType, cmd : Cmd}, {}> {
|
|
|
|
render() {
|
|
|
|
let {line, cmd} = this.props;
|
|
|
|
let lineNumStr = (line.linenumtemp ? "~" : "") + String(line.linenum);
|
|
|
|
let status = (cmd != null ? cmd.getStatus() : "done");
|
|
|
|
let rtnstate = (cmd != null ? cmd.getRtnState() : false);
|
|
|
|
return (
|
|
|
|
<div className={cn("avatar", "num-"+lineNumStr.length, "status-" + status, {"ephemeral": line.ephemeral}, {"rtnstate": rtnstate})}>
|
|
|
|
{lineNumStr}
|
|
|
|
<If condition={status == "hangup" || status == "error"}>
|
|
|
|
<i className="fa fa-exclamation-triangle status-icon"/>
|
|
|
|
</If>
|
|
|
|
<If condition={status == "detached"}>
|
|
|
|
<i className="fa fa-refresh status-icon"/>
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-08 02:25:35 +02:00
|
|
|
@mobxReact.observer
|
2022-07-13 09:44:19 +02:00
|
|
|
class LineText extends React.Component<{sw : ScreenWindow, line : LineType}, {}> {
|
2022-12-06 19:24:44 +01:00
|
|
|
@boundMethod
|
|
|
|
clickHandler() {
|
|
|
|
let {line} = this.props;
|
|
|
|
GlobalCommandRunner.swSelectLine(String(line.linenum));
|
|
|
|
}
|
|
|
|
|
2022-06-08 02:25:35 +02:00
|
|
|
render() {
|
2022-10-11 02:29:59 +02:00
|
|
|
let {sw, line} = this.props;
|
2022-06-18 02:54:14 +02:00
|
|
|
let formattedTime = getLineDateStr(line.ts);
|
2022-10-11 02:29:59 +02:00
|
|
|
let isSelected = (sw.selectedLine.get() == line.linenum);
|
2022-10-11 08:44:04 +02:00
|
|
|
let isFocused = (sw.focusType.get() == "cmd");
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-12-06 19:24:44 +01:00
|
|
|
<div className="line line-text" data-lineid={line.lineid} data-linenum={line.linenum} data-windowid={line.windowid} onClick={this.clickHandler}>
|
2022-10-11 02:29:59 +02:00
|
|
|
<div className={cn("focus-indicator", {"selected": isSelected}, {"active": isSelected && isFocused})}/>
|
2022-06-08 02:25:35 +02:00
|
|
|
<div className="avatar">
|
2022-06-17 01:34:46 +02:00
|
|
|
S
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
<div className="line-content">
|
|
|
|
<div className="meta">
|
|
|
|
<div className="user">{line.userid}</div>
|
2022-06-18 02:54:14 +02:00
|
|
|
<div className="ts">{formattedTime}</div>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
<div className="text">
|
|
|
|
{line.text}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-24 22:19:59 +02:00
|
|
|
@mobxReact.observer
|
2022-11-29 03:08:19 +01:00
|
|
|
class Prompt extends React.Component<{rptr : RemotePtrType, festate : FeStateType}, {}> {
|
2022-08-24 22:19:59 +02:00
|
|
|
render() {
|
|
|
|
let remote : RemoteType = null;
|
|
|
|
if (this.props.rptr && !isBlank(this.props.rptr.remoteid)) {
|
|
|
|
remote = GlobalModel.getRemote(this.props.rptr.remoteid);
|
|
|
|
}
|
|
|
|
let remoteStr = getRemoteStr(this.props.rptr);
|
2022-11-29 03:08:19 +01:00
|
|
|
let cwd = getCwdStr(remote, this.props.festate);
|
2022-08-24 22:19:59 +02:00
|
|
|
let isRoot = false;
|
|
|
|
if (remote && remote.remotevars) {
|
|
|
|
if (remote.remotevars["sudo"] || remote.remotevars["bestuser"] == "root") {
|
|
|
|
isRoot = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let className = (isRoot ? "term-bright-red" : "term-bright-green");
|
|
|
|
return (
|
|
|
|
<span className="term-bright-green">[{remoteStr}] {cwd} {isRoot ? "#" : "$"}</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-08 02:25:35 +02:00
|
|
|
@mobxReact.observer
|
2022-10-21 01:15:21 +02:00
|
|
|
class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : HeightChangeCallbackType}, {}> {
|
2022-11-23 07:57:35 +01:00
|
|
|
termLoaded : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "linecmd-term-loaded"});
|
2022-08-13 03:34:56 +02:00
|
|
|
lineRef : React.RefObject<any> = React.createRef();
|
2022-11-23 07:57:35 +01:00
|
|
|
rtnStateDiff : mobx.IObservableValue<string> = mobx.observable.box(null, {name: "linecmd-rtn-state-diff"});
|
2022-10-27 09:36:03 +02:00
|
|
|
rtnStateDiffFetched : boolean = false;
|
2022-07-13 09:44:19 +02:00
|
|
|
|
2022-06-16 09:31:54 +02:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2022-10-08 03:25:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
checkLoad() : void {
|
|
|
|
let {line, staticRender, visible} = this.props;
|
|
|
|
if (staticRender) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let vis = visible && visible.get();
|
2022-09-08 22:44:24 +02:00
|
|
|
let curVis = this.termLoaded.get();
|
|
|
|
if (vis && !curVis) {
|
2022-08-13 03:34:56 +02:00
|
|
|
this.loadTerminal();
|
|
|
|
}
|
2022-09-08 22:44:24 +02:00
|
|
|
else if (!vis && curVis) {
|
2022-10-08 03:25:47 +02:00
|
|
|
this.unloadTerminal(false);
|
2022-08-13 03:34:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-27 09:36:03 +02:00
|
|
|
checkStateDiffLoad() : void {
|
|
|
|
let {line, staticRender, visible} = this.props;
|
|
|
|
if (staticRender) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!visible) {
|
|
|
|
if (this.rtnStateDiffFetched) {
|
|
|
|
this.rtnStateDiffFetched = false;
|
|
|
|
this.setRtnStateDiff(null);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let cmd = GlobalModel.getCmd(line);
|
|
|
|
if (cmd == null || !cmd.getRtnState() || this.rtnStateDiffFetched) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (cmd.getStatus() != "done") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.fetchRtnStateDiff();
|
|
|
|
}
|
|
|
|
|
2022-08-13 03:34:56 +02:00
|
|
|
loadTerminal() : void {
|
2022-07-13 09:44:19 +02:00
|
|
|
let {sw, line} = this.props;
|
2022-07-11 23:43:18 +02:00
|
|
|
let model = GlobalModel;
|
2022-07-12 02:55:03 +02:00
|
|
|
let cmd = model.getCmd(line);
|
2022-08-13 03:34:56 +02:00
|
|
|
if (cmd == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let termId = "term-" + getLineId(line);
|
|
|
|
let termElem = document.getElementById(termId);
|
|
|
|
if (termElem == null) {
|
|
|
|
console.log("cannot load terminal, no term elem found", termId);
|
|
|
|
return;
|
|
|
|
}
|
2022-10-11 10:11:36 +02:00
|
|
|
sw.connectElem(termElem, line, cmd, this.props.width);
|
2022-08-13 03:34:56 +02:00
|
|
|
mobx.action(() => this.termLoaded.set(true))();
|
|
|
|
}
|
2022-09-08 22:44:24 +02:00
|
|
|
|
2022-10-08 03:25:47 +02:00
|
|
|
unloadTerminal(unmount : boolean) : void {
|
2022-09-08 22:44:24 +02:00
|
|
|
let {sw, line} = this.props;
|
|
|
|
sw.disconnectElem(line.cmdid);
|
2022-10-08 03:25:47 +02:00
|
|
|
if (!unmount) {
|
|
|
|
mobx.action(() => this.termLoaded.set(false))();
|
|
|
|
let termId = "term-" + getLineId(line);
|
|
|
|
let termElem = document.getElementById(termId);
|
|
|
|
if (termElem != null) {
|
|
|
|
termElem.replaceChildren();
|
|
|
|
}
|
2022-09-08 22:44:24 +02:00
|
|
|
}
|
|
|
|
}
|
2022-10-08 03:25:47 +02:00
|
|
|
|
2022-10-27 09:36:03 +02:00
|
|
|
fetchRtnStateDiff() : void {
|
|
|
|
if (this.rtnStateDiffFetched) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let {line} = this.props;
|
|
|
|
this.rtnStateDiffFetched = true;
|
|
|
|
let usp = new URLSearchParams({sessionid: line.sessionid, cmdid: line.cmdid});
|
2022-12-28 22:47:51 +01:00
|
|
|
let url = GlobalModel.getBaseHostPort() + "/api/rtnstate?" + usp.toString();
|
2022-12-21 01:22:05 +01:00
|
|
|
let fetchHeaders = GlobalModel.getFetchHeaders();
|
|
|
|
fetch(url, {headers: fetchHeaders}).then((resp) => {
|
2022-10-27 09:36:03 +02:00
|
|
|
if (!resp.ok) {
|
|
|
|
throw new Error(sprintf("Bad fetch response for /api/rtnstate: %d %s", resp.status, resp.statusText));
|
|
|
|
}
|
|
|
|
return resp.text();
|
|
|
|
}).then((text) => {
|
|
|
|
this.setRtnStateDiff(text ?? "");
|
|
|
|
}).catch((err) => {
|
|
|
|
this.setRtnStateDiff("ERROR " + err.toString())
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
setRtnStateDiff(val : string) : void {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.rtnStateDiff.set(val);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-08-13 03:34:56 +02:00
|
|
|
componentDidMount() {
|
2022-12-25 22:03:34 +01:00
|
|
|
this.componentDidUpdate(null, null, null);
|
2022-06-15 01:02:20 +02:00
|
|
|
}
|
|
|
|
|
2022-07-12 07:43:58 +02:00
|
|
|
componentWillUnmount() {
|
2022-08-13 03:34:56 +02:00
|
|
|
if (this.termLoaded.get()) {
|
2022-10-08 03:25:47 +02:00
|
|
|
this.unloadTerminal(true);
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-08 03:25:47 +02:00
|
|
|
// FIXME
|
2022-06-18 02:54:14 +02:00
|
|
|
scrollIntoView() {
|
|
|
|
let lineElem = document.getElementById("line-" + getLineId(this.props.line));
|
|
|
|
lineElem.scrollIntoView({block: "end"});
|
2022-06-08 02:25:35 +02:00
|
|
|
}
|
2022-06-13 20:12:39 +02:00
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
doRefresh() {
|
2022-07-13 09:44:19 +02:00
|
|
|
let {sw, line} = this.props;
|
2022-07-12 02:55:03 +02:00
|
|
|
let model = GlobalModel;
|
2022-08-13 03:34:56 +02:00
|
|
|
let termWrap = sw.getTermWrap(line.cmdid);
|
|
|
|
if (termWrap != null) {
|
|
|
|
termWrap.reloadTerminal(500);
|
2022-07-12 02:55:03 +02:00
|
|
|
}
|
2022-06-15 01:02:20 +02:00
|
|
|
}
|
|
|
|
|
2022-07-11 23:43:18 +02:00
|
|
|
renderCmdText(cmd : Cmd, remote : RemoteType) : any {
|
2022-07-07 22:27:44 +02:00
|
|
|
if (cmd == null) {
|
|
|
|
return (
|
|
|
|
<div className="metapart-mono cmdtext">
|
|
|
|
<span className="term-bright-green">(cmd not found)</span>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2022-08-24 22:19:59 +02:00
|
|
|
let remoteStr = getRemoteStr(cmd.remote);
|
2022-11-29 03:08:19 +01:00
|
|
|
let cwd = getCwdStr(remote, cmd.getRemoteFeState());
|
2022-07-07 22:27:44 +02:00
|
|
|
return (
|
|
|
|
<div className="metapart-mono cmdtext">
|
2022-11-29 03:08:19 +01:00
|
|
|
<Prompt rptr={cmd.remote} festate={cmd.getRemoteFeState()}/> {cmd.getSingleLineCmdText()}
|
2022-07-07 22:27:44 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2022-08-25 04:00:03 +02:00
|
|
|
|
|
|
|
@boundMethod
|
2022-08-25 21:12:56 +02:00
|
|
|
clickTermBlock(e : any) {
|
2022-08-25 04:00:03 +02:00
|
|
|
let {sw, line} = this.props;
|
|
|
|
let model = GlobalModel;
|
|
|
|
let termWrap = sw.getTermWrap(line.cmdid);
|
|
|
|
if (termWrap != null) {
|
2022-10-11 22:25:23 +02:00
|
|
|
termWrap.focusTerminal();
|
2022-08-25 04:00:03 +02:00
|
|
|
}
|
|
|
|
}
|
2022-10-08 03:25:47 +02:00
|
|
|
|
|
|
|
getSnapshotBeforeUpdate(prevProps, prevState) : {height : number} {
|
|
|
|
let elem = this.lineRef.current;
|
|
|
|
if (elem == null) {
|
|
|
|
return {height: 0};
|
|
|
|
}
|
|
|
|
return {height: elem.offsetHeight};
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps, prevState, snapshot : {height : number}) : void {
|
|
|
|
let {line} = this.props;
|
|
|
|
let curHeight = 0;
|
|
|
|
let elem = this.lineRef.current;
|
|
|
|
if (elem != null) {
|
|
|
|
curHeight = elem.offsetHeight;
|
|
|
|
}
|
2022-12-22 07:05:05 +01:00
|
|
|
if (snapshot == null) {
|
|
|
|
snapshot = {height: 0};
|
|
|
|
}
|
2022-10-08 03:25:47 +02:00
|
|
|
if (snapshot.height != curHeight && this.props.onHeightChange != null) {
|
|
|
|
this.props.onHeightChange(line.linenum, curHeight, snapshot.height);
|
|
|
|
}
|
|
|
|
this.checkLoad();
|
2022-10-27 09:36:03 +02:00
|
|
|
this.checkStateDiffLoad();
|
2022-10-08 03:25:47 +02:00
|
|
|
}
|
2022-10-28 00:25:58 +02:00
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
handleClick() {
|
|
|
|
let {line} = this.props;
|
|
|
|
GlobalCommandRunner.swSelectLine(String(line.linenum), "cmd");
|
|
|
|
}
|
2022-12-06 07:59:31 +01:00
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
clickStar() {
|
|
|
|
let {line} = this.props;
|
|
|
|
if (!line.star || line.star == 0) {
|
|
|
|
GlobalCommandRunner.lineStar(line.lineid, 1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
GlobalCommandRunner.lineStar(line.lineid, 0);
|
|
|
|
}
|
|
|
|
}
|
2022-06-08 02:25:35 +02:00
|
|
|
|
|
|
|
render() {
|
2022-10-08 03:25:47 +02:00
|
|
|
let {sw, line, width, staticRender, visible} = this.props;
|
2022-07-12 02:55:03 +02:00
|
|
|
let model = GlobalModel;
|
2022-08-17 00:59:28 +02:00
|
|
|
let lineid = line.lineid;
|
2022-10-11 10:11:36 +02:00
|
|
|
let isVisible = visible.get();
|
2022-06-18 02:54:14 +02:00
|
|
|
let formattedTime = getLineDateStr(line.ts);
|
2022-07-12 02:55:03 +02:00
|
|
|
let cmd = model.getCmd(line);
|
|
|
|
if (cmd == null) {
|
2022-08-13 03:34:56 +02:00
|
|
|
return (
|
2022-10-08 03:25:47 +02:00
|
|
|
<div className="line line-invalid" id={"line-" + getLineId(line)} ref={this.lineRef} data-lineid={line.lineid} data-linenum={line.linenum} data-windowid={line.windowid}>
|
2022-08-13 03:34:56 +02:00
|
|
|
[cmd not found '{line.cmdid}']
|
|
|
|
</div>
|
|
|
|
);
|
2022-07-07 22:27:44 +02:00
|
|
|
}
|
2022-07-13 09:44:19 +02:00
|
|
|
let termLoaded = this.termLoaded.get();
|
2022-08-13 03:34:56 +02:00
|
|
|
let usedRows = sw.getUsedRows(cmd, width);
|
2022-10-07 00:17:48 +02:00
|
|
|
let termHeight = termHeightFromRows(usedRows);
|
2022-07-12 02:55:03 +02:00
|
|
|
let remote = model.getRemote(cmd.remoteId);
|
|
|
|
let status = cmd.getStatus();
|
|
|
|
let termOpts = cmd.getTermOpts();
|
2022-09-21 01:51:42 +02:00
|
|
|
let lineNumStr = (line.linenumtemp ? "~" : "") + String(line.linenum);
|
2022-10-07 03:35:01 +02:00
|
|
|
let isSelected = (sw.selectedLine.get() == line.linenum);
|
2022-10-11 22:25:23 +02:00
|
|
|
let isPhysicalFocused = sw.getIsFocused(line.linenum);
|
2022-10-11 10:11:36 +02:00
|
|
|
let swFocusType = sw.focusType.get();
|
|
|
|
let isFocused = isPhysicalFocused && (swFocusType == "cmd" || swFocusType == "cmd-fg");
|
2022-10-12 08:13:34 +02:00
|
|
|
let isFgFocused = isPhysicalFocused && swFocusType == "cmd-fg";
|
2022-10-08 03:25:47 +02:00
|
|
|
let isStatic = staticRender;
|
2022-11-23 07:57:35 +01:00
|
|
|
let rsdiff = this.rtnStateDiff.get();
|
|
|
|
// console.log("render", "#" + line.linenum, termHeight, usedRows, cmd.getStatus(), (this.rtnStateDiff.get() != null), (!cmd.isRunning() ? "cmd-done" : "running"));
|
|
|
|
let mainDivCn = cn(
|
|
|
|
"line",
|
|
|
|
"line-cmd",
|
|
|
|
{"focus": isFocused},
|
|
|
|
{"cmd-done": !cmd.isRunning()},
|
|
|
|
{"has-rtnstate": cmd.getRtnState()},
|
|
|
|
);
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-11-23 07:57:35 +01:00
|
|
|
<div className={mainDivCn} id={"line-" + getLineId(line)}
|
|
|
|
ref={this.lineRef} onClick={this.handleClick}
|
|
|
|
data-lineid={line.lineid} data-linenum={line.linenum} data-windowid={line.windowid} data-cmdid={line.cmdid}>
|
2022-10-12 08:13:34 +02:00
|
|
|
<div className={cn("focus-indicator", {"selected": isSelected}, {"active": isSelected && isFocused}, {"fg-focus": isFgFocused})}/>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className="line-header">
|
2022-10-28 00:25:58 +02:00
|
|
|
<LineAvatar line={line} cmd={cmd}/>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className="meta-wrap">
|
|
|
|
<div className="meta">
|
|
|
|
<div className="user" style={{display: "none"}}>{line.userid}</div>
|
|
|
|
<div className="ts">{formattedTime}</div>
|
|
|
|
</div>
|
|
|
|
<div className="meta">
|
|
|
|
<div className="metapart-mono" style={{display: "none"}}>
|
|
|
|
{line.cmdid}
|
|
|
|
({termOpts.rows}x{termOpts.cols})
|
|
|
|
</div>
|
|
|
|
{this.renderCmdText(cmd, remote)}
|
2022-06-18 02:54:14 +02:00
|
|
|
</div>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
2022-12-06 07:59:31 +01:00
|
|
|
<div className="flex-spacer"/>
|
|
|
|
<div className={cn("line-star", {"active": line.star > 0})} onClick={this.clickStar}>
|
|
|
|
<If condition={!line.star || line.star == 0}>
|
|
|
|
<i className="fa fa-star-o"/>
|
|
|
|
</If>
|
|
|
|
<If condition={line.star > 0}>
|
|
|
|
<i className="fa fa-star"/>
|
|
|
|
</If>
|
|
|
|
</div>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
2022-10-27 09:36:03 +02:00
|
|
|
<div className={cn("terminal-wrapper", {"focus": isFocused}, {"cmd-done": !cmd.isRunning()}, {"zero-height": (termHeight == 0)})}>
|
2022-08-25 04:00:03 +02:00
|
|
|
<If condition={!isFocused}>
|
|
|
|
<div className="term-block" onClick={this.clickTermBlock}></div>
|
|
|
|
</If>
|
2022-10-14 01:09:26 +02:00
|
|
|
<div className="terminal-connectelem" id={"term-" + getLineId(line)} data-cmdid={line.cmdid} style={{height: termHeight}}></div>
|
2022-12-22 07:05:05 +01:00
|
|
|
<If condition={!termLoaded}><div className="terminal-loading-message">(loading)</div></If>
|
2022-07-14 09:54:31 +02:00
|
|
|
</div>
|
2022-11-23 07:57:35 +01:00
|
|
|
<If condition={cmd.getRtnState()}>
|
|
|
|
<div className="cmd-rtnstate" style={{visibility: ((cmd.getStatus() == "done") ? "visible" : "hidden")}}>
|
|
|
|
<If condition={rsdiff == null || rsdiff == ""}>
|
2022-10-27 09:36:03 +02:00
|
|
|
<div className="cmd-rtnstate-label">state unchanged</div>
|
|
|
|
<div className="cmd-rtnstate-sep"></div>
|
|
|
|
</If>
|
2022-11-23 07:57:35 +01:00
|
|
|
<If condition={rsdiff != null && rsdiff != ""}>
|
2022-10-27 09:36:03 +02:00
|
|
|
<div className="cmd-rtnstate-label">new state</div>
|
|
|
|
<div className="cmd-rtnstate-sep"></div>
|
|
|
|
<div className="cmd-rtnstate-diff">{this.rtnStateDiff.get()}</div>
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-10-08 03:25:47 +02:00
|
|
|
class Line extends React.Component<{sw : ScreenWindow, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : HeightChangeCallbackType}, {}> {
|
2022-06-08 02:25:35 +02:00
|
|
|
render() {
|
|
|
|
let line = this.props.line;
|
2022-12-28 07:55:42 +01:00
|
|
|
if (line.archived) {
|
2022-12-22 07:05:05 +01:00
|
|
|
return null;
|
|
|
|
}
|
2022-06-08 02:25:35 +02:00
|
|
|
if (line.linetype == "text") {
|
|
|
|
return <LineText {...this.props}/>;
|
|
|
|
}
|
|
|
|
if (line.linetype == "cmd") {
|
|
|
|
return <LineCmd {...this.props}/>;
|
|
|
|
}
|
|
|
|
return <div className="line line-invalid">[invalid line type '{line.linetype}']</div>;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-08-25 04:00:03 +02:00
|
|
|
class TextAreaInput extends React.Component<{}, {}> {
|
2022-08-12 20:44:29 +02:00
|
|
|
lastTab : boolean = false;
|
|
|
|
lastHistoryUpDown : boolean = false;
|
2022-08-11 03:35:18 +02:00
|
|
|
lastTabCurLine : mobx.IObservableValue<string> = mobx.observable.box(null);
|
2022-10-11 08:44:04 +02:00
|
|
|
lastFocusType : string = null;
|
|
|
|
mainInputRef : React.RefObject<any>;
|
|
|
|
historyInputRef : React.RefObject<any>;
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.mainInputRef = React.createRef();
|
|
|
|
this.historyInputRef = React.createRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
setFocus() : void {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
if (inputModel.historyShow.get()) {
|
|
|
|
this.historyInputRef.current.focus();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.mainInputRef.current.focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-24 02:27:12 +02:00
|
|
|
componentDidMount() {
|
2022-10-11 08:44:04 +02:00
|
|
|
let activeSW = GlobalModel.getActiveSW();
|
|
|
|
if (activeSW != null) {
|
|
|
|
let focusType = activeSW.focusType.get();
|
|
|
|
if (focusType == "input") {
|
|
|
|
this.setFocus();
|
|
|
|
}
|
|
|
|
this.lastFocusType = focusType;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate() {
|
|
|
|
let activeSW = GlobalModel.getActiveSW();
|
|
|
|
if (activeSW != null) {
|
|
|
|
let focusType = activeSW.focusType.get();
|
|
|
|
if (this.lastFocusType != focusType && focusType == "input") {
|
|
|
|
this.setFocus();
|
|
|
|
}
|
|
|
|
this.lastFocusType = focusType;
|
2022-08-24 02:27:12 +02:00
|
|
|
}
|
2022-11-11 03:52:38 +01:00
|
|
|
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))();
|
|
|
|
}
|
2022-08-24 02:27:12 +02:00
|
|
|
}
|
|
|
|
|
2022-08-12 20:44:29 +02:00
|
|
|
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};
|
|
|
|
}
|
2022-06-08 02:25:35 +02:00
|
|
|
|
|
|
|
@mobx.action @boundMethod
|
|
|
|
onKeyDown(e : any) {
|
|
|
|
mobx.action(() => {
|
2022-10-14 03:58:21 +02:00
|
|
|
if (isModKeyPress(e)) {
|
2022-08-12 20:44:29 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-07-11 23:43:18 +02:00
|
|
|
let model = GlobalModel;
|
2022-08-11 03:35:18 +02:00
|
|
|
let inputModel = model.inputModel;
|
2022-07-12 02:55:03 +02:00
|
|
|
let win = model.getActiveWindow();
|
2022-06-08 02:25:35 +02:00
|
|
|
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
|
2022-08-11 03:35:18 +02:00
|
|
|
let curLine = inputModel.getCurLine();
|
2022-08-12 20:44:29 +02:00
|
|
|
|
|
|
|
let lastTab = this.lastTab;
|
|
|
|
this.lastTab = (e.code == "Tab");
|
|
|
|
let lastHist = this.lastHistoryUpDown;
|
|
|
|
this.lastHistoryUpDown = false;
|
|
|
|
|
2022-08-11 03:35:18 +02:00
|
|
|
if (e.code == "Tab") {
|
|
|
|
e.preventDefault();
|
|
|
|
if (lastTab) {
|
2022-11-23 23:34:05 +01:00
|
|
|
GlobalModel.submitCommand("_compgen", null, [curLine], {"comppos": String(curLine.length), "compshow": "1", "nohist": "1"}, true);
|
2022-08-11 03:35:18 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
2022-11-23 23:34:05 +01:00
|
|
|
GlobalModel.submitCommand("_compgen", null, [curLine], {"comppos": String(curLine.length), "nohist": "1"}, true);
|
2022-08-11 03:35:18 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2022-08-12 20:44:29 +02:00
|
|
|
if (e.code == "Enter") {
|
2022-06-08 02:25:35 +02:00
|
|
|
e.preventDefault();
|
2022-08-12 20:44:29 +02:00
|
|
|
if (!ctrlMod) {
|
2022-08-25 21:12:56 +02:00
|
|
|
setTimeout(() => GlobalModel.inputModel.uiSubmitCommand(), 0);
|
2022-08-12 20:44:29 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end");
|
|
|
|
GlobalModel.inputModel.setCurLine(e.target.value);
|
2022-06-08 02:25:35 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-08-11 03:35:18 +02:00
|
|
|
if (e.code == "Escape") {
|
2022-08-09 01:22:36 +02:00
|
|
|
e.preventDefault();
|
2022-12-25 22:03:34 +01:00
|
|
|
e.stopPropagation();
|
2022-10-23 08:54:46 +02:00
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
inputModel.toggleInfoMsg();
|
|
|
|
if (inputModel.inputMode.get() != null) {
|
|
|
|
inputModel.resetInputMode();
|
|
|
|
}
|
2022-08-11 03:35:18 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (e.code == "KeyC" && e.getModifierState("Control")) {
|
|
|
|
e.preventDefault();
|
2022-08-31 08:12:37 +02:00
|
|
|
inputModel.resetInput();
|
2022-08-09 01:22:36 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-08-31 02:05:35 +02:00
|
|
|
if (e.code == "KeyR" && e.getModifierState("Control")) {
|
|
|
|
e.preventDefault();
|
2022-08-31 08:12:37 +02:00
|
|
|
inputModel.openHistory();
|
2022-08-31 02:05:35 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-08-12 20:44:29 +02:00
|
|
|
if (e.code == "ArrowUp" || e.code == "ArrowDown") {
|
2022-08-31 08:12:37 +02:00
|
|
|
if (!inputModel.isHistoryLoaded()) {
|
|
|
|
if (e.code == "ArrowUp") {
|
2022-08-31 22:29:59 +02:00
|
|
|
this.lastHistoryUpDown = true;
|
|
|
|
inputModel.loadHistory(false, 1, "window");
|
2022-08-31 08:12:37 +02:00
|
|
|
}
|
2022-08-31 02:05:35 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-08-31 08:12:37 +02:00
|
|
|
// invisible history movement
|
2022-08-12 20:44:29 +02:00
|
|
|
let linePos = this.getLinePos(e.target);
|
|
|
|
if (e.code == "ArrowUp") {
|
|
|
|
if (!lastHist && linePos.linePos > 1) {
|
|
|
|
// regular arrow
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
e.preventDefault();
|
2022-08-31 08:12:37 +02:00
|
|
|
inputModel.moveHistorySelection(1);
|
2022-08-12 20:44:29 +02:00
|
|
|
this.lastHistoryUpDown = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (e.code == "ArrowDown") {
|
|
|
|
if (!lastHist && linePos.linePos < linePos.numLines) {
|
|
|
|
// regular arrow
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
e.preventDefault();
|
2022-08-31 08:12:37 +02:00
|
|
|
inputModel.moveHistorySelection(-1);
|
2022-08-12 20:44:29 +02:00
|
|
|
this.lastHistoryUpDown = true;
|
|
|
|
return;
|
|
|
|
}
|
2022-06-21 01:06:37 +02:00
|
|
|
}
|
2022-08-29 22:54:11 +02:00
|
|
|
if (e.code == "PageUp" || e.code == "PageDown") {
|
|
|
|
e.preventDefault();
|
2022-08-31 02:05:35 +02:00
|
|
|
let infoScroll = inputModel.hasScrollingInfoMsg();
|
2022-08-29 22:54:11 +02:00
|
|
|
if (infoScroll) {
|
2022-08-30 00:42:50 +02:00
|
|
|
let div = document.querySelector(".cmd-input-info");
|
|
|
|
let amt = pageSize(div);
|
|
|
|
scrollDiv(div, (e.code == "PageUp" ? -amt : amt));
|
2022-08-29 22:54:11 +02:00
|
|
|
}
|
|
|
|
}
|
2022-06-13 20:12:39 +02:00
|
|
|
// console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e);
|
2022-06-08 02:25:35 +02:00
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
onChange(e : any) {
|
|
|
|
mobx.action(() => {
|
2022-08-11 03:35:18 +02:00
|
|
|
GlobalModel.inputModel.setCurLine(e.target.value);
|
2022-06-08 02:25:35 +02:00
|
|
|
})();
|
|
|
|
}
|
2022-06-13 20:12:39 +02:00
|
|
|
|
2022-08-31 08:12:37 +02:00
|
|
|
@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;
|
|
|
|
}
|
2022-10-23 08:54:46 +02:00
|
|
|
if (e.code == "KeyG" && e.getModifierState("Control")) {
|
|
|
|
e.preventDefault();
|
|
|
|
inputModel.resetInput();
|
|
|
|
return;
|
|
|
|
}
|
2022-08-31 08:12:37 +02:00
|
|
|
if (e.code == "KeyC" && e.getModifierState("Control")) {
|
|
|
|
e.preventDefault();
|
|
|
|
inputModel.resetInput();
|
|
|
|
return;
|
|
|
|
}
|
2022-08-31 22:29:59 +02:00
|
|
|
if (e.code == "KeyM" && (e.getModifierState("Meta") || e.getModifierState("Control"))) {
|
2022-08-31 09:02:16 +02:00
|
|
|
e.preventDefault();
|
|
|
|
let opts = mobx.toJS(inputModel.historyQueryOpts.get());
|
|
|
|
opts.includeMeta = !opts.includeMeta;
|
|
|
|
inputModel.setHistoryQueryOpts(opts);
|
2022-08-31 21:00:53 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-08-31 22:29:59 +02:00
|
|
|
if (e.code == "KeyR" && ((e.getModifierState("Meta") || e.getModifierState("Control")) && !e.getModifierState("Shift"))) {
|
2022-08-31 21:00:53 +02:00
|
|
|
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;
|
2022-08-31 09:02:16 +02:00
|
|
|
}
|
2022-08-31 22:29:59 +02:00
|
|
|
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;
|
|
|
|
}
|
2022-08-31 08:12:37 +02:00
|
|
|
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(() => {
|
2022-08-31 09:02:16 +02:00
|
|
|
let opts = mobx.toJS(inputModel.historyQueryOpts.get());
|
|
|
|
opts.queryStr = e.target.value;
|
|
|
|
inputModel.setHistoryQueryOpts(opts);
|
2022-08-31 08:12:37 +02:00
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
handleMainFocus(e : any) {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
if (inputModel.historyShow.get()) {
|
|
|
|
e.preventDefault();
|
2022-10-11 22:25:23 +02:00
|
|
|
if (this.historyInputRef.current != null) {
|
|
|
|
this.historyInputRef.current.focus();
|
|
|
|
}
|
2022-10-11 08:44:04 +02:00
|
|
|
return;
|
2022-08-31 08:12:37 +02:00
|
|
|
}
|
2022-10-11 08:44:04 +02:00
|
|
|
inputModel.setPhysicalInputFocused(true);
|
2022-10-06 23:00:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
handleMainBlur(e : any) {
|
2022-10-11 08:44:04 +02:00
|
|
|
if (document.activeElement == this.mainInputRef.current) {
|
|
|
|
return;
|
|
|
|
}
|
2022-10-06 23:00:24 +02:00
|
|
|
GlobalModel.inputModel.setPhysicalInputFocused(false);
|
2022-08-31 08:12:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
handleHistoryFocus(e : any) {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
if (!inputModel.historyShow.get()) {
|
|
|
|
e.preventDefault();
|
2022-10-11 22:25:23 +02:00
|
|
|
if (this.mainInputRef.current != null) {
|
|
|
|
this.mainInputRef.current.focus();
|
|
|
|
}
|
2022-10-11 08:44:04 +02:00
|
|
|
return;
|
2022-08-31 08:12:37 +02:00
|
|
|
}
|
2022-10-11 08:44:04 +02:00
|
|
|
inputModel.setPhysicalInputFocused(true);
|
2022-10-06 23:00:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
handleHistoryBlur(e : any) {
|
2022-10-11 08:44:04 +02:00
|
|
|
if (document.activeElement == this.historyInputRef.current) {
|
|
|
|
return;
|
|
|
|
}
|
2022-10-06 23:00:24 +02:00
|
|
|
GlobalModel.inputModel.setPhysicalInputFocused(false);
|
2022-08-31 08:12:37 +02:00
|
|
|
}
|
|
|
|
|
2022-08-25 04:00:03 +02:00
|
|
|
render() {
|
|
|
|
let model = GlobalModel;
|
|
|
|
let inputModel = model.inputModel;
|
|
|
|
let curLine = inputModel.getCurLine();
|
2022-11-11 03:52:38 +01:00
|
|
|
let fcp = inputModel.forceCursorPos.get(); // for reaction
|
2022-08-25 04:00:03 +02:00
|
|
|
let numLines = curLine.split("\n").length;
|
|
|
|
let displayLines = numLines;
|
|
|
|
if (displayLines > 5) {
|
|
|
|
displayLines = 5;
|
|
|
|
}
|
2022-08-31 08:12:37 +02:00
|
|
|
let disabled = inputModel.historyShow.get();
|
2022-08-31 21:00:53 +02:00
|
|
|
if (disabled) {
|
|
|
|
displayLines = 1;
|
|
|
|
}
|
2022-10-11 08:44:04 +02:00
|
|
|
let activeSW = GlobalModel.getActiveSW();
|
|
|
|
if (activeSW != null) {
|
|
|
|
activeSW.focusType.get(); // for reaction
|
|
|
|
}
|
2022-08-25 04:00:03 +02:00
|
|
|
return (
|
2022-08-31 08:12:37 +02:00
|
|
|
<div className="control cmd-input-control is-expanded">
|
2022-10-11 08:44:04 +02:00
|
|
|
<textarea ref={this.mainInputRef} spellCheck="false" id="main-cmd-input" onFocus={this.handleMainFocus} onBlur={this.handleMainBlur} rows={displayLines} value={curLine} onKeyDown={this.onKeyDown} onChange={this.onChange} className={cn("textarea", {"display-disabled": disabled})}></textarea>
|
|
|
|
<input ref={this.historyInputRef} spellCheck="false" className="history-input" type="text" onFocus={this.handleHistoryFocus} onKeyDown={this.onHistoryKeyDown} onChange={this.handleHistoryInput} value={inputModel.historyQueryOpts.get().queryStr}/>
|
2022-08-31 08:12:37 +02:00
|
|
|
</div>
|
2022-08-25 04:00:03 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-15 02:14:27 +02:00
|
|
|
@mobxReact.observer
|
2022-09-25 04:53:06 +02:00
|
|
|
class InfoRemoteShowAll extends React.Component<{}, {}> {
|
2022-10-01 00:42:10 +02:00
|
|
|
clickRow(remoteId : string) : void {
|
|
|
|
GlobalCommandRunner.showRemote(remoteId);
|
|
|
|
}
|
|
|
|
|
2022-09-25 04:53:06 +02:00
|
|
|
render() {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
let infoMsg = inputModel.infoMsg.get();
|
|
|
|
if (infoMsg == null || !infoMsg.remoteshowall) {
|
|
|
|
return null;
|
|
|
|
}
|
2022-10-01 00:42:10 +02:00
|
|
|
let remotes = GlobalModel.remotes ?? [];
|
|
|
|
let remote : RemoteType = null;
|
|
|
|
let idx : number = 0;
|
|
|
|
remotes = sortAndFilterRemotes(remotes);
|
2022-09-25 04:53:06 +02:00
|
|
|
return (
|
|
|
|
<div className="info-remote-showall">
|
2022-10-03 21:25:55 +02:00
|
|
|
<div className="info-title">
|
|
|
|
show all remotes
|
|
|
|
</div>
|
2022-10-01 00:42:10 +02:00
|
|
|
<table className="remotes-table">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th>status</th>
|
|
|
|
<th>id</th>
|
|
|
|
<th>alias</th>
|
|
|
|
<th>user@host</th>
|
|
|
|
<th>connectmode</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
<For each="remote" of={remotes}>
|
|
|
|
<tr key={remote.remoteid} onClick={() => this.clickRow(remote.remoteid)}>
|
|
|
|
<td className="status-cell">
|
2022-10-01 02:23:28 +02:00
|
|
|
<div><RemoteStatusLight remote={remote}/>{remote.status}</div>
|
2022-10-01 00:42:10 +02:00
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{remote.remoteid.substr(0, 8)}
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{isBlank(remote.remotealias) ? "-" : remote.remotealias}
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{remote.remotecanonicalname}
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{remote.connectmode}
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</For>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
2022-09-25 04:53:06 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
|
|
|
class InfoRemoteShow extends React.Component<{}, {}> {
|
2022-09-23 06:20:37 +02:00
|
|
|
getRemoteTypeStr(remote : RemoteType) : string {
|
|
|
|
let mshellStr = "";
|
|
|
|
if (!isBlank(remote.mshellversion)) {
|
|
|
|
mshellStr = "mshell=" + remote.mshellversion;
|
2022-09-15 02:14:27 +02:00
|
|
|
}
|
2022-09-23 06:20:37 +02:00
|
|
|
if (!isBlank(remote.uname)) {
|
|
|
|
if (mshellStr != "") {
|
|
|
|
mshellStr += " ";
|
|
|
|
}
|
|
|
|
mshellStr += "uname=\"" + remote.uname + "\"";
|
2022-09-15 02:14:27 +02:00
|
|
|
}
|
2022-09-23 06:20:37 +02:00
|
|
|
if (mshellStr == "") {
|
|
|
|
return remote.remotetype;
|
2022-09-15 09:18:20 +02:00
|
|
|
}
|
2022-09-23 06:20:37 +02:00
|
|
|
return remote.remotetype + " (" + mshellStr + ")";
|
2022-09-15 09:18:20 +02:00
|
|
|
}
|
2022-09-15 09:37:52 +02:00
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
connectRemote(remoteId : string) {
|
|
|
|
GlobalCommandRunner.connectRemote(remoteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
disconnectRemote(remoteId : string) {
|
|
|
|
GlobalCommandRunner.disconnectRemote(remoteId);
|
|
|
|
}
|
2022-09-16 02:10:02 +02:00
|
|
|
|
2022-09-27 08:24:15 +02:00
|
|
|
@boundMethod
|
|
|
|
installRemote(remoteId : string) {
|
|
|
|
GlobalCommandRunner.installRemote(remoteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
cancelInstall(remoteId : string) {
|
|
|
|
GlobalCommandRunner.installCancelRemote(remoteId);
|
|
|
|
}
|
|
|
|
|
2022-10-04 20:15:35 +02:00
|
|
|
@boundMethod
|
|
|
|
editRemote(remoteId : string) {
|
|
|
|
GlobalCommandRunner.openEditRemote(remoteId);
|
|
|
|
}
|
|
|
|
|
2022-09-16 02:10:02 +02:00
|
|
|
renderConnectButton(remote : RemoteType) : any {
|
2022-09-16 02:44:58 +02:00
|
|
|
if (remote.status == "connected" || remote.status == "connecting") {
|
2022-09-16 02:10:02 +02:00
|
|
|
return <div onClick={() => this.disconnectRemote(remote.remoteid)} className="text-button disconnect-button">[disconnect remote]</div>
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return <div onClick={() => this.connectRemote(remote.remoteid)} className="text-button connect-button">[connect remote]</div>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-04 20:15:35 +02:00
|
|
|
renderEditButton(remote : RemoteType) : any {
|
|
|
|
return <div onClick={() => this.editRemote(remote.remoteid)} className="text-button">[edit remote]</div>
|
|
|
|
}
|
|
|
|
|
2022-09-27 08:24:15 +02:00
|
|
|
renderInstallButton(remote : RemoteType) : any {
|
|
|
|
if (remote.status == "connected" || remote.status == "connecting") {
|
|
|
|
return "(must disconnect to install)";
|
|
|
|
}
|
|
|
|
if (remote.installstatus == "disconnected" || remote.installstatus == "error") {
|
2022-10-03 21:25:55 +02:00
|
|
|
return <div key="run-install" onClick={() => this.installRemote(remote.remoteid)} className="text-button connect-button">[run install]</div>
|
2022-09-27 08:24:15 +02:00
|
|
|
}
|
|
|
|
if (remote.installstatus == "connecting") {
|
2022-10-03 21:25:55 +02:00
|
|
|
return <div key="cancel-install" onClick={() => this.cancelInstall(remote.remoteid)} className="text-button disconnect-button">[cancel install]</div>
|
2022-09-27 08:24:15 +02:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
renderInstallStatus(remote : RemoteType) : any {
|
|
|
|
let statusStr : string = null;
|
|
|
|
if (remote.installstatus == "disconnected") {
|
|
|
|
if (remote.needsmshellupgrade) {
|
|
|
|
statusStr = "needs upgrade"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
statusStr = remote.installstatus;
|
|
|
|
}
|
|
|
|
if (statusStr == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
let installButton = this.renderInstallButton(remote);
|
|
|
|
return (
|
2022-10-03 21:25:55 +02:00
|
|
|
<div key="install-status" className="remote-field">
|
2022-09-27 08:24:15 +02:00
|
|
|
<div className="remote-field-def"> install-status</div>
|
|
|
|
<div className="remote-field-val">
|
|
|
|
{statusStr}<If condition={installButton != null}> | {this.renderInstallButton(remote)}</If>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-23 06:20:37 +02:00
|
|
|
@boundMethod
|
|
|
|
clickTermBlock(e : any) {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
if (inputModel.remoteTermWrap != null) {
|
2022-10-11 22:25:23 +02:00
|
|
|
inputModel.remoteTermWrap.focusTerminal();
|
2022-09-22 08:31:03 +02:00
|
|
|
}
|
2022-09-23 06:20:37 +02:00
|
|
|
}
|
2022-10-03 21:25:55 +02:00
|
|
|
|
|
|
|
getCanonicalNameDisplayWithPort(remote : RemoteType) {
|
|
|
|
if (isBlank(remote.remotevars.port) || remote.remotevars.port == "22") {
|
|
|
|
return remote.remotecanonicalname;
|
|
|
|
}
|
|
|
|
return remote.remotecanonicalname + " (port " + remote.remotevars.port + ")";
|
|
|
|
}
|
2022-09-23 06:20:37 +02:00
|
|
|
|
|
|
|
render() {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
let infoMsg = inputModel.infoMsg.get();
|
|
|
|
let ptyRemoteId = (infoMsg == null ? null : infoMsg.ptyremoteid);
|
2022-10-11 22:25:23 +02:00
|
|
|
let isTermFocused = (inputModel.remoteTermWrap == null ? false : inputModel.remoteTermWrapFocus.get());
|
2022-09-23 06:20:37 +02:00
|
|
|
let remote : RemoteType;
|
|
|
|
if (ptyRemoteId != null) {
|
|
|
|
remote = GlobalModel.getRemote(ptyRemoteId);
|
2022-09-22 08:31:03 +02:00
|
|
|
}
|
2022-09-23 06:20:37 +02:00
|
|
|
if (ptyRemoteId == null || remote == null) {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div key="term" className="terminal-wrapper" style={{display: "none"}}>
|
2022-10-14 01:09:26 +02:00
|
|
|
<div key="terminal" className="terminal-connectelem" id="term-remote"></div>
|
2022-09-23 06:20:37 +02:00
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
2022-09-22 08:31:03 +02:00
|
|
|
}
|
2022-09-23 06:20:37 +02:00
|
|
|
return (
|
|
|
|
<>
|
2022-10-03 21:25:55 +02:00
|
|
|
<div key="info" className="info-remote">
|
|
|
|
<div key="title" className="info-title">
|
|
|
|
show remote [{remote.remotecanonicalname}]
|
2022-09-23 06:20:37 +02:00
|
|
|
</div>
|
2022-10-03 21:25:55 +02:00
|
|
|
<div key="remoteid" className="remote-field">
|
|
|
|
<div className="remote-field-def"> remoteid</div>
|
2022-10-04 20:15:35 +02:00
|
|
|
<div className="remote-field-val">{remote.remoteid} | {this.renderEditButton(remote)}</div>
|
2022-09-23 06:20:37 +02:00
|
|
|
</div>
|
2022-10-03 21:25:55 +02:00
|
|
|
<div key="type" className="remote-field">
|
|
|
|
<div className="remote-field-def"> type</div>
|
|
|
|
<div className="remote-field-val">{this.getRemoteTypeStr(remote)}</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div key="cname" className="remote-field">
|
|
|
|
<div className="remote-field-def"> canonicalname</div>
|
|
|
|
<div className="remote-field-val">{this.getCanonicalNameDisplayWithPort(remote)}</div>
|
|
|
|
</div>
|
|
|
|
<div key="alias" className="remote-field">
|
|
|
|
<div className="remote-field-def"> alias</div>
|
|
|
|
<div className="remote-field-val">{isBlank(remote.remotealias) ? "-" : remote.remotealias}</div>
|
|
|
|
</div>
|
|
|
|
<div key="cm" className="remote-field">
|
|
|
|
<div className="remote-field-def"> connectmode</div>
|
|
|
|
<div className="remote-field-val">{remote.connectmode}</div>
|
|
|
|
</div>
|
|
|
|
<div key="status" className="remote-field">
|
|
|
|
<div className="remote-field-def"> status</div>
|
|
|
|
<div className="remote-field-val"><RemoteStatusLight remote={remote}/>{remote.status} | {this.renderConnectButton(remote)}</div>
|
|
|
|
</div>
|
|
|
|
<If condition={!isBlank(remote.errorstr)}>
|
|
|
|
<div key="error" className="remote-field">
|
|
|
|
<div className="remote-field-def"> error</div>
|
|
|
|
<div className="remote-field-val">{remote.errorstr}</div>
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
{this.renderInstallStatus(remote)}
|
|
|
|
<If condition={!isBlank(remote.installerrorstr)}>
|
|
|
|
<div key="ierror" className="remote-field">
|
|
|
|
<div className="remote-field-def"> install error</div>
|
|
|
|
<div className="remote-field-val">{remote.installerrorstr}</div>
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
</div>
|
2022-10-12 22:57:15 +02:00
|
|
|
<div key="term" className={cn("terminal-wrapper", {"focus": isTermFocused}, (remote != null ? "status-" + remote.status : null))} style={{display: (ptyRemoteId == null ? "none" : "block"), width: termWidthFromCols(RemotePtyCols)}}>
|
2022-10-03 21:25:55 +02:00
|
|
|
<If condition={!isTermFocused}>
|
|
|
|
<div key="termblock" className="term-block" onClick={this.clickTermBlock}></div>
|
|
|
|
</If>
|
|
|
|
<If condition={inputModel.showNoInputMsg.get()}>
|
|
|
|
<div key="termtag" className="term-tag">input is only allowed while status is 'connecting'</div>
|
|
|
|
</If>
|
2022-10-14 01:09:26 +02:00
|
|
|
<div key="terminal" className="terminal-connectelem" id="term-remote" data-remoteid={ptyRemoteId} style={{height: termHeightFromRows(RemotePtyRows)}}></div>
|
2022-10-03 21:25:55 +02:00
|
|
|
</div>
|
2022-09-23 06:20:37 +02:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-30 23:57:23 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class InfoRemoteEdit extends React.Component<{}, {}> {
|
|
|
|
alias : mobx.IObservableValue<string>;
|
|
|
|
hostName : mobx.IObservableValue<string>;
|
2022-10-01 00:42:10 +02:00
|
|
|
keyStr : mobx.IObservableValue<string>;
|
2022-09-30 23:57:23 +02:00
|
|
|
portStr : mobx.IObservableValue<string>;
|
2022-10-03 21:25:55 +02:00
|
|
|
passwordStr : mobx.IObservableValue<string>;
|
2022-09-30 23:57:23 +02:00
|
|
|
colorStr : mobx.IObservableValue<string>;
|
|
|
|
connectMode : mobx.IObservableValue<string>;
|
|
|
|
sudoBool : mobx.IObservableValue<boolean>;
|
|
|
|
autoInstallBool : mobx.IObservableValue<boolean>;
|
2022-10-04 04:05:52 +02:00
|
|
|
authMode : mobx.IObservableValue<string>;
|
2022-10-04 20:15:35 +02:00
|
|
|
archiveConfirm : mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
2022-09-30 23:57:23 +02:00
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.resetForm();
|
|
|
|
}
|
|
|
|
|
2022-10-04 04:05:52 +02:00
|
|
|
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";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-30 23:57:23 +02:00
|
|
|
resetForm() {
|
2022-10-04 04:05:52 +02:00
|
|
|
let redit = this.getRemoteEdit();
|
|
|
|
let remote = this.getEditingRemote();
|
|
|
|
if (redit == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let isEditMode = !isBlank(redit.remoteid);
|
|
|
|
if (isEditMode && remote == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// not editable
|
2022-09-30 23:57:23 +02:00
|
|
|
this.hostName = mobx.observable.box("");
|
2022-10-03 21:25:55 +02:00
|
|
|
this.portStr = mobx.observable.box("");
|
2022-09-30 23:57:23 +02:00
|
|
|
this.sudoBool = mobx.observable.box(false);
|
2022-10-04 04:05:52 +02:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
2022-09-30 23:57:23 +02:00
|
|
|
}
|
|
|
|
|
2022-10-04 19:52:55 +02:00
|
|
|
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);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-10-04 20:15:35 +02:00
|
|
|
@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);
|
|
|
|
}
|
|
|
|
|
2022-09-30 23:57:23 +02:00
|
|
|
@boundMethod
|
2022-10-04 04:05:52 +02:00
|
|
|
doSubmitRemote() {
|
|
|
|
let redit = this.getRemoteEdit();
|
|
|
|
let isEditing = !isBlank(redit.remoteid);
|
2022-10-01 00:42:10 +02:00
|
|
|
let cname = this.hostName.get();
|
2022-09-30 23:57:23 +02:00
|
|
|
let kwargs : Record<string, string> = {};
|
2022-10-04 04:05:52 +02:00
|
|
|
let authMode = this.authMode.get();
|
|
|
|
if (!isEditing) {
|
|
|
|
if (this.sudoBool.get()) {
|
|
|
|
kwargs["sudo"] = "1";
|
|
|
|
}
|
2022-09-30 23:57:23 +02:00
|
|
|
}
|
2022-10-04 04:05:52 +02:00
|
|
|
kwargs["alias"] = this.alias.get();
|
|
|
|
kwargs["color"] = this.colorStr.get();
|
|
|
|
if (authMode == "key" || authMode == "key+pw") {
|
2022-10-01 00:42:10 +02:00
|
|
|
kwargs["key"] = this.keyStr.get();
|
|
|
|
}
|
2022-10-04 04:05:52 +02:00
|
|
|
else {
|
|
|
|
kwargs["key"] = "";
|
|
|
|
}
|
|
|
|
if (authMode == "pw" || authMode == "key+pw") {
|
2022-10-01 02:23:28 +02:00
|
|
|
kwargs["password"] = this.passwordStr.get();
|
|
|
|
}
|
2022-10-04 04:05:52 +02:00
|
|
|
else {
|
|
|
|
kwargs["password"] = ""
|
2022-09-30 23:57:23 +02:00
|
|
|
}
|
2022-10-04 04:05:52 +02:00
|
|
|
kwargs["connectmode"] = this.connectMode.get();
|
|
|
|
kwargs["autoinstall"] = (this.autoInstallBool.get() ? "1" : "0");
|
2022-09-30 23:57:23 +02:00
|
|
|
kwargs["visual"] = "1";
|
|
|
|
kwargs["submit"] = "1";
|
2022-10-04 04:05:52 +02:00
|
|
|
console.log("submit remote", (isEditing ? redit.remoteid : cname), kwargs);
|
2022-09-30 23:57:23 +02:00
|
|
|
mobx.action(() => {
|
2022-10-04 04:05:52 +02:00
|
|
|
if (isEditing) {
|
|
|
|
GlobalCommandRunner.editRemote(redit.remoteid, kwargs);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
GlobalCommandRunner.createRemote(cname, kwargs);
|
|
|
|
}
|
2022-09-30 23:57:23 +02:00
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
doCancel() {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.resetForm();
|
|
|
|
GlobalModel.inputModel.clearInfoMsg(true);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
keyDownCreateRemote(e : any) {
|
|
|
|
if (e.code == "Enter") {
|
2022-10-04 04:05:52 +02:00
|
|
|
this.doSubmitRemote();
|
2022-09-30 23:57:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@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
|
2022-10-01 00:42:10 +02:00
|
|
|
onChangeKeyStr(e : any) {
|
2022-09-30 23:57:23 +02:00
|
|
|
mobx.action(() => {
|
2022-10-01 00:42:10 +02:00
|
|
|
this.keyStr.set(e.target.value);
|
2022-09-30 23:57:23 +02:00
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-10-01 02:23:28 +02:00
|
|
|
@boundMethod
|
2022-10-03 21:25:55 +02:00
|
|
|
onChangePortStr(e : any) {
|
2022-10-01 02:23:28 +02:00
|
|
|
mobx.action(() => {
|
2022-10-03 21:25:55 +02:00
|
|
|
this.portStr.set(e.target.value);
|
2022-10-01 02:23:28 +02:00
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-09-30 23:57:23 +02:00
|
|
|
@boundMethod
|
2022-10-03 21:25:55 +02:00
|
|
|
onChangePasswordStr(e : any) {
|
2022-09-30 23:57:23 +02:00
|
|
|
mobx.action(() => {
|
2022-10-03 21:25:55 +02:00
|
|
|
this.passwordStr.set(e.target.value);
|
2022-09-30 23:57:23 +02:00
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-10-04 04:05:52 +02:00
|
|
|
@boundMethod
|
|
|
|
onFocusPasswordStr(e : any) {
|
|
|
|
if (this.passwordStr.get() == PasswordUnchangedSentinel) {
|
|
|
|
e.target.select();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-30 23:57:23 +02:00
|
|
|
@boundMethod
|
|
|
|
onChangeColorStr(e : any) {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.colorStr.set(e.target.value);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
onChangeConnectMode(e : any) {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.connectMode.set(e.target.value);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-10-04 04:05:52 +02:00
|
|
|
@boundMethod
|
|
|
|
onChangeAuthMode(e : any) {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.authMode.set(e.target.value);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-09-30 23:57:23 +02:00
|
|
|
@boundMethod
|
|
|
|
onChangeSudo(e : any) {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.sudoBool.set(e.target.checked);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
onChangeAutoInstall(e : any) {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.autoInstallBool.set(e.target.checked);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-10-03 21:25:55 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-01 00:42:10 +02:00
|
|
|
remoteCName() : string {
|
2022-10-03 21:25:55 +02:00
|
|
|
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;
|
2022-10-01 00:42:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-30 23:57:23 +02:00
|
|
|
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;
|
|
|
|
}
|
2022-10-03 21:25:55 +02:00
|
|
|
let isEditMode = !isBlank(redit.remoteid);
|
|
|
|
let remote = this.getEditingRemote();
|
|
|
|
if (isEditMode && remote == null) {
|
2022-09-30 23:57:23 +02:00
|
|
|
return (
|
2022-10-03 21:25:55 +02:00
|
|
|
<div className="info-title">cannot edit, remote {redit.remoteid} not found</div>
|
2022-09-30 23:57:23 +02:00
|
|
|
);
|
|
|
|
}
|
2022-10-04 04:05:52 +02:00
|
|
|
let colorStr : string = null;
|
2022-09-30 23:57:23 +02:00
|
|
|
return (
|
|
|
|
<form className="info-remote">
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="title" className="info-title">
|
2022-10-03 21:25:55 +02:00
|
|
|
<If condition={!isEditMode}>
|
2022-12-12 21:11:02 +01:00
|
|
|
add new remote <If condition={this.hostName.get() != ""}>'{this.remoteCName()}'</If>
|
2022-09-30 23:57:23 +02:00
|
|
|
</If>
|
2022-10-03 21:25:55 +02:00
|
|
|
<If condition={isEditMode}>
|
|
|
|
edit remote '{this.remoteCName()}'
|
2022-09-30 23:57:23 +02:00
|
|
|
</If>
|
|
|
|
</div>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="type" className="remote-input-field">
|
2022-09-30 23:57:23 +02:00
|
|
|
<div className="remote-field-label">type</div>
|
|
|
|
<div className="remote-field-control text-control">
|
|
|
|
ssh
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-03 21:25:55 +02:00
|
|
|
<If condition={!isEditMode}>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="hostname" className="remote-input-field">
|
2022-10-03 21:25:55 +02:00
|
|
|
<div className="remote-field-label">user@host</div>
|
|
|
|
<div className="remote-field-control text-input">
|
2022-10-04 04:05:52 +02:00
|
|
|
<input type="text" autoFocus={!isEditMode ? true : null} onChange={this.onChangeHostName} value={this.hostName.get()}/>
|
2022-10-03 21:25:55 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="port" className="remote-input-field">
|
2022-10-03 21:25:55 +02:00
|
|
|
<div className="remote-field-label">port</div>
|
|
|
|
<div className="remote-field-control text-input">
|
|
|
|
<input type="number" placeholder="22" onChange={this.onChangePortStr} value={this.portStr.get()}/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<If condition={isEditMode}>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="hostname" className="remote-input-field">
|
2022-10-03 21:25:55 +02:00
|
|
|
<div className="remote-field-label">user@host</div>
|
|
|
|
<div className="remote-field-control text-control">
|
|
|
|
{remote.remotecanonicalname}
|
|
|
|
<If condition={remote.remotevars.port != "22"}>
|
2022-10-04 04:05:52 +02:00
|
|
|
(port {remote.remotevars.port})
|
2022-10-03 21:25:55 +02:00
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="alias" className="remote-input-field">
|
2022-09-30 23:57:23 +02:00
|
|
|
<div className="remote-field-label">alias</div>
|
|
|
|
<div className="remote-field-control text-input">
|
2022-10-04 04:05:52 +02:00
|
|
|
<input type="text" autoFocus={isEditMode ? true : null} onChange={this.onChangeAlias} value={this.alias.get()}/>
|
2022-09-30 23:57:23 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="auth" className="remote-input-field">
|
|
|
|
<div className="remote-field-label">authmode</div>
|
|
|
|
<div className="remote-field-control select-input">
|
|
|
|
<select onChange={this.onChangeAuthMode} value={this.authMode.get()}>
|
|
|
|
<option value="none">none</option>
|
|
|
|
<option value="key">keyfile</option>
|
|
|
|
<option value="pw">password</option>
|
|
|
|
<option value="key+pw">keyfile and password</option>
|
|
|
|
</select>
|
2022-09-30 23:57:23 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-04 04:05:52 +02:00
|
|
|
<If condition={this.authMode.get() == "key" || this.authMode.get() == "key+pw"}>
|
|
|
|
<div key="keyfile" className="remote-input-field">
|
|
|
|
<div className="remote-field-label">ssh keyfile</div>
|
|
|
|
<div className="remote-field-control text-input">
|
|
|
|
<input type="text" onChange={this.onChangeKeyStr} value={this.keyStr.get()}/>
|
|
|
|
</div>
|
2022-10-01 02:23:28 +02:00
|
|
|
</div>
|
2022-10-04 04:05:52 +02:00
|
|
|
</If>
|
|
|
|
<If condition={this.authMode.get() == "pw" || this.authMode.get() == "key+pw"}>
|
|
|
|
<div key="pw" className="remote-input-field">
|
|
|
|
<div className="remote-field-label">ssh password</div>
|
|
|
|
<div className="remote-field-control text-input">
|
|
|
|
<input type="password" onFocus={this.onFocusPasswordStr} onChange={this.onChangePasswordStr} value={this.passwordStr.get()}/>
|
2022-10-04 19:52:55 +02:00
|
|
|
<If condition={this.canResetPw()}>
|
|
|
|
<i onClick={this.resetPw} title="restore to original password" className="icon fa fa-undo undo-icon"/>
|
|
|
|
</If>
|
2022-10-04 04:05:52 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<div key="sudo" className="remote-input-field" style={{display: "none"}}>
|
2022-09-30 23:57:23 +02:00
|
|
|
<div className="remote-field-label">sudo</div>
|
|
|
|
<div className="remote-field-control checkbox-input">
|
|
|
|
<input type="checkbox" onChange={this.onChangeSudo} checked={this.sudoBool.get()}/>
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="cm" className="remote-input-field">
|
2022-09-30 23:57:23 +02:00
|
|
|
<div className="remote-field-label">connectmode</div>
|
|
|
|
<div className="remote-field-control select-input">
|
|
|
|
<select onChange={this.onChangeConnectMode} value={this.connectMode.get()}>
|
|
|
|
<option>startup</option>
|
|
|
|
<option>auto</option>
|
|
|
|
<option>manual</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="ai" className="remote-input-field">
|
2022-09-30 23:57:23 +02:00
|
|
|
<div className="remote-field-label">autoinstall</div>
|
|
|
|
<div className="remote-field-control checkbox-input">
|
|
|
|
<input type="checkbox" onChange={this.onChangeAutoInstall} checked={this.autoInstallBool.get()}/>
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-12-12 21:26:55 +01:00
|
|
|
<div key="color" className="remote-input-field" style={{display: "none"}}>
|
2022-09-30 23:57:23 +02:00
|
|
|
<div className="remote-field-label">color</div>
|
|
|
|
<div className="remote-field-control select-input">
|
|
|
|
<select onChange={this.onChangeColorStr} value={this.colorStr.get()}>
|
|
|
|
<option value="">(default)</option>
|
|
|
|
<For each="colorStr" of={RemoteColors}>
|
|
|
|
<option key={colorStr} value={colorStr}>{colorStr}</option>
|
|
|
|
</For>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-01 00:42:10 +02:00
|
|
|
<If condition={!isBlank(redit.errorstr)}>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="error" className="info-error">
|
2022-10-01 00:42:10 +02:00
|
|
|
{redit.errorstr}
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<If condition={!isBlank(redit.infostr)}>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="msg" className="info-msg">
|
2022-10-01 00:42:10 +02:00
|
|
|
{redit.infostr}
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-10-04 04:05:52 +02:00
|
|
|
<div key="controls" style={{marginTop: 15, marginBottom: 10}} className="remote-input-field">
|
|
|
|
<a tabIndex={0} style={{marginRight: 20}} onClick={this.doSubmitRemote} onKeyDown={this.keyDownCreateRemote} className="text-button success-button">[{isEditMode ? "update" : "create"} remote]</a>
|
2022-09-30 23:57:23 +02:00
|
|
|
{"|"}
|
2022-12-12 21:26:55 +01:00
|
|
|
<If condition={isEditMode}>
|
|
|
|
<a tabIndex={0} style={{marginLeft: 20, marginRight: 5}} onClick={this.doArchiveRemote} onKeyDown={this.keyDownCreateRemote} className={cn("text-button", (this.archiveConfirm.get() ? "error-button" : "disabled-button"))}>[archive remote]</a>
|
|
|
|
<input onChange={this.updateArchiveConfirm} checked={this.archiveConfirm.get()} style={{marginRight: 20}} type="checkbox"/>
|
|
|
|
{"|"}
|
|
|
|
</If>
|
2022-09-30 23:57:23 +02:00
|
|
|
<a tabIndex={0} style={{marginLeft: 20}} onClick={this.doCancel} onKeyDown={this.keyDownCancel} className="text-button grey-button">[cancel (ESC)]</a>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-23 06:20:37 +02:00
|
|
|
@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);
|
2022-09-22 08:31:03 +02:00
|
|
|
}
|
|
|
|
|
2022-09-15 02:14:27 +02:00
|
|
|
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;
|
2022-09-30 23:57:23 +02:00
|
|
|
let titleStr = null;
|
2022-10-04 04:05:52 +02:00
|
|
|
let remoteEditKey = "inforemoteedit";
|
2022-09-30 23:57:23 +02:00
|
|
|
if (infoMsg != null) {
|
|
|
|
titleStr = infoMsg.infotitle;
|
2022-10-04 04:05:52 +02:00
|
|
|
if (infoMsg.remoteedit != null) {
|
|
|
|
remoteEditKey += (infoMsg.remoteedit.remoteid == null ? "-new" : "-" + infoMsg.remoteedit.remoteid);
|
|
|
|
}
|
2022-09-30 23:57:23 +02:00
|
|
|
}
|
2022-09-15 02:14:27 +02:00
|
|
|
return (
|
|
|
|
<div className="cmd-input-info" style={{display: (infoShow ? "block" : "none")}}>
|
|
|
|
<If condition={infoMsg && infoMsg.infotitle != null}>
|
2022-09-23 06:20:37 +02:00
|
|
|
<div key="infotitle" className="info-title">
|
2022-09-30 23:57:23 +02:00
|
|
|
{titleStr}
|
2022-09-15 02:14:27 +02:00
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<If condition={infoMsg && infoMsg.infomsg != null}>
|
2022-09-23 06:20:37 +02:00
|
|
|
<div key="infomsg" className="info-msg">
|
2022-09-15 02:14:27 +02:00
|
|
|
{infoMsg.infomsg}
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<If condition={infoMsg && infoMsg.infolines != null}>
|
2022-09-23 06:20:37 +02:00
|
|
|
<div key="infolines" className="info-lines">
|
2022-09-15 02:14:27 +02:00
|
|
|
<For index="idx" each="line" of={infoMsg.infolines}>
|
|
|
|
<div key={idx}>{line == "" ? " " : line}</div>
|
|
|
|
</For>
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-10-01 00:42:10 +02:00
|
|
|
<If condition={infoMsg && infoMsg.remoteedit}>
|
2022-10-04 04:05:52 +02:00
|
|
|
<InfoRemoteEdit key={"inforemoteedit"} />
|
2022-10-01 00:42:10 +02:00
|
|
|
</If>
|
2022-09-25 04:53:06 +02:00
|
|
|
<InfoRemoteShow key="inforemoteshow"/>
|
|
|
|
<InfoRemoteShowAll key="inforemoteshowall"/>
|
2022-09-15 02:14:27 +02:00
|
|
|
<If condition={infoMsg && infoMsg.infocomps != null && infoMsg.infocomps.length > 0}>
|
2022-09-23 06:20:37 +02:00
|
|
|
<div key="infocomps" className="info-comps">
|
2022-09-15 02:14:27 +02:00
|
|
|
<For each="istr" index="idx" of={infoMsg.infocomps}>
|
|
|
|
<div key={idx} className={cn("info-comp", {"metacmd-comp": istr.startsWith("^")})}>
|
|
|
|
{this.getAfterSlash(istr)}
|
|
|
|
</div>
|
|
|
|
</For>
|
|
|
|
<If condition={infoMsg.infocompsmore}>
|
|
|
|
<div key="more" className="info-comp">
|
|
|
|
...
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<If condition={infoMsg && infoMsg.infoerror != null}>
|
2022-09-23 06:20:37 +02:00
|
|
|
<div key="infoerror" className="info-error">
|
2022-09-15 02:14:27 +02:00
|
|
|
[error] {infoMsg.infoerror}
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-31 00:25:51 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class HistoryInfo extends React.Component<{}, {}> {
|
2022-08-31 02:05:35 +02:00
|
|
|
lastClickHNum : string = null;
|
|
|
|
lastClickTs : number = 0;
|
2022-08-31 08:12:37 +02:00
|
|
|
containingText : mobx.IObservableValue<string> = mobx.observable.box("");
|
2022-08-31 02:05:35 +02:00
|
|
|
|
2022-08-31 00:25:51 +02:00
|
|
|
componentDidMount() {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
2022-08-31 08:12:37 +02:00
|
|
|
let hitem = inputModel.getHistorySelectedItem();
|
|
|
|
if (hitem == null) {
|
|
|
|
hitem = inputModel.getFirstHistoryItem();
|
|
|
|
}
|
|
|
|
if (hitem != null) {
|
|
|
|
inputModel.scrollHistoryItemIntoView(hitem.historynum);
|
2022-08-31 00:25:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-31 02:05:35 +02:00
|
|
|
@boundMethod
|
|
|
|
handleItemClick(hitem : HistoryItem) {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
2022-08-31 08:12:37 +02:00
|
|
|
let selItem = inputModel.getHistorySelectedItem();
|
|
|
|
if (this.lastClickHNum == hitem.historynum && selItem != null && selItem.historynum == hitem.historynum) {
|
2022-08-31 02:05:35 +02:00
|
|
|
inputModel.grabSelectedHistoryItem();
|
|
|
|
return;
|
|
|
|
}
|
2022-08-31 08:12:37 +02:00
|
|
|
inputModel.giveFocus();
|
2022-08-31 02:05:35 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-08-31 21:00:53 +02:00
|
|
|
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 {
|
2022-08-31 00:25:51 +02:00
|
|
|
let lines = hitem.cmdstr.split("\n");
|
|
|
|
let line : string = "";
|
|
|
|
let idx = 0;
|
2022-08-31 21:00:53 +02:00
|
|
|
let limitRemote = opts.limitRemote;
|
2022-08-31 22:29:59 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-31 00:25:51 +02:00
|
|
|
return (
|
2022-08-31 09:02:16 +02:00
|
|
|
<div key={hitem.historynum} className={cn("history-item", {"is-selected": isSelected}, {"history-haderror": hitem.haderror}, "hnum-" + hitem.historynum)} onClick={() => this.handleItemClick(hitem)}>
|
2022-08-31 22:29:59 +02:00
|
|
|
<div className="history-line">{(isSelected ? "*" : " ")}{sprintf("%5s", hitem.historynum)} {opts.queryType == "global" ? sessionStr : ""}{!limitRemote ? this.renderRemote(hitem) : ""} {lines[0]}</div>
|
|
|
|
<For each="line" index="idx" of={lines.slice(1)}>
|
2022-08-31 00:25:51 +02:00
|
|
|
<div key={idx} className="history-line">{line}</div>
|
|
|
|
</For>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2022-08-31 02:05:35 +02:00
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
handleClose() {
|
|
|
|
GlobalModel.inputModel.toggleInfoMsg();
|
|
|
|
}
|
2022-08-31 08:12:37 +02:00
|
|
|
|
2022-08-31 00:25:51 +02:00
|
|
|
render() {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
let idx : number = 0;
|
2022-08-31 08:12:37 +02:00
|
|
|
let selItem = inputModel.getHistorySelectedItem();
|
2022-08-31 02:05:35 +02:00
|
|
|
let hitems = inputModel.getFilteredHistoryItems();
|
2022-08-31 00:25:51 +02:00
|
|
|
hitems = hitems.slice().reverse();
|
2022-08-31 02:05:35 +02:00
|
|
|
let hitem : HistoryItem = null;
|
2022-08-31 09:02:16 +02:00
|
|
|
let opts = inputModel.historyQueryOpts.get();
|
2022-08-31 00:25:51 +02:00
|
|
|
return (
|
|
|
|
<div className="cmd-history">
|
|
|
|
<div className="history-title">
|
2022-08-31 21:00:53 +02:00
|
|
|
<div>history</div>
|
|
|
|
<div className="spacer"></div>
|
2022-08-31 22:29:59 +02:00
|
|
|
<div className="history-opt">[for {opts.queryType} ⌘S]</div>
|
2022-08-31 21:00:53 +02:00
|
|
|
<div className="spacer"></div>
|
|
|
|
<div className="history-opt">[containing '{opts.queryStr}']</div>
|
|
|
|
<div className="spacer"></div>
|
|
|
|
<div className="history-opt">[{opts.limitRemote ? "this" : "any"} remote ⌘R]</div>
|
|
|
|
<div className="spacer"></div>
|
|
|
|
<div className="history-opt">[{opts.includeMeta ? "" : "no "}metacmds ⌘M]</div>
|
|
|
|
<div className="grow-spacer"></div>
|
|
|
|
<div className="history-clickable-opt" onClick={this.handleClose}>(ESC)</div>
|
|
|
|
<div className="spacer"></div>
|
2022-08-31 00:25:51 +02:00
|
|
|
</div>
|
2022-08-31 22:29:59 +02:00
|
|
|
<div className={cn("history-items", {"show-remotes": !opts.limitRemote}, {"show-sessions": opts.queryType == "global"})}>
|
2022-08-31 00:25:51 +02:00
|
|
|
<If condition={hitems.length == 0}>
|
|
|
|
[no history]
|
|
|
|
</If>
|
|
|
|
<If condition={hitems.length > 0}>
|
|
|
|
<For each="hitem" index="idx" of={hitems}>
|
2022-08-31 21:00:53 +02:00
|
|
|
{this.renderHItem(hitem, opts, (hitem == selItem))}
|
2022-08-31 00:25:51 +02:00
|
|
|
</For>
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-25 04:00:03 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class CmdInput extends React.Component<{}, {}> {
|
2022-12-27 19:58:11 +01:00
|
|
|
cmdInputRef : React.RefObject<any> = React.createRef();
|
|
|
|
|
2022-09-17 01:37:54 +02:00
|
|
|
@boundMethod
|
|
|
|
onInfoToggle() : void {
|
|
|
|
GlobalModel.inputModel.toggleInfoMsg();
|
|
|
|
return;
|
|
|
|
}
|
2022-12-27 19:58:11 +01:00
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
let elem = this.cmdInputRef.current;
|
|
|
|
if (elem == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let height = elem.offsetHeight;
|
|
|
|
mobx.action(() => {
|
|
|
|
GlobalModel.inputModel.cmdInputHeight.set(height);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps, prevState, snapshot : {height : number}) : void {
|
|
|
|
let elem = this.cmdInputRef.current;
|
|
|
|
if (elem == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let height = elem.offsetHeight;
|
|
|
|
mobx.action(() => {
|
|
|
|
GlobalModel.inputModel.cmdInputHeight.set(height);
|
|
|
|
})();
|
|
|
|
}
|
2022-09-17 01:37:54 +02:00
|
|
|
|
2022-06-08 02:25:35 +02:00
|
|
|
render() {
|
2022-08-11 03:35:18 +02:00
|
|
|
let model = GlobalModel;
|
2022-08-30 21:22:42 +02:00
|
|
|
let inputModel = model.inputModel;
|
2022-08-11 03:35:18 +02:00
|
|
|
let win = GlobalModel.getActiveWindow();
|
|
|
|
let ri : RemoteInstanceType = null;
|
2022-08-24 22:19:59 +02:00
|
|
|
let rptr : RemotePtrType = null;
|
2022-08-11 03:35:18 +02:00
|
|
|
if (win != null) {
|
|
|
|
ri = win.getCurRemoteInstance();
|
2022-08-24 22:19:59 +02:00
|
|
|
rptr = win.curRemote.get();
|
2022-08-11 03:35:18 +02:00
|
|
|
}
|
|
|
|
let remote : RemoteType = null;
|
2022-11-29 03:08:19 +01:00
|
|
|
let remoteState : FeStateType = null;
|
2022-08-11 03:35:18 +02:00
|
|
|
if (ri != null) {
|
|
|
|
remote = GlobalModel.getRemote(ri.remoteid);
|
2022-11-29 03:08:19 +01:00
|
|
|
remoteState = ri.festate;
|
2022-08-11 03:35:18 +02:00
|
|
|
}
|
2022-08-24 22:19:59 +02:00
|
|
|
let remoteStr = getRemoteStr(rptr);
|
2022-08-11 03:35:18 +02:00
|
|
|
let cwdStr = getCwdStr(remote, remoteState);
|
2022-08-30 21:22:42 +02:00
|
|
|
let infoShow = inputModel.infoShow.get();
|
|
|
|
let historyShow = !infoShow && inputModel.historyShow.get();
|
2022-09-23 06:20:37 +02:00
|
|
|
let infoMsg = inputModel.infoMsg.get();
|
|
|
|
let hasInfo = (infoMsg != null);
|
|
|
|
let remoteShow = (infoMsg != null && !isBlank(infoMsg.ptyremoteid));
|
2022-10-07 00:17:48 +02:00
|
|
|
let focusVal = inputModel.physicalInputFocused.get();
|
2022-10-23 08:54:46 +02:00
|
|
|
let inputMode : string = inputModel.inputMode.get();
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-12-27 19:58:11 +01:00
|
|
|
<div ref={this.cmdInputRef} className={cn("cmd-input has-background-black", {"has-info": infoShow}, {"has-history": historyShow}, {"has-remote": remoteShow})}>
|
2022-10-07 00:17:48 +02:00
|
|
|
<div key="focus" className={cn("focus-indicator", {"active": focusVal})}/>
|
2022-10-04 22:53:04 +02:00
|
|
|
<div key="minmax" onClick={this.onInfoToggle} className="input-minmax-control">
|
2022-09-17 01:37:54 +02:00
|
|
|
<If condition={infoShow || historyShow}>
|
|
|
|
<i className="fa fa-chevron-down"/>
|
|
|
|
</If>
|
|
|
|
<If condition={!(infoShow || historyShow) && hasInfo}>
|
|
|
|
<i className="fa fa-chevron-up"/>
|
|
|
|
</If>
|
|
|
|
</div>
|
2022-08-31 00:25:51 +02:00
|
|
|
<If condition={historyShow}>
|
2022-08-31 21:00:53 +02:00
|
|
|
<div className="cmd-input-grow-spacer"></div>
|
2022-08-31 00:25:51 +02:00
|
|
|
<HistoryInfo/>
|
|
|
|
</If>
|
2022-10-04 22:53:04 +02:00
|
|
|
<InfoMsg key="infomsg"/>
|
|
|
|
<div key="prompt" className="cmd-input-context">
|
2022-06-08 02:25:35 +02:00
|
|
|
<div className="has-text-white">
|
2022-11-29 03:08:19 +01:00
|
|
|
<Prompt rptr={rptr} festate={remoteState}/>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-23 08:54:46 +02:00
|
|
|
<div key="input" className={cn("cmd-input-field field has-addons", (inputMode != null ? "inputmode-" + inputMode : null))}>
|
|
|
|
<If condition={inputMode != null}>
|
|
|
|
<div className="control cmd-quick-context">
|
|
|
|
<div className="button is-static">{inputMode}</div>
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-08-31 08:12:37 +02:00
|
|
|
<TextAreaInput/>
|
2022-06-08 02:25:35 +02:00
|
|
|
<div className="control cmd-exec">
|
2022-08-25 21:12:56 +02:00
|
|
|
<div onClick={GlobalModel.inputModel.uiSubmitCommand} className="button">
|
2022-06-08 02:25:35 +02:00
|
|
|
<span className="icon">
|
|
|
|
<i className="fa fa-rocket"/>
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-10-08 03:25:47 +02:00
|
|
|
class LinesView extends React.Component<{sw : ScreenWindow, width : number, lines : LineType[]}, {}> {
|
2022-08-13 03:34:56 +02:00
|
|
|
rszObs : any;
|
2022-10-07 20:32:58 +02:00
|
|
|
linesRef : React.RefObject<any>;
|
2022-11-23 07:57:35 +01:00
|
|
|
staticRender : OV<boolean> = mobx.observable.box(true, {name: "static-render"});
|
2022-10-08 03:25:47 +02:00
|
|
|
lastOffsetHeight : number = 0;
|
|
|
|
lastOffsetWidth : number = 0;
|
|
|
|
ignoreNextScroll : boolean = false;
|
|
|
|
visibleMap : Map<string, OV<boolean>>; // lineid => OV<vis>
|
2022-10-10 21:45:57 +02:00
|
|
|
lastSelectedLine : number = 0;
|
2022-10-11 02:18:40 +02:00
|
|
|
lastLinesLength : number = 0;
|
2022-10-08 03:25:47 +02:00
|
|
|
|
|
|
|
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.computeVisibleMap_debounced = debounce(1000, this.computeVisibleMap.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
scrollHandler() {
|
2022-10-11 22:25:23 +02:00
|
|
|
// console.log("scroll", this.linesRef.current.scrollTop);
|
|
|
|
this.computeVisibleMap_debounced(); // always do this
|
2022-10-08 03:25:47 +02:00
|
|
|
if (this.ignoreNextScroll) {
|
|
|
|
this.ignoreNextScroll = false;
|
|
|
|
return;
|
|
|
|
}
|
2022-10-11 22:25:23 +02:00
|
|
|
this.computeAnchorLine_throttled(); // only do this when we're not ignoring the scroll
|
2022-10-08 03:25:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
computeAnchorLine() : void {
|
2022-10-10 21:45:57 +02:00
|
|
|
let {sw} = this.props;
|
2022-10-08 03:25:47 +02:00
|
|
|
let linesElem = this.linesRef.current;
|
|
|
|
if (linesElem == null) {
|
2022-11-23 07:57:35 +01:00
|
|
|
sw.setAnchorFields(null, 0, "no-lines");
|
2022-10-08 03:25:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
let lineElemArr = linesElem.querySelectorAll(".line");
|
|
|
|
if (lineElemArr == null) {
|
2022-11-23 07:57:35 +01:00
|
|
|
sw.setAnchorFields(null, 0, "no-line");
|
2022-10-08 03:25:47 +02:00
|
|
|
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];
|
|
|
|
}
|
2022-11-23 07:57:35 +01:00
|
|
|
sw.setAnchorFields(parseInt(anchorElem.dataset.linenum), containerBottom - (anchorElem.offsetTop + anchorElem.offsetHeight), "computeAnchorLine");
|
2022-10-08 03:25:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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<string, boolean>();
|
|
|
|
for (let i=0; i<lineElemArr.length; i++) {
|
|
|
|
let lineElem = lineElemArr[i];
|
|
|
|
let lineTop = lineElem.offsetTop;
|
|
|
|
let lineBot = lineElem.offsetTop + lineElem.offsetHeight;
|
|
|
|
let maxTop = Math.max(containerTop, lineTop);
|
|
|
|
let minBot = Math.min(containerBot, lineBot);
|
|
|
|
newMap.set(lineElem.dataset.linenum, (maxTop < minBot));
|
|
|
|
}
|
|
|
|
mobx.action(() => {
|
|
|
|
for (let [k, v] of newMap) {
|
|
|
|
let oldVal = this.visibleMap.get(k);
|
|
|
|
if (oldVal == null) {
|
2022-11-23 07:57:35 +01:00
|
|
|
oldVal = mobx.observable.box(v, {name: "lines-vis-map"});
|
2022-10-08 03:25:47 +02:00
|
|
|
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 {
|
2022-10-10 21:45:57 +02:00
|
|
|
let {sw} = this.props;
|
2022-10-08 03:25:47 +02:00
|
|
|
let linesElem = this.linesRef.current;
|
|
|
|
if (linesElem == null) {
|
|
|
|
return;
|
|
|
|
}
|
2022-10-10 21:45:57 +02:00
|
|
|
if (sw.anchorLine == null || sw.anchorLine == 0) {
|
2022-10-08 03:25:47 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-10-10 21:45:57 +02:00
|
|
|
let anchorElem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", sw.anchorLine));
|
2022-10-08 03:25:47 +02:00
|
|
|
if (anchorElem == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let scrollTop = linesElem.scrollTop;
|
|
|
|
let height = linesElem.clientHeight;
|
|
|
|
let containerBottom = scrollTop + height;
|
|
|
|
let curAnchorOffset = containerBottom - (anchorElem.offsetTop + anchorElem.offsetHeight);
|
2022-10-10 21:45:57 +02:00
|
|
|
if (curAnchorOffset != sw.anchorOffset) {
|
|
|
|
let offsetDiff = curAnchorOffset - sw.anchorOffset;
|
2022-10-08 03:25:47 +02:00
|
|
|
let newScrollTop = scrollTop - offsetDiff;
|
2022-10-11 00:21:43 +02:00
|
|
|
// console.log("update scrolltop", reason, "line=" + sw.anchorLine, -offsetDiff, linesElem.scrollTop, "=>", newScrollTop);
|
2022-10-08 03:25:47 +02:00
|
|
|
linesElem.scrollTop = newScrollTop;
|
|
|
|
this.ignoreNextScroll = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() : void {
|
2022-10-11 02:18:40 +02:00
|
|
|
let {sw, lines} = this.props;
|
2022-10-10 21:45:57 +02:00
|
|
|
if (sw.anchorLine == null) {
|
|
|
|
this.computeAnchorLine();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.restoreAnchorOffset("re-mount");
|
|
|
|
}
|
|
|
|
this.lastSelectedLine = sw.selectedLine.get();
|
2022-10-11 02:18:40 +02:00
|
|
|
this.lastLinesLength = lines.length;
|
2022-10-08 03:25:47 +02:00
|
|
|
|
|
|
|
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();
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-10-10 21:45:57 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-11 00:21:43 +02:00
|
|
|
getLineViewInfo(lineNum : number) : {height: number, topOffset: number, botOffset: number, anchorOffset: number} {
|
2022-10-10 21:45:57 +02:00
|
|
|
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,
|
2022-10-11 02:18:40 +02:00
|
|
|
anchorOffset: 0,
|
2022-10-10 21:45:57 +02:00
|
|
|
};
|
|
|
|
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;
|
|
|
|
}
|
2022-10-11 02:18:40 +02:00
|
|
|
rtn.anchorOffset = containerBot - lineBot;
|
2022-10-10 21:45:57 +02:00
|
|
|
return rtn;
|
|
|
|
}
|
|
|
|
|
|
|
|
updateSelectedLine() : void {
|
|
|
|
let {sw, lines} = this.props;
|
|
|
|
let linesElem = this.linesRef.current;
|
|
|
|
if (linesElem == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
let newLine = sw.selectedLine.get();
|
2022-10-11 22:25:23 +02:00
|
|
|
this.setLineVisible(newLine, true);
|
|
|
|
// console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", sw.anchorLine, sw.anchorOffset));
|
2022-10-10 21:45:57 +02:00
|
|
|
let viewInfo = this.getLineViewInfo(newLine);
|
|
|
|
if (viewInfo == null) {
|
|
|
|
return;
|
|
|
|
}
|
2022-11-23 07:57:35 +01:00
|
|
|
sw.setAnchorFields(newLine, viewInfo.anchorOffset, "updateSelectedLine");
|
2022-10-10 21:45:57 +02:00
|
|
|
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);
|
2022-10-11 00:21:43 +02:00
|
|
|
this.ignoreNextScroll = true;
|
|
|
|
sw.anchorOffset = (isLast ? 10 : 0);
|
2022-10-10 21:45:57 +02:00
|
|
|
}
|
|
|
|
else if (viewInfo.topOffset < 0) {
|
|
|
|
linesElem.scrollTop = linesElem.scrollTop + viewInfo.topOffset + (isFirst ? -10 : 0);
|
2022-10-11 00:21:43 +02:00
|
|
|
this.ignoreNextScroll = true;
|
|
|
|
sw.anchorOffset = linesElem.clientHeight - viewInfo.height;
|
2022-10-10 21:45:57 +02:00
|
|
|
}
|
2022-10-11 22:25:23 +02:00
|
|
|
// console.log("new anchor", sw.getAnchorStr());
|
2022-10-11 00:21:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
setLineVisible(lineNum : number, vis : boolean) : void {
|
|
|
|
mobx.action(() => {
|
|
|
|
let key = String(lineNum);
|
|
|
|
let visObj = this.visibleMap.get(key);
|
|
|
|
if (visObj == null) {
|
2022-11-23 07:57:35 +01:00
|
|
|
visObj = mobx.observable.box(true, {name: "lines-vis-map"});
|
2022-10-11 00:21:43 +02:00
|
|
|
this.visibleMap.set(key, visObj);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
visObj.set(true);
|
|
|
|
}
|
|
|
|
})();
|
2022-10-10 21:45:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps, prevState, snapshot) : void {
|
2022-10-11 02:18:40 +02:00
|
|
|
let {sw, lines} = this.props;
|
2022-10-10 21:45:57 +02:00
|
|
|
if (sw.selectedLine.get() != this.lastSelectedLine) {
|
|
|
|
this.updateSelectedLine();
|
|
|
|
this.lastSelectedLine = sw.selectedLine.get();
|
2022-10-11 02:18:40 +02:00
|
|
|
} else if (lines.length != this.lastLinesLength) {
|
|
|
|
this.restoreAnchorOffset("line-length-change");
|
2022-10-10 21:45:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-08 03:25:47 +02:00
|
|
|
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 {
|
2022-10-11 00:21:43 +02:00
|
|
|
// console.log("height-change", lineNum, oldHeight, "=>", newHeight);
|
2022-10-08 03:25:47 +02:00
|
|
|
this.restoreAnchorOffset("height-change");
|
|
|
|
this.computeVisibleMap_debounced();
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
let {sw, width, lines} = this.props;
|
2022-10-10 21:45:57 +02:00
|
|
|
let selectedLine = sw.selectedLine.get(); // for re-rendering
|
2022-10-08 03:25:47 +02:00
|
|
|
let line : LineType = null;
|
|
|
|
let idx : number = 0;
|
|
|
|
for (let i=0; i<lines.length; i++) {
|
|
|
|
let key = String(lines[i].linenum);
|
|
|
|
let visObs = this.visibleMap.get(key);
|
|
|
|
if (visObs == null) {
|
2022-11-23 07:57:35 +01:00
|
|
|
this.visibleMap.set(key, mobx.observable.box(false, {name: "lines-vis-map"}));
|
2022-10-08 03:25:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div key="lines" className="lines" onScroll={this.scrollHandler} ref={this.linesRef}>
|
|
|
|
<div className="lines-spacer"></div>
|
|
|
|
<For each="line" of={lines} index="idx">
|
|
|
|
<Line key={line.lineid} line={line} sw={sw} width={width} visible={this.visibleMap.get(String(line.linenum))} staticRender={this.staticRender.get()} onHeightChange={this.onHeightChange}/>
|
|
|
|
</For>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sw is not null
|
|
|
|
@mobxReact.observer
|
|
|
|
class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
|
|
|
rszObs : any;
|
2022-10-07 20:32:58 +02:00
|
|
|
windowViewRef : React.RefObject<any>;
|
2022-06-18 02:54:14 +02:00
|
|
|
|
2022-11-23 07:57:35 +01:00
|
|
|
width : mobx.IObservableValue<number> = mobx.observable.box(0, {name: "sw-view-width"});
|
2022-11-23 23:45:20 +01:00
|
|
|
height : mobx.IObservableValue<number> = mobx.observable.box(0, {name: "sw-view-height"});
|
|
|
|
setSize_debounced : (width : number, height : number) => void;
|
2022-09-22 02:20:16 +02:00
|
|
|
|
2022-09-04 08:39:50 +02:00
|
|
|
constructor(props : any) {
|
|
|
|
super(props);
|
2022-11-23 23:45:20 +01:00
|
|
|
this.setSize_debounced = debounce(1000, this.setSize.bind(this));
|
2022-10-07 20:32:58 +02:00
|
|
|
this.windowViewRef = React.createRef();
|
2022-09-22 02:20:16 +02:00
|
|
|
}
|
|
|
|
|
2022-11-23 23:45:20 +01:00
|
|
|
setSize(width : number, height : number) : void {
|
2022-09-22 02:20:16 +02:00
|
|
|
mobx.action(() => {
|
|
|
|
this.width.set(width);
|
2022-11-23 23:45:20 +01:00
|
|
|
this.height.set(height);
|
2022-09-22 02:20:16 +02:00
|
|
|
let {sw} = this.props;
|
|
|
|
let cols = widthToCols(width);
|
2022-11-23 23:45:20 +01:00
|
|
|
let rows = termRowsFromHeight(height);
|
|
|
|
if (sw == null || cols == 0 || rows == 0) {
|
2022-09-22 02:20:16 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-11-23 23:45:20 +01:00
|
|
|
sw.termSizeCallback(rows, cols);
|
2022-09-22 02:20:16 +02:00
|
|
|
})();
|
2022-09-04 08:39:50 +02:00
|
|
|
}
|
|
|
|
|
2022-07-12 07:43:58 +02:00
|
|
|
componentDidMount() {
|
2022-10-07 20:32:58 +02:00
|
|
|
let wvElem = this.windowViewRef.current;
|
2022-07-14 09:54:31 +02:00
|
|
|
if (wvElem != null) {
|
2022-09-22 02:20:16 +02:00
|
|
|
let width = wvElem.offsetWidth;
|
2022-11-23 23:45:20 +01:00
|
|
|
let height = wvElem.offsetHeight;
|
|
|
|
this.setSize(width, height);
|
2022-07-14 09:54:31 +02:00
|
|
|
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
|
|
|
|
this.rszObs.observe(wvElem);
|
2022-07-14 08:11:45 +02:00
|
|
|
}
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
2022-07-14 09:54:31 +02:00
|
|
|
if (this.rszObs) {
|
|
|
|
this.rszObs.disconnect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleResize(entries : any) {
|
|
|
|
if (entries.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let entry = entries[0];
|
|
|
|
let width = entry.target.offsetWidth;
|
2022-11-23 23:45:20 +01:00
|
|
|
let height = entry.target.offsetHeight;
|
|
|
|
this.setSize_debounced(width, height);
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
getWindow() : Window {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
2022-07-15 03:41:49 +02:00
|
|
|
let win = GlobalModel.getWindowById(sw.sessionId, sw.windowId);
|
|
|
|
if (win == null) {
|
|
|
|
win = GlobalModel.loadWindow(sw.sessionId, sw.windowId);
|
|
|
|
}
|
|
|
|
return win;
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
|
2022-07-13 08:29:39 +02:00
|
|
|
getWindowViewStyle() : any {
|
2022-07-14 09:54:31 +02:00
|
|
|
return {position: "absolute", width: "100%", height: "100%", overflowX: "hidden"};
|
|
|
|
}
|
|
|
|
|
2022-07-12 07:43:58 +02:00
|
|
|
renderError(message : string) {
|
2022-07-14 08:11:45 +02:00
|
|
|
let {sw} = this.props;
|
2022-07-12 07:43:58 +02:00
|
|
|
return (
|
2022-10-07 20:32:58 +02:00
|
|
|
<div className="window-view" style={this.getWindowViewStyle()} ref={this.windowViewRef} data-windowid={sw.windowId}>
|
2022-07-14 08:11:45 +02:00
|
|
|
<div key="window-tag" className="window-tag">
|
2022-10-11 02:18:40 +02:00
|
|
|
<span>{sw.name.get()}</span>
|
2022-07-14 08:11:45 +02:00
|
|
|
</div>
|
2022-10-08 03:25:47 +02:00
|
|
|
<div key="lines" className="lines"></div>
|
2022-07-14 08:11:45 +02:00
|
|
|
<div key="window-empty" className="window-empty">
|
|
|
|
<div>{message}</div>
|
2022-07-12 07:43:58 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2022-10-08 03:25:47 +02:00
|
|
|
|
2022-06-08 02:25:35 +02:00
|
|
|
render() {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
|
|
|
let win = this.getWindow();
|
2022-07-15 03:41:49 +02:00
|
|
|
if (win == null || !win.loaded.get()) {
|
2022-07-12 07:43:58 +02:00
|
|
|
return this.renderError("(loading)");
|
2022-07-02 22:32:25 +02:00
|
|
|
}
|
2022-07-15 03:41:49 +02:00
|
|
|
if (win.loadError.get() != null) {
|
|
|
|
return this.renderError(sprintf("(%s)", win.loadError.get()));
|
|
|
|
}
|
2022-09-22 02:20:16 +02:00
|
|
|
if (this.width.get() == 0) {
|
2022-07-14 09:54:31 +02:00
|
|
|
return this.renderError("");
|
|
|
|
}
|
2022-07-05 07:18:36 +02:00
|
|
|
let idx = 0;
|
2022-07-05 07:37:45 +02:00
|
|
|
let line : LineType = null;
|
2022-07-14 08:11:45 +02:00
|
|
|
let screen = GlobalModel.getScreenById(sw.sessionId, sw.screenId);
|
|
|
|
let session = GlobalModel.getSessionById(sw.sessionId);
|
2022-09-06 09:14:48 +02:00
|
|
|
let isActive = sw.isActive();
|
2022-10-08 03:25:47 +02:00
|
|
|
let selectedLine = sw.selectedLine.get();
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-10-07 20:32:58 +02:00
|
|
|
<div className="window-view" style={this.getWindowViewStyle()} ref={this.windowViewRef}>
|
2022-09-06 09:14:48 +02:00
|
|
|
<div key="window-tag" className={cn("window-tag", {"is-active": isActive})}>
|
2022-10-11 02:18:40 +02:00
|
|
|
<span>{sw.name.get()}</span>
|
2022-07-14 08:11:45 +02:00
|
|
|
</div>
|
2022-10-08 03:25:47 +02:00
|
|
|
<If condition={win.lines.length > 0}>
|
|
|
|
<LinesView sw={sw} width={this.width.get()} lines={win.lines}/>
|
|
|
|
</If>
|
2022-07-14 08:11:45 +02:00
|
|
|
<If condition={win.lines.length == 0}>
|
|
|
|
<div key="window-empty" className="window-empty">
|
|
|
|
<div><code>[session="{session.name.get()}" screen="{screen.name.get()}" window="{sw.name.get()}"]</code></div>
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-07-12 07:43:58 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-13 08:29:39 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class ScreenView extends React.Component<{screen : Screen}, {}> {
|
|
|
|
render() {
|
|
|
|
let {screen} = this.props;
|
2022-09-04 08:39:50 +02:00
|
|
|
let sw : ScreenWindow = null;
|
|
|
|
if (screen != null) {
|
|
|
|
sw = screen.getActiveSW();
|
|
|
|
}
|
|
|
|
if (screen == null || sw == null) {
|
2022-07-13 08:29:39 +02:00
|
|
|
return (
|
|
|
|
<div className="screen-view">
|
2022-09-04 08:39:50 +02:00
|
|
|
(no screen or window)
|
2022-07-13 08:29:39 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
2022-09-12 05:49:53 +02:00
|
|
|
<div className="screen-view" data-screenid={sw.screenId}>
|
2022-07-14 08:11:45 +02:00
|
|
|
<ScreenWindowView key={sw.windowId} sw={sw}/>
|
2022-07-13 08:29:39 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-07-14 08:11:45 +02:00
|
|
|
class ScreenTabs extends React.Component<{session : Session}, {}> {
|
|
|
|
@boundMethod
|
|
|
|
handleNewScreen() {
|
|
|
|
let {session} = this.props;
|
2022-08-31 02:05:35 +02:00
|
|
|
GlobalCommandRunner.createNewScreen();
|
2022-07-14 08:11:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@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;
|
|
|
|
}
|
2022-08-31 02:05:35 +02:00
|
|
|
GlobalCommandRunner.switchScreen(screenId);
|
2022-07-14 08:11:45 +02:00
|
|
|
}
|
2022-08-09 01:22:36 +02:00
|
|
|
|
|
|
|
handleContextMenu(e : any, screenId : string) : void {
|
|
|
|
e.preventDefault();
|
|
|
|
console.log("handle context menu!", screenId);
|
|
|
|
let model = GlobalModel;
|
2022-08-11 20:49:46 +02:00
|
|
|
model.contextScreen(e, screenId);
|
2022-08-09 01:22:36 +02:00
|
|
|
}
|
2022-08-27 06:43:48 +02:00
|
|
|
|
2022-07-13 08:29:39 +02:00
|
|
|
render() {
|
2022-07-14 08:11:45 +02:00
|
|
|
let {session} = this.props;
|
2022-07-13 08:29:39 +02:00
|
|
|
if (session == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2022-07-14 08:11:45 +02:00
|
|
|
let screen : Screen = null;
|
2022-07-15 03:41:49 +02:00
|
|
|
let index = 0;
|
2022-12-26 21:36:24 +01:00
|
|
|
let showingScreens = [];
|
|
|
|
let activeScreenId = session.activeScreenId.get();
|
|
|
|
for (let screen of session.screens) {
|
|
|
|
if (!screen.archived.get() || activeScreenId == screen.screenId) {
|
|
|
|
showingScreens.push(screen);
|
|
|
|
}
|
|
|
|
}
|
2022-07-13 08:29:39 +02:00
|
|
|
return (
|
|
|
|
<div className="screen-tabs">
|
2022-12-26 21:36:24 +01:00
|
|
|
<For each="screen" index="index" of={showingScreens}>
|
|
|
|
<div key={screen.screenId} className={cn("screen-tab", {"is-active": activeScreenId == screen.screenId, "is-archived": screen.archived.get()}, "color-" + screen.getTabColor())} onClick={() => this.handleSwitchScreen(screen.screenId)} onContextMenu={(event) => this.handleContextMenu(event, screen.screenId)}>
|
|
|
|
<If condition={screen.archived.get()}><i title="archived" className="fa fa-archive"/></If>{screen.name.get()}
|
2022-07-15 03:41:49 +02:00
|
|
|
<If condition={index+1 <= 9}>
|
|
|
|
<div className="tab-index">⌘{index+1}</div>
|
|
|
|
</If>
|
2022-07-14 08:11:45 +02:00
|
|
|
</div>
|
|
|
|
</For>
|
|
|
|
<div key="new-screen" className="screen-tab new-screen" onClick={this.handleNewScreen}>
|
|
|
|
+
|
|
|
|
</div>
|
2022-07-13 08:29:39 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-12 07:43:58 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class SessionView extends React.Component<{}, {}> {
|
|
|
|
render() {
|
|
|
|
let model = GlobalModel;
|
|
|
|
let session = model.getActiveSession();
|
|
|
|
if (session == null) {
|
|
|
|
return <div className="session-view">(no active session)</div>;
|
|
|
|
}
|
2022-07-13 08:29:39 +02:00
|
|
|
let activeScreen = session.getActiveScreen();
|
2022-12-27 19:58:11 +01:00
|
|
|
let cmdInputHeight = model.inputModel.cmdInputHeight.get();
|
|
|
|
if (cmdInputHeight == 0) {
|
|
|
|
cmdInputHeight = 110;
|
|
|
|
}
|
2022-07-12 07:43:58 +02:00
|
|
|
return (
|
2022-09-12 05:49:53 +02:00
|
|
|
<div className="session-view" data-sessionid={session.sessionId}>
|
2022-07-13 08:29:39 +02:00
|
|
|
<ScreenView screen={activeScreen}/>
|
2022-07-14 08:11:45 +02:00
|
|
|
<ScreenTabs session={session}/>
|
2022-12-27 19:58:11 +01:00
|
|
|
<div style={{height: cmdInputHeight}}></div>
|
2022-07-12 02:55:03 +02:00
|
|
|
<CmdInput/>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-14 21:07:31 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-09-22 07:17:04 +02:00
|
|
|
@mobxReact.observer
|
2022-10-01 02:23:28 +02:00
|
|
|
class RemoteStatusLight extends React.Component<{remote : RemoteType}, {}> {
|
2022-09-22 07:17:04 +02:00
|
|
|
render() {
|
2022-10-01 02:23:28 +02:00
|
|
|
let remote = this.props.remote;
|
|
|
|
let status = "error";
|
|
|
|
let wfp = false;
|
|
|
|
if (remote != null) {
|
|
|
|
status = remote.status;
|
|
|
|
wfp = remote.waitingforpassword;
|
|
|
|
}
|
|
|
|
let icon = "fa-circle"
|
|
|
|
if (status == "connecting") {
|
|
|
|
icon = (wfp ? "fa-key" : "fa-refresh");
|
|
|
|
}
|
2022-09-22 07:17:04 +02:00
|
|
|
return (
|
2022-10-01 02:23:28 +02:00
|
|
|
<i className={cn("remote-status fa", icon, "status-" + status)}/>
|
2022-09-22 07:17:04 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-20 22:03:20 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class MainSideBar extends React.Component<{}, {}> {
|
|
|
|
collapsed : mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
toggleCollapsed() {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.collapsed.set(!this.collapsed.get());
|
|
|
|
})();
|
|
|
|
}
|
2022-07-08 22:23:00 +02:00
|
|
|
|
|
|
|
handleSessionClick(sessionId : string) {
|
2022-08-31 02:05:35 +02:00
|
|
|
GlobalCommandRunner.switchSession(sessionId);
|
2022-08-09 01:22:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
handleNewSession() {
|
2022-08-31 02:05:35 +02:00
|
|
|
GlobalCommandRunner.createNewSession();
|
2022-07-08 22:23:00 +02:00
|
|
|
}
|
2022-07-09 10:37:19 +02:00
|
|
|
|
2022-08-18 09:39:06 +02:00
|
|
|
clickRemotes() {
|
2022-10-01 00:42:10 +02:00
|
|
|
GlobalCommandRunner.showAllRemotes();
|
2022-08-18 09:39:06 +02:00
|
|
|
}
|
|
|
|
|
2022-09-14 02:17:52 +02:00
|
|
|
remoteDisplayName(remote : RemoteType) : any {
|
|
|
|
if (!isBlank(remote.remotealias)) {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<span>{remote.remotealias}</span>
|
|
|
|
<span className="small-text"> {remote.remotecanonicalname}</span>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (<span>{remote.remotecanonicalname}</span>);
|
|
|
|
}
|
|
|
|
|
2022-09-14 22:02:33 +02:00
|
|
|
clickRemote(remote : RemoteType) {
|
|
|
|
GlobalCommandRunner.showRemote(remote.remoteid);
|
|
|
|
}
|
|
|
|
|
2022-09-22 07:42:51 +02:00
|
|
|
@boundMethod
|
|
|
|
handleAddRemote() : void {
|
2022-09-30 23:57:23 +02:00
|
|
|
GlobalCommandRunner.openCreateRemote();
|
2022-09-22 07:42:51 +02:00
|
|
|
}
|
|
|
|
|
2022-06-20 22:03:20 +02:00
|
|
|
render() {
|
2022-07-11 23:43:18 +02:00
|
|
|
let model = GlobalModel;
|
2022-07-13 23:16:47 +02:00
|
|
|
let activeSessionId = model.activeSessionId.get();
|
2022-09-30 23:57:23 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2022-10-11 23:57:22 +02:00
|
|
|
let sw : ScreenWindow = null;
|
|
|
|
if (GlobalModel.debugSW.get()) {
|
|
|
|
sw = GlobalModel.getActiveSW();
|
|
|
|
}
|
2022-07-12 02:55:03 +02:00
|
|
|
let session : Session = null;
|
2022-09-14 02:17:52 +02:00
|
|
|
let remotes = model.remotes ?? [];
|
2022-08-17 22:06:47 +02:00
|
|
|
let remote : RemoteType = null;
|
2022-08-27 02:28:56 +02:00
|
|
|
let idx : number = 0;
|
2022-09-22 07:17:04 +02:00
|
|
|
remotes = sortAndFilterRemotes(remotes);
|
2022-12-27 03:50:22 +01:00
|
|
|
let sessionList = [];
|
|
|
|
for (let session of model.sessionList) {
|
|
|
|
if (!session.archived.get() || session.sessionId == activeSessionId) {
|
|
|
|
sessionList.push(session);
|
|
|
|
}
|
|
|
|
}
|
2022-12-29 02:46:15 +01:00
|
|
|
let isCollapsed = this.collapsed.get();
|
2022-06-20 22:03:20 +02:00
|
|
|
return (
|
2022-12-29 02:46:15 +01:00
|
|
|
<div className={cn("main-sidebar", {"collapsed": isCollapsed})}>
|
|
|
|
<h1 className={cn("title", "prompt-logo-small", {"collapsed": isCollapsed})}>
|
|
|
|
{(isCollapsed ? "[p]" : "[prompt]")}
|
|
|
|
</h1>
|
2022-06-20 22:03:20 +02:00
|
|
|
<div className="collapse-container">
|
|
|
|
<div className="arrow-container" onClick={this.toggleCollapsed}>
|
2022-12-29 02:46:15 +01:00
|
|
|
<If condition={!isCollapsed}><i className="fa fa-arrow-left"/></If>
|
|
|
|
<If condition={isCollapsed}><i className="fa fa-arrow-right"/></If>
|
2022-06-20 22:03:20 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="menu">
|
|
|
|
<p className="menu-label">
|
2022-07-08 22:01:37 +02:00
|
|
|
Private Sessions
|
2022-06-20 22:03:20 +02:00
|
|
|
</p>
|
|
|
|
<ul className="menu-list">
|
2022-07-12 02:55:03 +02:00
|
|
|
<If condition={!model.sessionListLoaded.get()}>
|
2022-12-22 07:05:05 +01:00
|
|
|
<li className="menu-loading-message"><a>(loading)</a></li>
|
2022-07-11 23:43:18 +02:00
|
|
|
</If>
|
2022-07-12 02:55:03 +02:00
|
|
|
<If condition={model.sessionListLoaded.get()}>
|
2022-12-27 03:50:22 +01:00
|
|
|
<For each="session" index="idx" of={sessionList}>
|
2022-08-27 02:28:56 +02:00
|
|
|
<li key={session.sessionId}><a className={cn({"is-active": activeSessionId == session.sessionId})} onClick={() => this.handleSessionClick(session.sessionId)}>
|
2022-12-27 03:50:22 +01:00
|
|
|
<If condition={!session.archived.get()}>
|
|
|
|
<span className="session-num">{idx+1} </span>
|
|
|
|
</If>
|
|
|
|
<If condition={session.archived.get()}>
|
|
|
|
<i title="archived" className="fa fa-archive"/>
|
|
|
|
</If>
|
2022-08-27 02:28:56 +02:00
|
|
|
{session.name.get()}
|
|
|
|
</a></li>
|
2022-07-11 23:43:18 +02:00
|
|
|
</For>
|
2022-09-22 07:42:51 +02:00
|
|
|
<li className="new-session"><a onClick={() => this.handleNewSession()}><i className="fa fa-plus"/> New Session</a></li>
|
2022-07-11 23:43:18 +02:00
|
|
|
</If>
|
2022-06-20 22:03:20 +02:00
|
|
|
</ul>
|
|
|
|
<div className="spacer"></div>
|
2022-10-11 23:57:22 +02:00
|
|
|
<If condition={GlobalModel.debugSW.get() && sw != null}>
|
|
|
|
<div>
|
|
|
|
focus={sw.focusType.get()}<br/>
|
2022-12-27 03:50:22 +01:00
|
|
|
sline={sw.selectedLine.get()}<br/>
|
|
|
|
termfocus={sw.termLineNumFocus.get()}<br/>
|
2022-10-11 23:57:22 +02:00
|
|
|
</div>
|
|
|
|
</If>
|
2022-12-12 21:11:02 +01:00
|
|
|
<p className="menu-label">
|
|
|
|
<a onClick={() => this.clickRemotes()}>Links</a>
|
|
|
|
</p>
|
|
|
|
<ul className="menu-list">
|
|
|
|
<li>
|
|
|
|
<a target="_blank" href="https://docs.getprompt.dev/"><i className="fa fa-book"/> documentation</a>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<a target="_blank" href="https://discord.gg/XfvZ334gwU"><i className="fa fa-comments"/> discord</a>
|
|
|
|
</li>
|
|
|
|
</ul>
|
2022-06-20 22:03:20 +02:00
|
|
|
<p className="menu-label">
|
2022-11-29 03:08:19 +01:00
|
|
|
<a onClick={() => this.clickRemotes()}>Connections</a>
|
2022-06-20 22:03:20 +02:00
|
|
|
</p>
|
2022-09-14 21:07:31 +02:00
|
|
|
<ul className="menu-list remotes-menu-list">
|
2022-08-17 22:06:47 +02:00
|
|
|
<For each="remote" of={remotes}>
|
2022-09-30 23:57:23 +02:00
|
|
|
<li key={remote.remoteid} className={cn("remote-menu-item")}><a className={cn({"is-active": (remote.remoteid == activeRemoteId)})} onClick={() => this.clickRemote(remote)}>
|
2022-10-01 02:23:28 +02:00
|
|
|
<RemoteStatusLight remote={remote}/>
|
2022-09-14 02:17:52 +02:00
|
|
|
{this.remoteDisplayName(remote)}
|
|
|
|
</a></li>
|
2022-08-17 22:06:47 +02:00
|
|
|
</For>
|
2022-09-22 07:42:51 +02:00
|
|
|
<li key="add-remote" className="add-remote">
|
2022-11-29 03:08:19 +01:00
|
|
|
<a onClick={() => this.handleAddRemote()}><i className="fa fa-plus"/> Add Connection</a>
|
2022-09-22 07:42:51 +02:00
|
|
|
</li>
|
2022-06-20 22:03:20 +02:00
|
|
|
</ul>
|
|
|
|
<div className="bottom-spacer"></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-22 07:17:04 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-08-18 09:39:06 +02:00
|
|
|
@mobxReact.observer
|
2022-10-28 23:17:45 +02:00
|
|
|
class DisconnectedModal extends React.Component<{}, {}> {
|
|
|
|
logRef : any = React.createRef();
|
|
|
|
showLog : mobx.IObservableValue<boolean> = mobx.observable.box(false)
|
2022-09-22 07:42:51 +02:00
|
|
|
|
2022-10-28 23:17:45 +02:00
|
|
|
@boundMethod
|
|
|
|
restartServer() {
|
2022-10-30 20:53:39 +01:00
|
|
|
GlobalModel.restartLocalServer();
|
2022-09-22 07:42:51 +02:00
|
|
|
}
|
2022-08-18 09:39:06 +02:00
|
|
|
|
|
|
|
@boundMethod
|
2022-10-28 23:17:45 +02:00
|
|
|
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;
|
|
|
|
}
|
2022-08-18 09:39:06 +02:00
|
|
|
}
|
2022-09-22 08:31:03 +02:00
|
|
|
|
|
|
|
@boundMethod
|
2022-10-28 23:17:45 +02:00
|
|
|
handleShowLog() : void {
|
2022-09-22 08:31:03 +02:00
|
|
|
mobx.action(() => {
|
2022-10-28 23:17:45 +02:00
|
|
|
this.showLog.set(!this.showLog.get());
|
2022-09-22 08:31:03 +02:00
|
|
|
})();
|
|
|
|
}
|
2022-08-18 09:39:06 +02:00
|
|
|
|
|
|
|
render() {
|
|
|
|
let model = GlobalModel;
|
2022-10-28 23:17:45 +02:00
|
|
|
let logLine : string = null;
|
|
|
|
let idx : number = 0;
|
2022-08-18 09:39:06 +02:00
|
|
|
return (
|
2022-10-28 23:17:45 +02:00
|
|
|
<div className="sc-modal disconnected-modal modal is-active">
|
|
|
|
<div className="modal-background"></div>
|
2022-08-18 09:39:06 +02:00
|
|
|
<div className="modal-content message">
|
|
|
|
<div className="message-header">
|
2022-11-29 03:08:19 +01:00
|
|
|
<p>Prompt Client Disconnected</p>
|
2022-08-18 09:39:06 +02:00
|
|
|
</div>
|
2022-10-28 23:17:45 +02:00
|
|
|
<If condition={this.showLog.get()}>
|
|
|
|
<div className="message-content">
|
|
|
|
<div className="ws-log" ref={this.logRef}>
|
|
|
|
<For each="logLine" index="idx" of={GlobalModel.ws.wsLog}>
|
|
|
|
<div key={idx} className="ws-logline">{logLine}</div>
|
2022-08-18 09:39:06 +02:00
|
|
|
</For>
|
2022-10-28 23:17:45 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-09-22 07:42:51 +02:00
|
|
|
<div className="message-footer">
|
2022-10-28 23:17:45 +02:00
|
|
|
<div className="footer-text-link" style={{marginLeft: 10}} onClick={this.handleShowLog}>
|
|
|
|
<If condition={!this.showLog.get()}>
|
|
|
|
<i className="fa fa-plus"/> Show Log
|
|
|
|
</If>
|
|
|
|
<If condition={this.showLog.get()}>
|
|
|
|
<i className="fa fa-minus"/> Hide Log
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
<div className="spacer"/>
|
|
|
|
<button onClick={this.tryReconnect} className="button">
|
2022-08-18 09:39:06 +02:00
|
|
|
<span className="icon">
|
2022-10-28 23:17:45 +02:00
|
|
|
<i className="fa fa-refresh"/>
|
2022-08-18 09:39:06 +02:00
|
|
|
</span>
|
2022-10-28 23:17:45 +02:00
|
|
|
<span>Try Reconnect</span>
|
|
|
|
</button>
|
|
|
|
<button onClick={this.restartServer} className="button is-danger" style={{marginLeft: 10}}>
|
|
|
|
<span className="icon">
|
|
|
|
<i className="fa fa-exclamation-triangle"/>
|
|
|
|
</span>
|
|
|
|
<span>Restart Server</span>
|
2022-08-18 09:39:06 +02:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-16 03:12:22 +02:00
|
|
|
@mobxReact.observer
|
2022-06-17 00:51:17 +02:00
|
|
|
class Main extends React.Component<{}, {}> {
|
2022-06-16 03:12:22 +02:00
|
|
|
constructor(props : any) {
|
|
|
|
super(props);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
return (
|
2022-06-20 22:03:20 +02:00
|
|
|
<div id="main">
|
|
|
|
<div className="main-content">
|
|
|
|
<MainSideBar/>
|
2022-07-11 23:43:18 +02:00
|
|
|
<SessionView/>
|
2022-06-20 22:03:20 +02:00
|
|
|
</div>
|
2022-10-30 21:06:25 +01:00
|
|
|
<If condition={!GlobalModel.ws.open.get() || !GlobalModel.localServerRunning.get()}>
|
2022-10-28 23:17:45 +02:00
|
|
|
<DisconnectedModal/>
|
2022-09-22 08:31:03 +02:00
|
|
|
</If>
|
2022-06-16 03:12:22 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-08 02:25:35 +02:00
|
|
|
export {Main};
|
2022-07-07 22:27:44 +02:00
|
|
|
|