mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-21 21:32:13 +01:00
auto resize terminal width to window size
This commit is contained in:
parent
8a710669ec
commit
879cb03da0
143
src/main.tsx
143
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 (
|
||||
<div className="meta">
|
||||
<div className="lineid">{line.lineid}</div>
|
||||
<div className="user">{line.userid}</div>
|
||||
<div className="ts">{dayjs(line.ts).format("hh:mm:ss a")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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<boolean> = 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 (
|
||||
<div className="line line-cmd" id={"line-" + getLineId(line)}>
|
||||
<div className={cn("avatar",{"num4": lineid.length == 4}, {"num5": lineid.length >= 5}, {"running": running}, {"detached": detached})} onClick={this.doRefresh}>
|
||||
{lineid}
|
||||
</div>
|
||||
<div className="line-content">
|
||||
<div className="meta">
|
||||
<div className="user" style={{display: "none"}}>{line.userid}</div>
|
||||
<div className="ts">{formattedTime}</div>
|
||||
<div className={cn("line", "line-cmd", {"focus": isFocused})} id={"line-" + getLineId(line)}>
|
||||
<div className="line-header">
|
||||
<div className={cn("avatar",{"num4": lineid.length == 4}, {"num5": lineid.length >= 5}, {"running": running}, {"detached": detached})} onClick={this.doRefresh}>
|
||||
{lineid}
|
||||
</div>
|
||||
<div className="meta">
|
||||
<div className="metapart-mono" style={{display: "none"}}>
|
||||
{line.cmdid}
|
||||
({termOpts.rows}x{termOpts.cols})
|
||||
<div className="meta-wrap">
|
||||
<div className="meta">
|
||||
<div className="user" style={{display: "none"}}>{line.userid}</div>
|
||||
<div className="ts">{formattedTime}</div>
|
||||
width={this.props.width}, cellwidth={termWidth}
|
||||
</div>
|
||||
<div className="meta">
|
||||
<div className="metapart-mono" style={{display: "none"}}>
|
||||
{line.cmdid}
|
||||
({termOpts.rows}x{termOpts.cols})
|
||||
</div>
|
||||
{this.renderCmdText(cmd, remote)}
|
||||
</div>
|
||||
{this.renderCmdText(cmd, remote)}
|
||||
</div>
|
||||
<div className={cn("terminal-wrapper", {"focus": isFocused})} style={{overflowY: "hidden"}}>
|
||||
<div className="terminal" id={"term-" + getLineId(line)} data-cmdid={line.cmdid} style={{height: totalHeight}}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn("terminal-wrapper", {"focus": isFocused})} style={{overflowY: "hidden"}}>
|
||||
<div className="terminal" id={"term-" + getLineId(line)} data-cmdid={line.cmdid} style={{height: totalHeight}}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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<number> = 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 (
|
||||
<div className="window-view" style={this.getWindowViewStyle()}>
|
||||
<div className="window-view" style={this.getWindowViewStyle()} id={this.getWindowViewDOMId()}>
|
||||
<div key="window-tag" className="window-tag">
|
||||
<If condition={sw != null}>
|
||||
<span>{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}</span>
|
||||
</If>
|
||||
</div>
|
||||
<div key="lines" className="lines" id={this.getLinesId()}></div>
|
||||
<div key="lines" className="lines" id={this.getLinesDOMId()}></div>
|
||||
<div key="window-empty" className="window-empty">
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
@ -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 (
|
||||
<div className="window-view" style={this.getWindowViewStyle()}>
|
||||
<div className="window-view" style={this.getWindowViewStyle()} id={this.getWindowViewDOMId()}>
|
||||
<div key="window-tag" className="window-tag">
|
||||
<span>{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}</span>
|
||||
</div>
|
||||
<div key="lines" className="lines" onScroll={this.scrollHandler} id={this.getLinesId()} style={linesStyle}>
|
||||
<div key="lines" className="lines" onScroll={this.scrollHandler} id={this.getLinesDOMId()} style={linesStyle}>
|
||||
<For each="line" of={win.lines} index="idx">
|
||||
<Line key={line.lineid} line={line} sw={sw}/>
|
||||
<Line key={line.lineid} line={line} sw={sw} width={this.width.get()}/>
|
||||
</For>
|
||||
</div>
|
||||
<If condition={win.lines.length == 0}>
|
||||
|
@ -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;
|
||||
}
|
||||
|
159
src/sh2.less
159
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;
|
||||
|
17
src/term.ts
17
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<boolean> = 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);
|
||||
|
Loading…
Reference in New Issue
Block a user