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 (
[mike@local ~]
mike@local
); } } @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() ? "*" : ""}
{message}
); } 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 ( ); } } @mobxReact.observer class Main extends React.Component<{}, {}> { constructor(props : any) { super(props); } render() { return (

ScriptHaus

); } } export {Main};