import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import {sprintf} from "sprintf-js";
import {boundMethod} from "autobind-decorator";
import {v4 as uuidv4} from "uuid";
import dayjs from 'dayjs'
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
import cn from "classnames"
import {TermWrap} from "./term";
import type {SessionDataType, LineType, CmdDataType, RemoteType} from "./types";
import localizedFormat from 'dayjs/plugin/localizedFormat';
import {GlobalModel, Session, Cmd, Window, Screen, ScreenWindow} from "./model";
dayjs.extend(localizedFormat)
function getLineId(line : LineType) : string {
return sprintf("%s-%s-%s", line.sessionid, line.windowid, line.lineid);
}
@mobxReact.observer
class LineMeta extends React.Component<{line : LineType}, {}> {
render() {
let line = this.props.line;
return (
{line.lineid}
{line.userid}
{dayjs(line.ts).format("hh:mm:ss a")}
);
}
}
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");
}
}
@mobxReact.observer
class LineText extends React.Component<{sw : ScreenWindow, line : LineType}, {}> {
render() {
let line = this.props.line;
let formattedTime = getLineDateStr(line.ts);
return (
S
{line.userid}
{formattedTime}
{line.text}
);
}
}
@mobxReact.observer
class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType}, {}> {
termLoaded : mobx.IObservableValue = mobx.observable.box(false);
constructor(props) {
super(props);
}
componentDidMount() {
let {sw, line} = this.props;
let model = GlobalModel;
let cmd = model.getCmd(line);
if (cmd != null) {
let termElem = document.getElementById("term-" + getLineId(line));
cmd.connectElem(termElem, sw.screenId, sw.windowId);
mobx.action(() => this.termLoaded.set(true))();
}
}
componentWillUnmount() {
let {sw, line} = this.props;
let model = GlobalModel;
let cmd = model.getCmd(line);
if (cmd != null) {
cmd.disconnectElem(sw.screenId, sw.windowId);
}
}
scrollIntoView() {
let lineElem = document.getElementById("line-" + getLineId(this.props.line));
lineElem.scrollIntoView({block: "end"});
}
@boundMethod
doRefresh() {
let {sw, line} = this.props;
let model = GlobalModel;
let cmd = model.getCmd(line);
if (cmd != null) {
let termWrap = cmd.getTermWrap(sw.screenId, sw.windowId);
if (termWrap != null) {
termWrap.reloadTerminal(500);
}
}
}
replaceHomePath(path : string, homeDir : string) : string {
if (path == homeDir) {
return "~";
}
if (path.startsWith(homeDir + "/")) {
return "~" + path.substr(homeDir.length);
}
return path;
}
renderCmdText(cmd : Cmd, remote : RemoteType) : any {
if (cmd == null) {
return (
(cmd not found)
);
}
let promptStr = "";
if (remote.remotevars.local) {
promptStr = sprintf("%s@%s", remote.remotevars.remoteuser, "local")
}
else if (remote.remotevars.remotehost) {
promptStr = sprintf("%s@%s", remote.remotevars.remoteuser, remote.remotevars.remotehost)
}
else {
let host = remote.remotevars.host || "unknown";
if (remote.remotevars.user) {
promptStr = sprintf("%s@%s", remote.remotevars.user, host)
}
else {
promptStr = host;
}
}
let cwd = "(unknown)";
let remoteState = cmd.getRemoteState();
if (remoteState && remoteState.cwd) {
cwd = remoteState.cwd;
}
if (remote.remotevars.home) {
cwd = this.replaceHomePath(cwd, remote.remotevars.home)
}
return (
[{promptStr} {cwd}] {cmd.getSingleLineCmdText()}
);
}
render() {
let {sw, line} = this.props;
let model = GlobalModel;
let lineid = line.lineid.toString();
let formattedTime = getLineDateStr(line.ts);
let cmd = model.getCmd(line);
if (cmd == null) {
return [cmd not found '{line.cmdid}']
;
}
let termLoaded = this.termLoaded.get();
let cellHeightPx = 16;
let usedRows = cmd.getUsedRows(sw.screenId, sw.windowId);
let totalHeight = cellHeightPx * usedRows;
let remote = model.getRemote(cmd.remoteId);
let status = cmd.getStatus();
let running = (status == "running");
let detached = (status == "detached");
let termOpts = cmd.getTermOpts();
let isFocused = cmd.getIsFocused(sw.screenId, sw.windowId);
return (
= 5}, {"running": running}, {"detached": detached})} onClick={this.doRefresh}>
{lineid}
{line.userid}
{formattedTime}
{line.cmdid}
({termOpts.rows}x{termOpts.cols})
{this.renderCmdText(cmd, remote)}
);
}
}
@mobxReact.observer
class Line extends React.Component<{sw : ScreenWindow, line : LineType}, {}> {
render() {
let line = this.props.line;
if (line.linetype == "text") {
return ;
}
if (line.linetype == "cmd") {
return ;
}
return [invalid line type '{line.linetype}']
;
}
}
@mobxReact.observer
class CmdInput extends React.Component<{}, {}> {
historyIndex : mobx.IObservableValue = mobx.observable.box(0, {name: "history-index"});
modHistory : mobx.IObservableArray = mobx.observable.array([""], {name: "mod-history"});
@mobx.action @boundMethod
onKeyDown(e : any) {
mobx.action(() => {
let model = GlobalModel;
let win = model.getActiveWindow();
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
if (e.code == "Enter" && !ctrlMod) {
e.preventDefault();
setTimeout(() => this.doSubmitCmd(), 0);
return;
}
if (e.code == "ArrowUp") {
e.preventDefault();
let hidx = this.historyIndex.get();
hidx += 1;
if (hidx > win.getNumHistoryItems()) {
hidx = win.getNumHistoryItems();
}
this.historyIndex.set(hidx);
return;
}
if (e.code == "ArrowDown") {
e.preventDefault();
let hidx = this.historyIndex.get();
hidx -= 1;
if (hidx < 0) {
hidx = 0;
}
this.historyIndex.set(hidx);
return;
}
// console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e);
})();
}
@boundMethod
clearCurLine() {
mobx.action(() => {
this.historyIndex.set(0);
this.modHistory.clear();
this.modHistory[0] = "";
})();
}
@boundMethod
getCurLine() : string {
let model = GlobalModel;
let hidx = this.historyIndex.get();
if (hidx < this.modHistory.length && this.modHistory[hidx] != null) {
return this.modHistory[hidx];
}
let win = model.getActiveWindow();
if (win == null) {
return "";
}
let hitem = win.getHistoryItem(-hidx);
if (hitem == null) {
return "";
}
return hitem.cmdtext;
}
@boundMethod
setCurLine(val : string) {
let hidx = this.historyIndex.get();
this.modHistory[hidx] = val;
}
@boundMethod
onChange(e : any) {
mobx.action(() => {
this.setCurLine(e.target.value);
})();
}
@boundMethod
doSubmitCmd() {
let model = GlobalModel;
let commandStr = this.getCurLine();
let hitem = {cmdtext: commandStr};
this.clearCurLine();
model.submitCommand(commandStr);
}
render() {
let curLine = this.getCurLine();
return (
);
}
}
@mobxReact.observer
class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
mutObs : any;
randomId : string;
scrollToBottom(reason : string) {
let elem = document.getElementById(this.getLinesId());
if (elem == null) {
return;
}
let oldST = elem.scrollTop;
elem.scrollTop = elem.scrollHeight;
// console.log("scroll-elem", oldST, elem.scrollHeight, elem.scrollTop, elem.scrollLeft, elem);
}
@boundMethod
scrollHandler(event : any) {
let {sw} = this.props;
let target = event.target;
let atBottom = (target.scrollTop + 30 > (target.scrollHeight - target.offsetHeight));
if (sw && sw.shouldFollow.get() != atBottom) {
mobx.action(() => sw.shouldFollow.set(atBottom));
}
// console.log("scroll-handler>", atBottom, target.scrollTop, target.scrollHeight);
}
componentDidMount() {
let elem = document.getElementById(this.getLinesId());
if (elem == null) {
return;
}
this.mutObs = new MutationObserver(this.handleDomMutation.bind(this));
this.mutObs.observe(elem, {childList: true});
elem.addEventListener("termresize", this.handleTermResize);
let {sw} = this.props;
if (sw && sw.shouldFollow.get()) {
setTimeout(() => this.scrollToBottom("mount"), 0);
}
}
componentWillUnmount() {
if (this.mutObs) {
this.mutObs.disconnect();
}
}
handleDomMutation(mutations, mutObs) {
let {sw} = this.props;
if (sw && sw.shouldFollow.get()) {
setTimeout(() => this.scrollToBottom("mut"), 0);
}
}
getWindow() : Window {
let {sw} = this.props;
return GlobalModel.getWindowById(sw.sessionId, sw.windowId);
}
getLinesId() {
let {sw} = this.props;
if (sw == null) {
if (!this.randomId) {
this.randomId = uuidv4();
}
return "window-lines-" + this.randomId;
}
return "window-lines-" + sw.windowId;
}
@boundMethod
handleTermResize(e : any) {
let {sw} = this.props;
if (sw && sw.shouldFollow.get()) {
setTimeout(() => this.scrollToBottom("termresize"), 0);
}
}
getWindowViewStyle() : any {
return {width: "100%", height: "100%"};
}
renderError(message : string) {
let {sw} = this.props;
return (
{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}
);
}
render() {
let {sw} = this.props;
if (sw == null) {
return this.renderError("(no screen window)");
}
let win = this.getWindow();
if (win == null) {
return this.renderError("(no window)");
}
if (!win.linesLoaded.get()) {
return this.renderError("(loading)");
}
let idx = 0;
let line : LineType = null;
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";
}
return (
{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}
[session="{session.name.get()}" screen="{screen.name.get()}" window="{sw.name.get()}"]
);
}
}
@mobxReact.observer
class ScreenView extends React.Component<{screen : Screen}, {}> {
render() {
let {screen} = this.props;
if (screen == null) {
return (
(no screen)
);
}
let sw = screen.getActiveSW();
return (
);
}
}
@mobxReact.observer
class ScreenTabs extends React.Component<{session : Session}, {}> {
@boundMethod
handleNewScreen() {
let {session} = this.props;
GlobalModel.createNewScreen(session, null, true);
}
@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;
}
GlobalModel.activateScreen(screen.sessionId, screen.screenId);
}
render() {
let {session} = this.props;
if (session == null) {
return null;
}
let screen : Screen = null;
return (
this.handleSwitchScreen(screen.screenId)}>
{screen.name.get()}
+
);
}
}
@mobxReact.observer
class SessionView extends React.Component<{}, {}> {
render() {
let model = GlobalModel;
let session = model.getActiveSession();
if (session == null) {
return (no active session)
;
}
let activeScreen = session.getActiveScreen();
return (
);
}
}
@mobxReact.observer
class MainSideBar extends React.Component<{}, {}> {
collapsed : mobx.IObservableValue = mobx.observable.box(false);
@boundMethod
toggleCollapsed() {
mobx.action(() => {
this.collapsed.set(!this.collapsed.get());
})();
}
handleSessionClick(sessionId : string) {
console.log("click session", sessionId);
}
render() {
let model = GlobalModel;
let activeSessionId = model.activeSessionId.get();
let session : Session = null;
return (
Private Sessions
Shared Sessions
Direct Messages
Remotes
);
}
}
@mobxReact.observer
class Main extends React.Component<{}, {}> {
constructor(props : any) {
super(props);
}
render() {
return (
);
}
}
export {Main};