+
= 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() ? "*" : ""}
-
+
@@ -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);