diff --git a/src/main.tsx b/src/main.tsx index d33e6f5c..14fbe307 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -18,20 +18,6 @@ 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(); @@ -76,7 +62,7 @@ class LineText extends React.Component<{sw : ScreenWindow, line : LineType}, {}> } @mobxReact.observer -class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType}, {}> { +class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width: number}, {}> { termLoaded : mobx.IObservableValue = mobx.observable.box(false); constructor(props) { @@ -89,7 +75,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType}, {}> let cmd = model.getCmd(line); if (cmd != null) { let termElem = document.getElementById("term-" + getLineId(line)); - cmd.connectElem(termElem, sw.screenId, sw.windowId); + cmd.connectElem(termElem, sw.screenId, sw.windowId, this.props.width); mobx.action(() => this.termLoaded.set(true))(); } } @@ -181,6 +167,8 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType}, {}> } let termLoaded = this.termLoaded.get(); let cellHeightPx = 16; + let cellWidthPx = 8; + let termWidth = Math.max(Math.trunc((this.props.width - 20)/cellWidthPx), 10); let usedRows = cmd.getUsedRows(sw.screenId, sw.windowId); let totalHeight = cellHeightPx * usedRows; let remote = model.getRemote(cmd.remoteId); @@ -190,33 +178,36 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType}, {}> 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}
+
+
+
= 5}, {"running": running}, {"detached": detached})} onClick={this.doRefresh}> + {lineid}
-
-
- {line.cmdid} - ({termOpts.rows}x{termOpts.cols}) +
+
+
{line.userid}
+
{formattedTime}
+ width={this.props.width}, cellwidth={termWidth} +
+
+
+ {line.cmdid} + ({termOpts.rows}x{termOpts.cols}) +
+ {this.renderCmdText(cmd, remote)}
- {this.renderCmdText(cmd, remote)} -
-
-
+
+
+
); } } @mobxReact.observer -class Line extends React.Component<{sw : ScreenWindow, line : LineType}, {}> { +class Line extends React.Component<{sw : ScreenWindow, line : LineType, width : number}, {}> { render() { let line = this.props.line; if (line.linetype == "text") { @@ -350,10 +341,12 @@ class CmdInput extends React.Component<{}, {}> { @mobxReact.observer class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { mutObs : any; + rszObs : any randomId : string; + width : mobx.IObservableValue = mobx.observable.box(0); scrollToBottom(reason : string) { - let elem = document.getElementById(this.getLinesId()); + let elem = document.getElementById(this.getLinesDOMId()); if (elem == null) { return; } @@ -374,23 +367,45 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { } componentDidMount() { - let elem = document.getElementById(this.getLinesId()); - if (elem == null) { - return; + 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; + if (sw && sw.shouldFollow.get()) { + setTimeout(() => this.scrollToBottom("mount"), 0); + } } - 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); + let wvElem = document.getElementById(this.getWindowViewDOMId()); + if (wvElem != null) { + this.rszObs = new ResizeObserver(this.handleResize.bind(this)); + this.rszObs.observe(wvElem); } } + updateWidth(width : number) { + mobx.action(() => { + this.width.set(width); + })(); + } + componentWillUnmount() { if (this.mutObs) { this.mutObs.disconnect(); } + if (this.rszObs) { + this.rszObs.disconnect(); + } + } + + handleResize(entries : any) { + if (entries.length == 0) { + return; + } + let entry = entries[0]; + let width = entry.target.offsetWidth; + this.updateWidth(width); } handleDomMutation(mutations, mutObs) { @@ -405,15 +420,8 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { 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; + getLinesDOMId() { + return "window-lines-" + this.getWindowId(); } @boundMethod @@ -425,19 +433,35 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { } getWindowViewStyle() : any { - return {width: "100%", height: "100%"}; + // return {width: "100%", height: "100%"}; + return {position: "absolute", width: "100%", height: "100%", overflowX: "hidden"}; + } + + getWindowId() : string { + let {sw} = this.props; + if (sw == null) { + if (!this.randomId) { + this.randomId = uuidv4(); + } + return this.randomId; + } + return sw.windowId; + } + + getWindowViewDOMId() { + return sprintf("window-view-%s", this.getWindowId()); } renderError(message : string) { let {sw} = this.props; return ( -
+
{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}
-
+
{message}
@@ -457,6 +481,9 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { if (!win.linesLoaded.get()) { return this.renderError("(loading)"); } + if (this.width.get() == 0) { + return this.renderError(""); + } let idx = 0; let line : LineType = null; let screen = GlobalModel.getScreenById(sw.sessionId, sw.screenId); @@ -466,13 +493,13 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { linesStyle.display = "none"; } return ( -
+
{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}
-
+
- +
diff --git a/src/model.ts b/src/model.ts index 86bcd1a1..d309ef1a 100644 --- a/src/model.ts +++ b/src/model.ts @@ -49,13 +49,13 @@ class Cmd { this.data = mobx.observable.box(cmd, {deep: false}); } - connectElem(elem : Element, screenId : string, windowId : string) { + connectElem(elem : Element, screenId : string, windowId : string, width : number) { let termWrap = this.getTermWrap(screenId, windowId); if (termWrap != null) { console.log("term-wrap already exists for", screenId, windowId); return; } - termWrap = new TermWrap(elem, this.sessionId, this.cmdId, 0, this.getTermOpts(), this.handleKey.bind(this)); + termWrap = new TermWrap(elem, this.sessionId, this.cmdId, 0, this.getTermOpts(), {height: 0, width: width}, this.handleKey.bind(this)); this.instances[screenId + "/" + windowId] = termWrap; return; } diff --git a/src/sh2.less b/src/sh2.less index b54b222f..7fb767af 100644 --- a/src/sh2.less +++ b/src/sh2.less @@ -17,6 +17,7 @@ html, body, #main { flex-grow: 1; display: flex; flex-direction: column; + min-width: 300px; } .screen-tabs { @@ -25,6 +26,7 @@ html, body, #main { flex-direction: row; border-top: 1px solid #eee; border-right: 1px solid #eee; + overflow: scroll; .screen-tab { height: 30px; @@ -71,6 +73,7 @@ html, body, #main { .screen-view { flex-grow: 1; border-right: 1px solid #ccc; + position: relative; } .window-view { @@ -143,6 +146,7 @@ html, body, #main { color: #ddd; position: relative; background-color: darken(rgb(0, 177, 10), 30%); + flex-shrink: 0; .menu { padding-top: 10px; @@ -274,9 +278,57 @@ html, body, #main { .line.line-text { flex-direction: row; + + .line-content { + display: flex; + flex-direction: column; + flex-grow: 1; + + .text { + font-size: 1rem; + color: #ddd; + } + } } .line.line-cmd { + flex-direction: column; + + .avatar { + cursor: pointer; + } + + .line-header { + display: flex; + flex-direction: row; + + .meta-wrap { + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; + } + } + + &.focus { + background-color: #000; + } + + .terminal-wrapper { + background-color: #000; + padding: 2px 10px 5px 4px; + margin-left: -4px; + margin-right: 8px; + margin-top: 4px; + align-self: flex-start; + + &.focus { + /* box-shadow: -8px 0 12px -5px #aaa; */ + /* box-shadow: 0 0 0 4px hsl(0, 0%, 20%);*/ + /* filter:drop-shadow(3px 3px 10px #555); */ + box-shadow: 0 0 4px 4px rgba(255, 255, 255, 0.3); + } + } } .line { @@ -285,6 +337,8 @@ html, body, #main { display: flex; line-height: 1.25; border-top: 1px solid #777; + overflow: hidden; + flex-shrink: 0; &:first-child { margin: 0px 5px 5px 5px; @@ -302,7 +356,7 @@ html, body, #main { font-weight: bold; color: white; font-size: 1rem; - margin-right: 15px; + margin-right: 10px; border-radius: 5px; &.num4 { @@ -323,67 +377,40 @@ html, body, #main { } } - &.line-cmd .avatar { - cursor: pointer; - } - - .line-content { + .meta { display: flex; - flex-direction: column; - flex-grow: 1; - - .meta { - display: flex; - flex-direction: row; - font-size: 1rem; - margin-top: -4px; - - .user { - color: lighten(#729fcf, 10%); - font-weight: bold; - margin-top: 1px; - margin-right: 10px; - } - - .ts { - color: #ddd; - margin-top: 5px; - font-size: 12px; - } - - .metapart-mono { - color: #ddd; - margin-left: 8px; - font-size: 14px; - margin-top: 4px; - font-family: 'JetBrains Mono', monospace; - font-weight: 400; - } - - .cmdtext { - color: #fff; - font-size: 16px; - font-weight: bold; - overflow: hidden; - margin-left: 0; - } + flex-direction: row; + font-size: 1rem; + margin-top: -4px; + + .user { + color: lighten(#729fcf, 10%); + font-weight: bold; + margin-top: 1px; + margin-right: 10px; } - - .text { - font-size: 1rem; + + .ts { color: #ddd; + margin-top: 5px; + font-size: 12px; } - } - - .terminal-wrapper { - background-color: #000; - padding: 5px 15px 5px 5px; - margin-right: 8px; - margin-top: 2px; - align-self: flex-start; - - &.focus { - box-shadow: -8px 0 12px -5px #aaa; + + .metapart-mono { + color: #ddd; + margin-left: 8px; + font-size: 14px; + margin-top: 4px; + font-family: 'JetBrains Mono', monospace; + font-weight: 400; + } + + .cmdtext { + color: #fff; + font-size: 16px; + font-weight: bold; + overflow: hidden; + margin-left: 0; } } } @@ -401,22 +428,6 @@ body .xterm .xterm-viewport { background: white; } -.line.line-cmd { - .refresh-button { - cursor: pointer; - border-color: #777; - color: #777; - margin-right: 10px; - - &:hover { - border-color: #fff; - .icon i { - color: #fff; - } - } - } -} - .line.line-invalid { color: #000; margin-left: 5px; diff --git a/src/term.ts b/src/term.ts index d81c7ab7..09a22117 100644 --- a/src/term.ts +++ b/src/term.ts @@ -24,6 +24,14 @@ type DataUpdate = { pos : number, } +type WindowSize = { + height : number, + width: number, +}; + +const DefaultCellWidth = 8; +const DefaultCellHeight = 16; + // cmd-instance class TermWrap { terminal : any; @@ -39,7 +47,7 @@ class TermWrap { dataUpdates : DataUpdate[] = []; loadError : mobx.IObservableValue = mobx.observable.box(false); - constructor(elem : Element, sessionId : string, cmdId : string, usedRows : number, termOpts : TermOptsType, keyHandler : (event : any) => void) { + constructor(elem : Element, sessionId : string, cmdId : string, usedRows : number, termOpts : TermOptsType, winSize : WindowSize, keyHandler : (event : any) => void) { this.sessionId = sessionId; this.cmdId = cmdId; this.connectedElem = elem; @@ -52,7 +60,12 @@ class TermWrap { this.atRowMax = true; this.usedRows = mobx.observable.box(termOpts.rows); } - this.terminal = new Terminal({rows: termOpts.rows, cols: termOpts.cols, fontSize: 14, theme: {foreground: "#d3d7cf"}}); + let cols = termOpts.cols; + let maxCols = Math.trunc((winSize.width - 25) / DefaultCellWidth); + if (maxCols > cols) { + cols = maxCols; + } + this.terminal = new Terminal({rows: termOpts.rows, cols: maxCols, fontSize: 14, theme: {foreground: "#d3d7cf"}}); this.terminal.open(elem); if (keyHandler != null) { this.terminal.onKey(keyHandler);