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-09-22 02:20:16 +02:00
|
|
|
import {debounce} 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-08-31 21:00:53 +02:00
|
|
|
import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts} from "./types";
|
2022-06-18 02:54:14 +02:00
|
|
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
2022-09-22 02:20:16 +02:00
|
|
|
import {GlobalModel, GlobalCommandRunner, Session, Cmd, Window, Screen, ScreenWindow, riToRPtr, widthToCols} from "./model";
|
2022-06-18 02:54:14 +02:00
|
|
|
|
|
|
|
dayjs.extend(localizedFormat)
|
2022-06-13 20:12:39 +02:00
|
|
|
|
2022-09-15 09:18:20 +02:00
|
|
|
const CellHeightPx = 16;
|
|
|
|
const CellWidthPx = 8;
|
2022-09-16 02:10:02 +02:00
|
|
|
const RemotePtyRows = 8;
|
2022-09-15 09:18:20 +02:00
|
|
|
const RemotePtyCols = 80;
|
|
|
|
|
2022-08-13 03:34:56 +02:00
|
|
|
type InterObsValue = {
|
|
|
|
sessionid : string,
|
|
|
|
windowid : string,
|
|
|
|
lineid : string,
|
|
|
|
cmdid : string,
|
|
|
|
visible : mobx.IObservableValue<boolean>,
|
|
|
|
timeoutid? : any,
|
|
|
|
};
|
|
|
|
|
|
|
|
let globalLineWeakMap = new WeakMap<any, InterObsValue>();
|
|
|
|
|
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 windowLinesDOMId(windowid : string) {
|
|
|
|
return "window-lines-" + windowid;
|
|
|
|
}
|
|
|
|
|
|
|
|
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-08-13 03:34:56 +02:00
|
|
|
function interObsCallback(entries) {
|
|
|
|
let now = Date.now();
|
|
|
|
entries.forEach((entry) => {
|
|
|
|
let line = globalLineWeakMap.get(entry.target);
|
|
|
|
if ((line.timeoutid != null) && (line.visible.get() == entry.isIntersecting)) {
|
|
|
|
clearTimeout(line.timeoutid);
|
|
|
|
line.timeoutid = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (line.visible.get() != entry.isIntersecting && line.timeoutid == null) {
|
|
|
|
line.timeoutid = setTimeout(() => {
|
|
|
|
line.timeoutid = null;
|
|
|
|
mobx.action(() => {
|
|
|
|
line.visible.set(entry.isIntersecting);
|
|
|
|
})();
|
|
|
|
}, 250);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCwdStr(remote : RemoteType, state : RemoteStateType) : string {
|
|
|
|
if ((state == null || state.cwd == null) && remote != null) {
|
|
|
|
return "~";
|
|
|
|
}
|
|
|
|
let cwd = "(unknown)";
|
|
|
|
if (state && state.cwd) {
|
|
|
|
cwd = state.cwd;
|
|
|
|
}
|
|
|
|
if (remote && remote.remotevars.home) {
|
|
|
|
cwd = replaceHomePath(cwd, remote.remotevars.home)
|
|
|
|
}
|
|
|
|
return cwd;
|
|
|
|
}
|
|
|
|
|
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-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-06-08 02:25:35 +02:00
|
|
|
render() {
|
|
|
|
let line = this.props.line;
|
2022-06-18 02:54:14 +02:00
|
|
|
let formattedTime = getLineDateStr(line.ts);
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-08-17 00:59:28 +02:00
|
|
|
<div className="line line-text" data-lineid={line.lineid} data-windowid={line.windowid}>
|
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
|
|
|
|
class Prompt extends React.Component<{rptr : RemotePtrType, rstate : RemoteStateType}, {}> {
|
|
|
|
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);
|
|
|
|
let cwd = getCwdStr(remote, this.props.rstate);
|
|
|
|
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-09-21 01:51:42 +02:00
|
|
|
class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width : number, interObs : IntersectionObserver, initVis : boolean}, {}> {
|
2022-07-13 09:44:19 +02:00
|
|
|
termLoaded : mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
2022-08-13 03:34:56 +02:00
|
|
|
lineRef : React.RefObject<any> = React.createRef();
|
|
|
|
iobsVal : InterObsValue = null;
|
|
|
|
autorunDisposer : () => void = null;
|
2022-07-13 09:44:19 +02:00
|
|
|
|
2022-06-16 09:31:54 +02:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2022-08-13 03:34:56 +02:00
|
|
|
|
|
|
|
let line = props.line;
|
|
|
|
let ival : InterObsValue = {
|
|
|
|
sessionid: line.sessionid,
|
|
|
|
windowid: line.windowid,
|
|
|
|
lineid: line.lineid,
|
|
|
|
cmdid: line.cmdid,
|
|
|
|
visible: mobx.observable.box(this.props.initVis),
|
|
|
|
};
|
|
|
|
this.iobsVal = ival;
|
2022-06-16 09:31:54 +02:00
|
|
|
}
|
2022-08-13 03:34:56 +02:00
|
|
|
|
|
|
|
visibilityChanged(vis : boolean) : void {
|
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) {
|
|
|
|
this.unloadTerminal();
|
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;
|
|
|
|
}
|
|
|
|
sw.connectElem(termElem, cmd, this.props.width);
|
|
|
|
mobx.action(() => this.termLoaded.set(true))();
|
|
|
|
}
|
2022-09-08 22:44:24 +02:00
|
|
|
|
|
|
|
unloadTerminal() : void {
|
|
|
|
let {sw, line} = this.props;
|
|
|
|
let model = GlobalModel;
|
|
|
|
let cmd = model.getCmd(line);
|
|
|
|
if (cmd == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let termId = "term-" + getLineId(line);
|
|
|
|
sw.disconnectElem(line.cmdid);
|
|
|
|
mobx.action(() => this.termLoaded.set(false))();
|
|
|
|
let termElem = document.getElementById(termId);
|
|
|
|
if (termElem != null) {
|
|
|
|
termElem.replaceChildren();
|
|
|
|
}
|
|
|
|
}
|
2022-08-13 03:34:56 +02:00
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
let {line} = this.props;
|
|
|
|
if (this.lineRef.current == null || this.props.interObs == null) {
|
|
|
|
console.log("LineCmd lineRef current is null or interObs is null", line, this.lineRef.current, this.props.interObs);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
globalLineWeakMap.set(this.lineRef.current, this.iobsVal);
|
|
|
|
this.props.interObs.observe(this.lineRef.current);
|
|
|
|
this.autorunDisposer = mobx.autorun(() => {
|
|
|
|
let vis = this.iobsVal.visible.get();
|
|
|
|
this.visibilityChanged(vis);
|
|
|
|
});
|
2022-07-12 02:55:03 +02:00
|
|
|
}
|
2022-06-15 01:02:20 +02:00
|
|
|
}
|
|
|
|
|
2022-07-12 07:43:58 +02:00
|
|
|
componentWillUnmount() {
|
2022-07-13 09:44:19 +02:00
|
|
|
let {sw, line} = this.props;
|
2022-07-12 07:43:58 +02:00
|
|
|
let model = GlobalModel;
|
2022-08-13 03:34:56 +02:00
|
|
|
if (this.termLoaded.get()) {
|
|
|
|
sw.disconnectElem(line.cmdid);
|
|
|
|
}
|
|
|
|
if (this.lineRef.current != null && this.props.interObs != null) {
|
|
|
|
this.props.interObs.unobserve(this.lineRef.current);
|
|
|
|
}
|
|
|
|
if (this.autorunDisposer != null) {
|
|
|
|
this.autorunDisposer();
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-08-11 03:35:18 +02:00
|
|
|
let cwd = getCwdStr(remote, cmd.getRemoteState());
|
2022-07-07 22:27:44 +02:00
|
|
|
return (
|
|
|
|
<div className="metapart-mono cmdtext">
|
2022-08-24 22:19:59 +02:00
|
|
|
<Prompt rptr={cmd.remote} rstate={cmd.getRemoteState()}/> {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) {
|
|
|
|
termWrap.terminal.focus();
|
|
|
|
}
|
|
|
|
}
|
2022-06-08 02:25:35 +02:00
|
|
|
|
|
|
|
render() {
|
2022-08-13 03:34:56 +02:00
|
|
|
let {sw, line, width} = 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-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 (
|
|
|
|
<div className="line line-invalid" id={"line-" + getLineId(line)} ref={this.lineRef}>
|
|
|
|
[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-09-15 09:18:20 +02:00
|
|
|
let termWidth = Math.max(Math.trunc((width - 20)/CellWidthPx), 10);
|
2022-08-13 03:34:56 +02:00
|
|
|
let usedRows = sw.getUsedRows(cmd, width);
|
2022-09-15 09:18:20 +02:00
|
|
|
let totalHeight = CellHeightPx * usedRows;
|
2022-07-12 02:55:03 +02:00
|
|
|
let remote = model.getRemote(cmd.remoteId);
|
|
|
|
let status = cmd.getStatus();
|
|
|
|
let termOpts = cmd.getTermOpts();
|
2022-08-13 03:34:56 +02:00
|
|
|
let isFocused = sw.getIsFocused(line.cmdid);
|
2022-09-21 01:51:42 +02:00
|
|
|
let lineNumStr = (line.linenumtemp ? "~" : "") + String(line.linenum);
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-08-17 00:59:28 +02:00
|
|
|
<div className={cn("line", "line-cmd", {"focus": isFocused})} id={"line-" + getLineId(line)} ref={this.lineRef} style={{position: "relative"}} data-lineid={line.lineid} data-windowid={line.windowid} data-cmdid={line.cmdid}>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className="line-header">
|
2022-09-21 01:51:42 +02:00
|
|
|
<div className={cn("avatar", "num-"+lineNumStr.length, "status-" + status, {"ephemeral": line.ephemeral})} onClick={this.doRefresh}>
|
|
|
|
{lineNumStr}
|
2022-08-20 01:35:38 +02:00
|
|
|
<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>
|
2022-06-18 02:54:14 +02:00
|
|
|
</div>
|
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>
|
|
|
|
</div>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className={cn("terminal-wrapper", {"focus": isFocused})} style={{overflowY: "hidden"}}>
|
2022-08-25 04:00:03 +02:00
|
|
|
<If condition={!isFocused}>
|
|
|
|
<div className="term-block" onClick={this.clickTermBlock}></div>
|
|
|
|
</If>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className="terminal" id={"term-" + getLineId(line)} data-cmdid={line.cmdid} style={{height: totalHeight}}></div>
|
2022-08-13 03:34:56 +02:00
|
|
|
<If condition={!termLoaded}><div style={{position: "absolute", top: 60, left: 30}}>(loading)</div></If>
|
2022-07-14 09:54:31 +02:00
|
|
|
</div>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-09-21 01:51:42 +02:00
|
|
|
class Line extends React.Component<{sw : ScreenWindow, line : LineType, width : number, interObs : IntersectionObserver, initVis : boolean}, {}> {
|
2022-06-08 02:25:35 +02:00
|
|
|
render() {
|
|
|
|
let line = this.props.line;
|
|
|
|
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-08-25 04:00:03 +02:00
|
|
|
|
2022-08-24 02:27:12 +02:00
|
|
|
componentDidMount() {
|
|
|
|
let input = document.getElementById("main-cmd-input");
|
|
|
|
if (input != null) {
|
|
|
|
input.focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-12 20:44:29 +02:00
|
|
|
isModKeyPress(e : any) {
|
|
|
|
return e.code.match(/^(Control|Meta|Alt|Shift)(Left|Right)$/);
|
|
|
|
}
|
|
|
|
|
|
|
|
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-08-12 20:44:29 +02:00
|
|
|
if (this.isModKeyPress(e)) {
|
|
|
|
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-08-29 20:23:20 +02: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-08-29 20:23:20 +02: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-08-30 21:22:42 +02:00
|
|
|
GlobalModel.inputModel.toggleInfoMsg();
|
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
|
|
|
}
|
|
|
|
else {
|
|
|
|
let win = GlobalModel.getActiveWindow();
|
|
|
|
if (win == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let id = windowLinesDOMId(win.windowId);
|
|
|
|
let div = document.getElementById(id);
|
2022-08-30 00:42:50 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
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
|
|
|
console.log("meta-r");
|
|
|
|
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();
|
|
|
|
inputModel.giveFocus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
handleHistoryFocus(e : any) {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
if (!inputModel.historyShow.get()) {
|
|
|
|
e.preventDefault();
|
|
|
|
inputModel.giveFocus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-25 04:00:03 +02:00
|
|
|
render() {
|
|
|
|
let model = GlobalModel;
|
|
|
|
let inputModel = model.inputModel;
|
|
|
|
let curLine = inputModel.getCurLine();
|
|
|
|
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-08-25 04:00:03 +02:00
|
|
|
return (
|
2022-08-31 08:12:37 +02:00
|
|
|
<div className="control cmd-input-control is-expanded">
|
|
|
|
<textarea spellCheck="false" id="main-cmd-input" onFocus={this.handleMainFocus} rows={displayLines} value={curLine} onKeyDown={this.onKeyDown} onChange={this.onChange} className={cn("textarea", {"display-disabled": disabled})}></textarea>
|
|
|
|
<input spellCheck="false" className="history-input" type="text" onFocus={this.handleHistoryFocus} onKeyDown={this.onHistoryKeyDown} onChange={this.handleHistoryInput} value={inputModel.historyQueryOpts.get().queryStr}/>
|
|
|
|
</div>
|
2022-08-25 04:00:03 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-15 02:14:27 +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-15 09:18:20 +02:00
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
clickTermBlock(e : any) {
|
|
|
|
let inputModel = GlobalModel.inputModel;
|
|
|
|
if (inputModel.remoteTermWrap != null) {
|
|
|
|
inputModel.remoteTermWrap.terminal.focus();
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
|
|
|
|
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-09-22 08:31:03 +02:00
|
|
|
getRemoteTypeStr(remote : RemoteType) : string {
|
|
|
|
let mshellStr = "";
|
|
|
|
if (!isBlank(remote.mshellversion)) {
|
|
|
|
mshellStr = "mshell=" + remote.mshellversion;
|
|
|
|
}
|
|
|
|
if (!isBlank(remote.uname)) {
|
|
|
|
if (mshellStr != "") {
|
|
|
|
mshellStr += " ";
|
|
|
|
}
|
|
|
|
mshellStr += "uname=\"" + remote.uname + "\"";
|
|
|
|
}
|
|
|
|
if (mshellStr == "") {
|
|
|
|
return remote.remotetype;
|
|
|
|
}
|
|
|
|
return remote.remotetype + " (" + mshellStr + ")";
|
|
|
|
}
|
|
|
|
|
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-15 09:18:20 +02:00
|
|
|
let ptyRemoteId = (infoMsg == null ? null : infoMsg.ptyremoteid);
|
|
|
|
let isTermFocused = (inputModel.remoteTermWrap == null ? false : inputModel.remoteTermWrap.isFocused.get());
|
2022-09-15 09:37:52 +02:00
|
|
|
let remote : RemoteType;
|
|
|
|
if (ptyRemoteId != null) {
|
|
|
|
remote = GlobalModel.getRemote(ptyRemoteId);
|
|
|
|
}
|
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}>
|
|
|
|
<div className="info-title">
|
|
|
|
{infoMsg.infotitle}
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<If condition={infoMsg && infoMsg.infomsg != null}>
|
|
|
|
<div className="info-msg">
|
|
|
|
{infoMsg.infomsg}
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<If condition={infoMsg && infoMsg.infolines != null}>
|
|
|
|
<div className="info-lines">
|
|
|
|
<For index="idx" each="line" of={infoMsg.infolines}>
|
|
|
|
<div key={idx}>{line == "" ? " " : line}</div>
|
|
|
|
</For>
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-09-15 09:37:52 +02:00
|
|
|
<If condition={ptyRemoteId != null && remote != null}>
|
2022-09-16 02:10:02 +02:00
|
|
|
<div className="info-remote">
|
|
|
|
<div className="remote-field">
|
|
|
|
<div className="remote-field-def"> remoteid</div>
|
|
|
|
<div className="remote-field-val">{remote.remoteid}</div>
|
|
|
|
</div>
|
|
|
|
<div className="remote-field">
|
|
|
|
<div className="remote-field-def"> type</div>
|
2022-09-22 08:31:03 +02:00
|
|
|
<div className="remote-field-val">{this.getRemoteTypeStr(remote)}</div>
|
2022-09-16 02:10:02 +02:00
|
|
|
</div>
|
|
|
|
<div className="remote-field">
|
|
|
|
<div className="remote-field-def"> alias</div>
|
2022-09-21 01:42:41 +02:00
|
|
|
<div className="remote-field-val">{isBlank(remote.remotealias) ? "-" : remote.remotealias}</div>
|
2022-09-16 02:10:02 +02:00
|
|
|
</div>
|
|
|
|
<div className="remote-field">
|
|
|
|
<div className="remote-field-def"> canonicalname</div>
|
|
|
|
<div className="remote-field-val">{remote.remotecanonicalname}</div>
|
|
|
|
</div>
|
|
|
|
<div className="remote-field">
|
|
|
|
<div className="remote-field-def"> connectmode</div>
|
|
|
|
<div className="remote-field-val">{remote.connectmode}</div>
|
|
|
|
</div>
|
|
|
|
<div className="remote-field">
|
|
|
|
<div className="remote-field-def"> status</div>
|
|
|
|
<div className="remote-field-val">{remote.status} | {this.renderConnectButton(remote)}</div>
|
|
|
|
</div>
|
|
|
|
<If condition={!isBlank(remote.errorstr)}>
|
|
|
|
<div className="remote-field">
|
|
|
|
<div className="remote-field-def"> error</div>
|
|
|
|
<div className="remote-field-val">{remote.errorstr}</div>
|
|
|
|
</div>
|
2022-09-15 09:37:52 +02:00
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-09-16 21:02:46 +02:00
|
|
|
<div className={cn("terminal-wrapper", {"focus": isTermFocused}, (remote != null ? "status-" + remote.status : null))} style={{overflowY: "hidden", display: (ptyRemoteId == null ? "none" : "block"), width: CellWidthPx*RemotePtyCols+15}}>
|
2022-09-15 09:18:20 +02:00
|
|
|
<If condition={!isTermFocused}>
|
|
|
|
<div className="term-block" onClick={this.clickTermBlock}></div>
|
|
|
|
</If>
|
2022-09-16 21:02:46 +02:00
|
|
|
<If condition={inputModel.showNoInputMsg.get()}>
|
|
|
|
<div className="term-tag">input is only allowed while status is 'connecting'</div>
|
|
|
|
</If>
|
2022-09-15 09:18:20 +02:00
|
|
|
<div className="terminal" id="term-remote" data-remoteid={ptyRemoteId} style={{height: CellHeightPx*RemotePtyRows}}></div>
|
|
|
|
</div>
|
2022-09-15 02:14:27 +02:00
|
|
|
<If condition={infoMsg && infoMsg.infocomps != null && infoMsg.infocomps.length > 0}>
|
|
|
|
<div className="info-comps">
|
|
|
|
<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}>
|
|
|
|
<div className="info-error">
|
|
|
|
[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-09-17 01:37:54 +02:00
|
|
|
@boundMethod
|
|
|
|
onInfoToggle() : void {
|
|
|
|
GlobalModel.inputModel.toggleInfoMsg();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
let remoteState : RemoteStateType = null;
|
|
|
|
if (ri != null) {
|
|
|
|
remote = GlobalModel.getRemote(ri.remoteid);
|
|
|
|
remoteState = ri.state;
|
|
|
|
}
|
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-17 01:37:54 +02:00
|
|
|
let hasInfo = (inputModel.infoMsg.get() != null);
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-08-31 21:00:53 +02:00
|
|
|
<div className={cn("box cmd-input has-background-black", {"has-info": infoShow}, {"has-history": historyShow})}>
|
2022-09-17 01:37:54 +02:00
|
|
|
<div onClick={this.onInfoToggle} className="input-minmax-control">
|
|
|
|
<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-09-15 02:14:27 +02:00
|
|
|
<InfoMsg/>
|
2022-06-08 02:25:35 +02:00
|
|
|
<div className="cmd-input-context">
|
|
|
|
<div className="has-text-white">
|
2022-08-24 22:19:59 +02:00
|
|
|
<Prompt rptr={rptr} rstate={remoteState}/>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="cmd-input-field field has-addons">
|
|
|
|
<div className="control cmd-quick-context">
|
2022-08-24 22:19:59 +02:00
|
|
|
<div className="button is-static">{remoteStr}</div>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
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>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-04 08:39:50 +02:00
|
|
|
// sw is not null
|
2022-06-08 02:25:35 +02:00
|
|
|
@mobxReact.observer
|
2022-07-13 08:29:39 +02:00
|
|
|
class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
2022-07-12 07:43:58 +02:00
|
|
|
mutObs : any;
|
2022-08-13 03:34:56 +02:00
|
|
|
rszObs : any;
|
|
|
|
interObs : IntersectionObserver;
|
2022-07-14 08:11:45 +02:00
|
|
|
randomId : string;
|
2022-08-11 19:41:08 +02:00
|
|
|
lastHeight : number = null;
|
2022-06-18 02:54:14 +02:00
|
|
|
|
2022-09-22 02:20:16 +02:00
|
|
|
width : mobx.IObservableValue<number> = mobx.observable.box(0);
|
|
|
|
setWidth_debounced : (width : number) => void;
|
|
|
|
|
2022-09-04 08:39:50 +02:00
|
|
|
constructor(props : any) {
|
|
|
|
super(props);
|
2022-09-22 02:20:16 +02:00
|
|
|
this.setWidth_debounced = debounce(1000, this.setWidth.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
setWidth(width : number) : void {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.width.set(width);
|
|
|
|
let {sw} = this.props;
|
|
|
|
let cols = widthToCols(width);
|
|
|
|
if (sw == null || cols == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sw.colsCallback(cols);
|
|
|
|
})();
|
2022-09-04 08:39:50 +02:00
|
|
|
}
|
|
|
|
|
2022-07-14 08:11:45 +02:00
|
|
|
scrollToBottom(reason : string) {
|
2022-07-14 09:54:31 +02:00
|
|
|
let elem = document.getElementById(this.getLinesDOMId());
|
2022-07-14 08:11:45 +02:00
|
|
|
if (elem == null) {
|
|
|
|
return;
|
|
|
|
}
|
2022-07-12 07:43:58 +02:00
|
|
|
let oldST = elem.scrollTop;
|
|
|
|
elem.scrollTop = elem.scrollHeight;
|
|
|
|
// console.log("scroll-elem", oldST, elem.scrollHeight, elem.scrollTop, elem.scrollLeft, elem);
|
|
|
|
}
|
|
|
|
|
2022-06-18 02:54:14 +02:00
|
|
|
@boundMethod
|
|
|
|
scrollHandler(event : any) {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
2022-06-18 02:54:14 +02:00
|
|
|
let target = event.target;
|
|
|
|
let atBottom = (target.scrollTop + 30 > (target.scrollHeight - target.offsetHeight));
|
2022-09-04 08:39:50 +02:00
|
|
|
if (sw.shouldFollow.get() != atBottom) {
|
2022-08-13 03:34:56 +02:00
|
|
|
mobx.action(() => sw.shouldFollow.set(atBottom))();
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
2022-08-25 04:00:03 +02:00
|
|
|
// console.log("scroll-handler (sw)>", atBottom, target.scrollTop, target.scrollHeight, event);
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
2022-07-14 09:54:31 +02:00
|
|
|
let elem = document.getElementById(this.getLinesDOMId());
|
|
|
|
if (elem != null) {
|
|
|
|
this.mutObs = new MutationObserver(this.handleDomMutation.bind(this));
|
|
|
|
this.mutObs.observe(elem, {childList: true});
|
|
|
|
elem.addEventListener("termresize", this.handleTermResize);
|
|
|
|
let {sw} = this.props;
|
2022-09-04 08:39:50 +02:00
|
|
|
if (sw.shouldFollow.get()) {
|
2022-07-14 09:54:31 +02:00
|
|
|
setTimeout(() => this.scrollToBottom("mount"), 0);
|
|
|
|
}
|
2022-08-13 03:34:56 +02:00
|
|
|
this.interObs = new IntersectionObserver(interObsCallback, {
|
|
|
|
root: elem,
|
|
|
|
rootMargin: "800px",
|
|
|
|
threshold: 0.0,
|
|
|
|
});
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
2022-07-14 09:54:31 +02:00
|
|
|
let wvElem = document.getElementById(this.getWindowViewDOMId());
|
|
|
|
if (wvElem != null) {
|
2022-09-22 02:20:16 +02:00
|
|
|
let width = wvElem.offsetWidth;
|
|
|
|
this.setWidth(width);
|
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 08:11:45 +02:00
|
|
|
if (this.mutObs) {
|
|
|
|
this.mutObs.disconnect();
|
|
|
|
}
|
2022-07-14 09:54:31 +02:00
|
|
|
if (this.rszObs) {
|
|
|
|
this.rszObs.disconnect();
|
|
|
|
}
|
2022-08-13 03:34:56 +02:00
|
|
|
if (this.interObs) {
|
|
|
|
this.interObs.disconnect();
|
|
|
|
}
|
2022-07-14 09:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
handleResize(entries : any) {
|
|
|
|
if (entries.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let entry = entries[0];
|
|
|
|
let width = entry.target.offsetWidth;
|
2022-09-22 02:20:16 +02:00
|
|
|
this.setWidth_debounced(width);
|
2022-08-11 19:41:08 +02:00
|
|
|
if (this.lastHeight == null) {
|
|
|
|
this.lastHeight = entry.target.offsetHeight;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this.lastHeight != entry.target.offsetHeight) {
|
|
|
|
this.lastHeight = entry.target.offsetHeight;
|
|
|
|
this.doConditionalScrollToBottom("resize-height");
|
|
|
|
}
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
handleDomMutation(mutations, mutObs) {
|
2022-08-11 19:41:08 +02:00
|
|
|
this.doConditionalScrollToBottom("mut");
|
|
|
|
}
|
|
|
|
|
|
|
|
doConditionalScrollToBottom(reason : string) {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
2022-09-04 08:39:50 +02:00
|
|
|
if (sw.shouldFollow.get()) {
|
2022-08-11 19:41:08 +02:00
|
|
|
setTimeout(() => this.scrollToBottom(reason), 0);
|
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-14 09:54:31 +02:00
|
|
|
getLinesDOMId() {
|
2022-08-29 22:54:11 +02:00
|
|
|
return windowLinesDOMId(this.getWindowId());
|
2022-06-18 02:54:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
2022-07-12 07:43:58 +02:00
|
|
|
handleTermResize(e : any) {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
2022-09-04 08:39:50 +02:00
|
|
|
if (sw.shouldFollow.get()) {
|
2022-07-14 08:11:45 +02:00
|
|
|
setTimeout(() => this.scrollToBottom("termresize"), 0);
|
2022-06-18 02:54:14 +02:00
|
|
|
}
|
|
|
|
}
|
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 {width: "100%", height: "100%"};
|
|
|
|
return {position: "absolute", width: "100%", height: "100%", overflowX: "hidden"};
|
|
|
|
}
|
|
|
|
|
|
|
|
getWindowId() : string {
|
|
|
|
let {sw} = this.props;
|
|
|
|
return sw.windowId;
|
|
|
|
}
|
|
|
|
|
|
|
|
getWindowViewDOMId() {
|
|
|
|
return sprintf("window-view-%s", this.getWindowId());
|
2022-07-13 08:29:39 +02:00
|
|
|
}
|
|
|
|
|
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-09-12 05:49:53 +02:00
|
|
|
<div className="window-view" style={this.getWindowViewStyle()} id={this.getWindowViewDOMId()} data-windowid={sw.windowId}>
|
2022-07-14 08:11:45 +02:00
|
|
|
<div key="window-tag" className="window-tag">
|
2022-09-04 08:39:50 +02:00
|
|
|
<span>{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}</span>
|
2022-07-14 08:11:45 +02:00
|
|
|
</div>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div key="lines" className="lines" id={this.getLinesDOMId()}></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-06-18 02:54:14 +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);
|
|
|
|
let linesStyle : any = {};
|
|
|
|
if (win.lines.length == 0) {
|
|
|
|
linesStyle.display = "none";
|
|
|
|
}
|
2022-09-06 09:14:48 +02:00
|
|
|
let isActive = sw.isActive();
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className="window-view" style={this.getWindowViewStyle()} id={this.getWindowViewDOMId()}>
|
2022-09-06 09:14:48 +02:00
|
|
|
<div key="window-tag" className={cn("window-tag", {"is-active": isActive})}>
|
|
|
|
<span>
|
|
|
|
{sw.name.get()}
|
|
|
|
<If condition={sw.shouldFollow.get()}>
|
|
|
|
<i className="fa fa-caret-down"/>
|
|
|
|
</If>
|
|
|
|
</span>
|
2022-07-14 08:11:45 +02:00
|
|
|
</div>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div key="lines" className="lines" onScroll={this.scrollHandler} id={this.getLinesDOMId()} style={linesStyle}>
|
2022-08-27 07:07:12 +02:00
|
|
|
<div className="lines-spacer"></div>
|
2022-07-11 23:43:18 +02:00
|
|
|
<For each="line" of={win.lines} index="idx">
|
2022-09-22 02:20:16 +02:00
|
|
|
<Line key={line.lineid} line={line} sw={sw} width={this.width.get()} interObs={this.interObs} initVis={idx > win.lines.length-1-7}/>
|
2022-06-08 02:25:35 +02:00
|
|
|
</For>
|
|
|
|
</div>
|
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-07-13 08:29:39 +02:00
|
|
|
return (
|
|
|
|
<div className="screen-tabs">
|
2022-07-15 03:41:49 +02:00
|
|
|
<For each="screen" index="index" of={session.screens}>
|
2022-08-27 06:43:48 +02:00
|
|
|
<div key={screen.screenId} className={cn("screen-tab", {"is-active": session.activeScreenId.get() == screen.screenId}, "color-" + screen.getTabColor())} onClick={() => this.handleSwitchScreen(screen.screenId)} onContextMenu={(event) => this.handleContextMenu(event, screen.screenId)}>
|
2022-07-14 08:11:45 +02:00
|
|
|
{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-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-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
|
|
|
|
class RemoteStatusLight extends React.Component<{status : string}, {}> {
|
|
|
|
render() {
|
|
|
|
let status = this.props.status;
|
|
|
|
return (
|
|
|
|
<i className={cn("remote-status fa", "status-" + status, (status == "connecting" ? "fa-refresh" : "fa-circle"))}/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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() {
|
|
|
|
mobx.action(() => {
|
|
|
|
GlobalModel.remotesModalOpen.set(true);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
mobx.action(() => {
|
|
|
|
GlobalModel.addRemoteModalOpen.set(true);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
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-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-06-20 22:03:20 +02:00
|
|
|
return (
|
|
|
|
<div className={cn("main-sidebar", {"collapsed": this.collapsed.get()})}>
|
|
|
|
<div className="collapse-container">
|
|
|
|
<div className="arrow-container" onClick={this.toggleCollapsed}>
|
|
|
|
<If condition={!this.collapsed.get()}><i className="fa fa-arrow-left"/></If>
|
|
|
|
<If condition={this.collapsed.get()}><i className="fa fa-arrow-right"/></If>
|
|
|
|
</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-07-11 23:43:18 +02:00
|
|
|
<li><a>(loading)</a></li>
|
|
|
|
</If>
|
2022-07-12 02:55:03 +02:00
|
|
|
<If condition={model.sessionListLoaded.get()}>
|
2022-08-27 02:28:56 +02:00
|
|
|
<For each="session" index="idx" of={model.sessionList}>
|
|
|
|
<li key={session.sessionId}><a className={cn({"is-active": activeSessionId == session.sessionId})} onClick={() => this.handleSessionClick(session.sessionId)}>
|
|
|
|
<span className="session-num">{idx+1} </span>
|
|
|
|
{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>
|
|
|
|
<p className="menu-label">
|
2022-07-08 22:01:37 +02:00
|
|
|
Shared Sessions
|
2022-06-20 22:03:20 +02:00
|
|
|
</p>
|
|
|
|
<ul className="menu-list">
|
2022-08-27 02:28:56 +02:00
|
|
|
<li><a>server-status</a></li>
|
|
|
|
<li><a className="activity">bug-3458 <div className="tag is-link">3</div></a></li>
|
|
|
|
<li><a>dev-build</a></li>
|
2022-09-22 07:42:51 +02:00
|
|
|
<li className="new-session"><a><i className="fa fa-plus"/> New Session</a></li>
|
2022-06-20 22:03:20 +02:00
|
|
|
</ul>
|
|
|
|
<p className="menu-label">
|
|
|
|
Direct Messages
|
|
|
|
</p>
|
|
|
|
<ul className="menu-list">
|
|
|
|
<li><a>
|
|
|
|
<i className="user-status status fa fa-circle"/>
|
|
|
|
<img className="avatar" src="https://i.pravatar.cc/48?img=4"/>
|
|
|
|
Mike S <span className="sub-label">you</span>
|
|
|
|
</a></li>
|
|
|
|
<li><a>
|
|
|
|
<i className="user-status status offline fa fa-circle"/>
|
|
|
|
<img className="avatar" src="https://i.pravatar.cc/48?img=8"/>
|
|
|
|
Matt P
|
|
|
|
</a></li>
|
|
|
|
<li><a>
|
|
|
|
<i className="user-status status offline fa fa-circle"/>
|
|
|
|
<img className="avatar" src="https://i.pravatar.cc/48?img=12"/>
|
|
|
|
Adam B
|
|
|
|
</a></li>
|
|
|
|
<li><a className="activity">
|
|
|
|
<i className="user-status status fa fa-circle"/>
|
2022-06-21 01:06:37 +02:00
|
|
|
<img className="avatar" src="https://i.pravatar.cc/48?img=5"/>
|
2022-06-20 22:03:20 +02:00
|
|
|
Michelle T <div className="tag is-link">2</div>
|
|
|
|
</a></li>
|
|
|
|
</ul>
|
|
|
|
<div className="spacer"></div>
|
|
|
|
<p className="menu-label">
|
2022-08-18 09:39:06 +02:00
|
|
|
<a onClick={() => this.clickRemotes()}>Remotes</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-14 22:02:33 +02:00
|
|
|
<li key={remote.remoteid} className="remote-menu-item"><a onClick={() => this.clickRemote(remote)}>
|
2022-09-22 07:17:04 +02:00
|
|
|
<RemoteStatusLight status={remote.status}/>
|
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">
|
|
|
|
<a onClick={() => this.handleAddRemote()}><i className="fa fa-plus"/> Add Remote</a>
|
|
|
|
</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-09-22 07:42:51 +02:00
|
|
|
class AddRemoteModal extends React.Component<{}, {}> {
|
2022-08-18 09:39:06 +02:00
|
|
|
@boundMethod
|
|
|
|
handleModalClose() : void {
|
|
|
|
mobx.action(() => {
|
2022-09-22 07:42:51 +02:00
|
|
|
GlobalModel.addRemoteModalOpen.set(false);
|
2022-08-18 09:39:06 +02:00
|
|
|
})();
|
|
|
|
}
|
2022-09-22 07:42:51 +02:00
|
|
|
|
|
|
|
render() {
|
|
|
|
return (
|
|
|
|
<div className="sc-modal add-remote-modal modal is-active">
|
|
|
|
<div onClick={this.handleModalClose} className="modal-background"></div>
|
|
|
|
<div className="modal-content message">
|
|
|
|
<div className="message-header">
|
|
|
|
<p>Add Remote</p>
|
|
|
|
</div>
|
|
|
|
<div className="message-content">
|
|
|
|
hello
|
|
|
|
</div>
|
|
|
|
<div className="message-footer">
|
|
|
|
<button onClick={this.handleModalClose} className="button">Cancel</button>
|
|
|
|
<div className="spacer"></div>
|
2022-09-22 08:31:03 +02:00
|
|
|
<button className="button is-primary">
|
2022-09-22 07:42:51 +02:00
|
|
|
<span className="icon">
|
|
|
|
<i className="fa fa-plus"/>
|
|
|
|
</span>
|
|
|
|
<span>Add Remote</span>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-08-18 09:39:06 +02:00
|
|
|
|
2022-09-22 07:42:51 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class RemoteModal extends React.Component<{}, {}> {
|
2022-08-18 09:39:06 +02:00
|
|
|
@boundMethod
|
2022-09-22 07:42:51 +02:00
|
|
|
handleModalClose() : void {
|
|
|
|
mobx.action(() => {
|
|
|
|
GlobalModel.remotesModalOpen.set(false);
|
|
|
|
})();
|
2022-08-18 09:39:06 +02:00
|
|
|
}
|
2022-09-22 08:31:03 +02:00
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
handleAddRemote() : void {
|
|
|
|
mobx.action(() => {
|
|
|
|
GlobalModel.addRemoteModalOpen.set(true);
|
|
|
|
})();
|
|
|
|
}
|
2022-08-18 09:39:06 +02:00
|
|
|
|
|
|
|
render() {
|
|
|
|
let model = GlobalModel;
|
2022-09-22 07:17:04 +02:00
|
|
|
let remotes = sortAndFilterRemotes(model.remotes);
|
2022-08-18 09:39:06 +02:00
|
|
|
let remote : RemoteType = null;
|
|
|
|
return (
|
2022-09-22 07:42:51 +02:00
|
|
|
<div className="sc-modal remote-modal modal is-active">
|
2022-08-18 09:39:06 +02:00
|
|
|
<div onClick={this.handleModalClose} className="modal-background"></div>
|
|
|
|
<div className="modal-content message">
|
|
|
|
<div className="message-header">
|
|
|
|
<p>Remotes</p>
|
|
|
|
</div>
|
2022-09-22 07:42:51 +02:00
|
|
|
<div className="message-content">
|
2022-08-18 09:39:06 +02:00
|
|
|
<table className="table">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th className="status-header">Status</th>
|
|
|
|
<th>Alias</th>
|
|
|
|
<th>User@Host</th>
|
2022-08-21 21:26:10 +02:00
|
|
|
<th>Connect</th>
|
2022-08-18 09:39:06 +02:00
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
<For each="remote" of={remotes}>
|
2022-09-22 08:31:03 +02:00
|
|
|
<tr key={remote.remoteid}>
|
2022-08-18 09:39:06 +02:00
|
|
|
<td className="status-cell">
|
2022-09-22 07:17:04 +02:00
|
|
|
<div><RemoteStatusLight status={remote.status}/></div>
|
2022-08-18 09:39:06 +02:00
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{remote.remotealias}
|
|
|
|
<If condition={isBlank(remote.remotealias)}>
|
|
|
|
-
|
|
|
|
</If>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{remote.remotecanonicalname}
|
|
|
|
</td>
|
|
|
|
<td>
|
2022-08-21 21:26:10 +02:00
|
|
|
{remote.connectmode}
|
2022-08-18 09:39:06 +02:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</For>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
2022-09-22 07:42:51 +02:00
|
|
|
<div className="message-footer">
|
2022-08-18 09:39:06 +02:00
|
|
|
<button onClick={this.handleAddRemote} className="button is-primary">
|
|
|
|
<span className="icon">
|
|
|
|
<i className="fa fa-plus"/>
|
|
|
|
</span>
|
|
|
|
<span>Add Remote</span>
|
|
|
|
</button>
|
|
|
|
<div className="spacer"></div>
|
|
|
|
<button onClick={this.handleModalClose} className="button">Close</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<button onClick={this.handleModalClose} className="modal-close is-large" aria-label="close"></button>
|
|
|
|
</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">
|
2022-06-16 03:12:22 +02:00
|
|
|
<h1 className="title scripthaus-logo-small">
|
|
|
|
<div className="title-cursor">█</div>
|
|
|
|
ScriptHaus
|
|
|
|
</h1>
|
2022-06-20 22:03:20 +02:00
|
|
|
<div className="main-content">
|
|
|
|
<MainSideBar/>
|
2022-07-11 23:43:18 +02:00
|
|
|
<SessionView/>
|
2022-06-20 22:03:20 +02:00
|
|
|
</div>
|
2022-09-22 07:42:51 +02:00
|
|
|
<If condition={GlobalModel.addRemoteModalOpen.get()}>
|
|
|
|
<AddRemoteModal/>
|
|
|
|
</If>
|
2022-09-22 08:31:03 +02:00
|
|
|
<If condition={GlobalModel.remotesModalOpen.get() && !GlobalModel.addRemoteModalOpen.get()}>
|
|
|
|
<RemoteModal/>
|
|
|
|
</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
|
|
|
|