terminal font-size updates (margins, paddings, etc.) (#315)

* reorganize line styles, split lines.less into lines.less and line.less

* switch everyone to use termFontSize getter (easier to find usages)

* better font-family font-size update logic

* update line styles, clean up more

* replace icons, fix line heights

* make paddings/margins more consistent

* fix more margins, make command completions use termfontfamily

* updates for cmdinput margins, font sizes, etc.

* fix more font sizes and margins (mostly command input)

* add comment
This commit is contained in:
Mike Sawka 2024-02-23 09:39:27 -08:00 committed by GitHub
parent b7539a26c7
commit 61de455b90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 569 additions and 645 deletions

View File

@ -2,6 +2,12 @@
:root {
--fa-style-family: "Font Awesome 6 Sharp";
// these variables are overridden by user settings
--termfontfamily: "JetBrains Mono", monospace;
--termfontsize: 12px;
--termlineheight: 15px;
--termpad: 7px; // padding value (scaled to termfontsize)
}
html,

View File

@ -160,7 +160,7 @@ class Bookmark extends React.Component<BookmarkProps, {}> {
<div className="bookmark-id-div">{bm.bookmarkid.substr(0, 8)}</div>
<div className="bookmark-content">
<If condition={hasDesc}>
<Markdown text={markdown} />
<Markdown text={markdown} extraClassName="bottom-margin" />
</If>
<CmdStrCode
cmdstr={bm.cmdstr}

View File

@ -27,7 +27,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
@boundMethod
handleChangeFontSize(fontSize: string): void {
const newFontSize = Number(fontSize);
if (GlobalModel.termFontSize.get() == newFontSize) {
if (GlobalModel.getTermFontSize() == newFontSize) {
return;
}
const prtn = GlobalCommandRunner.setTermFontSize(newFontSize, false);
@ -144,7 +144,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
const maxTokensStr = String(
openAIOpts.maxtokens == null || openAIOpts.maxtokens == 0 ? 1000 : openAIOpts.maxtokens
);
const curFontSize = GlobalModel.termFontSize.get();
const curFontSize = GlobalModel.getTermFontSize();
const curFontFamily = GlobalModel.getTermFontFamily();
return (

View File

@ -2,11 +2,14 @@
.markdown {
color: @term-white;
margin-bottom: 10px;
font-family: @markdown-font;
font-size: 14px;
overflow-wrap: break-word;
&.bottom-margin {
margin-bottom: 10px;
}
code {
background-color: @markdown-highlight;
color: @term-white;

View File

@ -41,7 +41,7 @@ class AlertModal extends React.Component<{}, {}> {
<Modal.Header onClose={this.closeModal} title={title} />
<div className="wave-modal-body">
<If condition={message?.markdown}>
<Markdown text={message?.message ?? ""} />
<Markdown text={message?.message ?? ""} extraClassName="bottom-margin" />
</If>
<If condition={!message?.markdown}>{message?.message}</If>
<If condition={message.confirmflag}>

View File

@ -59,7 +59,7 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
<div className="wave-modal-body">
<div className="userinput-query">
<If condition={userInputRequest.markdown}>
<Markdown text={userInputRequest.querytext} />
<Markdown text={userInputRequest.querytext} extraClassName="bottom-margin" />
</If>
<If condition={!userInputRequest.markdown}>{userInputRequest.querytext}</If>
</div>

View File

@ -291,7 +291,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
let model = this.model;
let isTermFocused = this.model.remoteTermWrapFocus.get();
let termFontSize = GlobalModel.termFontSize.get();
let termFontSize = GlobalModel.getTermFontSize();
let termWidth = textmeasure.termWidthFromCols(appconst.RemotePtyCols, termFontSize);
let remoteAliasText = util.isBlank(remote.remotealias) ? "(none)" : remote.remotealias;
let selectedRemoteStatus = this.getSelectedRemote().status;

View File

@ -2,6 +2,8 @@
.term-prompt {
font-weight: normal;
font-size: var(--termfontsize);
line-height: var(--termlineheight);
font-family: var(--termfontfamily);
.icon {
@ -12,6 +14,10 @@
fill: @wave-green;
}
i {
margin-right: 0.25em; // em for relative sizing
}
.term-prompt-branch {
color: @term-white;
}
@ -21,9 +27,6 @@
}
.term-prompt-remote {
i {
margin-right: 0;
}
}
.term-prompt-remote {

View File

@ -8,7 +8,6 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel } from "@/models";
import cn from "classnames";
import { isBlank } from "@/util/util";
import { ReactComponent as FolderIcon } from "@/assets/icons/folder.svg";
import "./prompt.less";
@ -76,6 +75,7 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
if (rptr == null || isBlank(rptr.remoteid)) {
return <span className={cn("term-prompt", "color-green")}>&nbsp;</span>;
}
let termFontSize = GlobalModel.getTermFontSize();
let remote = GlobalModel.getRemote(this.props.rptr.remoteid);
let remoteStr = getRemoteStr(rptr);
let festate = this.props.festate ?? {};
@ -96,7 +96,7 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
}
let cwdElem = (
<span title="current directory" className="term-prompt-cwd">
<FolderIcon className="icon" />
<i className="fa-sharp fa-solid fa-folder" style={{ marginRight: Math.ceil(termFontSize / 4) }} />
{cwd}
</span>
);

366
src/app/line/line.less Normal file
View File

@ -0,0 +1,366 @@
@import "@/common/themes/themes.less";
.line {
margin: 1rem 1rem 0 1rem;
padding: var(--termpad) var(--termpad) var(--termpad) var(--termpad);
border-radius: 6px;
display: flex;
overflow: hidden;
flex-shrink: 0;
position: relative;
background: @base-background;
border: 1px solid transparent;
&.line-cmd {
flex-direction: column;
scroll-margin-bottom: 20px;
position: relative;
}
&.line-text {
// old formatting for text lines (requires override)
flex-direction: row;
padding-top: 5px;
.line-content {
display: flex;
flex-direction: column;
flex-grow: 1;
margin-left: 10px;
}
}
&.line-invalid {
color: @term-white;
margin-left: 5px;
}
&.selected {
border: 1px solid rgba(@base-color, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
&.active {
border: 1px solid rgba(@wave-green, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
.load-error-text {
color: @term-red;
padding-top: 5px;
}
.renderer-loading {
padding-top: 5px;
}
.sidebar-message {
color: @term-yellow;
}
.focus-indicator {
height: calc(100% - 7px);
top: 5px;
}
&.line-simple {
font-size: 11px;
line-height: 1.2;
color: rgba(@base-color, 0.6);
.simple-line-header {
display: flex;
margin-top: 5px;
flex-direction: row;
}
.ts {
display: flex;
}
}
.simple-line-status {
.linenum {
cursor: pointer;
}
.avatar {
display: inline-block;
height: 1em;
margin-left: 0.5em;
margin-right: 0.5em;
vertical-align: text-top;
fill: @base-color;
}
.success {
color: @wave-green;
fill: @wave-green;
}
.fail {
color: @error-red;
fill: @error-red;
}
.warning {
color: @warning-yellow;
fill: @warning-yellow;
}
}
&.top-border {
border-top: 1px solid #777;
padding: 10px;
}
&:nth-child(2) {
margin: 0px 5px 5px 5px;
padding: 0px 5px 5px 12px;
border-top: none;
}
&:hover .meta .termopts {
display: block;
}
&:hover .meta .settings {
display: block;
}
.line-header {
display: flex;
flex-direction: row;
width: 100%;
font-weight: normal;
font-family: var(--termfontfamily);
font-size: var(--termfontsize);
line-height: var(--termlineheight);
&.is-expanded {
height: auto;
}
.line-icon {
display: block;
visibility: hidden;
cursor: pointer;
padding: 3px;
border-radius: 50%;
}
.line-icon-show {
visibility: visible;
}
.line-icon + .line-icon {
margin-left: 5px;
}
.line-icon.active {
visibility: visible;
display: block;
}
&:hover .line-icon {
visibility: visible;
display: block;
opacity: 1;
}
.meta.meta-line1 {
color: rgba(@base-color, 0.6) !important;
}
.meta.meta-line2 {
margin-left: -2px;
}
}
.line-header + div:not(.zero-height) {
margin-top: var(--termpad);
}
.meta-wrap {
flex: 1 1 0px;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.meta {
display: flex;
flex-direction: row;
.user {
color: lighten(@soft-blue, 10%);
font-weight: bold;
margin-top: 1px;
margin-right: 10px;
}
.ts,
.termopts,
.renderer {
display: flex;
}
.renderer {
margin-left: 3px;
svg {
width: 1em;
margin-right: 0.5em;
fill: rgba(@base-color, 0.6);
}
}
.settings {
display: none;
margin-left: 0.5em;
cursor: pointer;
width: 1em;
height: 1em;
border-radius: 50%;
line-height: 1em;
svg {
fill: rgba(@base-color, 0.6);
&:hover {
fill: @base-color;
}
}
}
.termopts {
display: none;
.resize-button {
cursor: pointer;
padding-left: 3px;
padding-right: 3px;
}
}
.metapart-mono {
margin-left: 8px;
white-space: nowrap;
}
.cmdtext {
overflow: hidden;
margin-left: 0;
}
.cmdtext-overflow {
flex-shrink: 0;
padding-right: 2px;
color: @term-white;
cursor: pointer;
}
}
.cmdtext-expanded-wrapper {
padding-left: 6px;
overflow-y: auto;
max-height: 60px;
border-left: 2px solid #333;
margin-left: 3px;
.cmdtext-expanded {
white-space: pre;
color: @term-white;
padding-bottom: 5px;
}
}
.cmd-hints {
position: absolute;
bottom: 0px;
right: 0px;
display: none;
.hint-item {
border-radius: 3px;
}
}
.image-wrapper {
.loading-div {
height: 20px;
margin-left: 50px;
}
}
&.has-rtnstate .terminal-wrapper {
padding-bottom: 0;
}
.terminal-wrapper {
line-height: normal;
&.zero-height {
padding: 0;
margin: 0;
}
.terminal-connectelem {
overflow-y: hidden;
overflow-x: hidden;
}
&.cmd-done .terminal .xterm-cursor {
display: none;
}
.terminal-loading-message {
position: absolute;
top: calc(40% - 8px);
left: 50px;
height: 20px;
}
}
.cmd-rtnstate {
position: relative;
.cmd-rtnstate-label {
font-family: var(--termfontfamily);
position: relative;
font-size: 10px;
z-index: 2;
margin: 6px 0 2px 10px;
padding: 2px 5px 0px 5px;
display: inline-block;
font-size: 10px;
color: @term-white;
background-color: #151715;
}
.cmd-rtnstate-diff {
font-family: var(--termfontfamily);
color: @term-white;
white-space: pre;
margin-left: 10px;
padding-left: 10px;
padding-bottom: 1px;
font-size: 11px;
line-height: 1.2;
max-height: 50px;
overflow-y: auto;
direction: rtl;
.cmd-rtnstate-diff-inner {
direction: ltr;
}
}
.cmd-rtnstate-sep {
height: 1px;
border-bottom: 1px solid #333;
position: relative;
top: -10px;
width: min(300px, 50%);
margin-bottom: -4px;
}
}
}

View File

@ -25,17 +25,9 @@ import * as lineutil from "./lineutil";
import { ErrorBoundary } from "@/common/error/errorboundary";
import * as appconst from "@/app/appconst";
import { ReactComponent as CheckIcon } from "@/assets/icons/line/check.svg";
import { ReactComponent as CommentIcon } from "@/assets/icons/line/comment.svg";
import { ReactComponent as QuestionIcon } from "@/assets/icons/line/question.svg";
import { ReactComponent as WarningIcon } from "@/assets/icons/line/triangle-exclamation.svg";
import { ReactComponent as XmarkIcon } from "@/assets/icons/line/xmark.svg";
import { ReactComponent as FillIcon } from "@/assets/icons/line/fill.svg";
import { ReactComponent as GearIcon } from "@/assets/icons/line/gear.svg";
import { RotateIcon } from "@/common/icons/icons";
import "./lines.less";
import "./line.less";
dayjs.extend(localizedFormat);
@ -44,41 +36,40 @@ class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRigh
render() {
const { line, cmd } = this.props;
const lineNumStr = (line.linenumtemp ? "~" : "#") + String(line.linenum);
const status = cmd != null ? cmd.getStatus() : "done";
const rtnstate = cmd != null ? cmd.getRtnState() : false;
let status = cmd != null ? cmd.getStatus() : "done";
const exitcode = cmd != null ? cmd.getExitCode() : 0;
const isComment = line.linetype == "text";
let icon = null;
let iconTitle = null;
if (isComment) {
icon = <CommentIcon />;
icon = <i className="fa-sharp fa-solid fa-comment" />;
iconTitle = "comment";
} else if (status == "done") {
if (exitcode === 0) {
icon = <CheckIcon className="success" />;
icon = <i className="success fa-sharp fa-solid fa-check" />;
iconTitle = "success";
} else {
icon = <XmarkIcon className="fail" />;
icon = <i className="fail fa-sharp fa-solid fa-xmark" />;
iconTitle = "exitcode " + exitcode;
}
} else if (status == "hangup") {
icon = <WarningIcon className="warning" />;
icon = <i className="warning fa-sharp fa-solid fa-triangle-exclamation" />;
iconTitle = status;
} else if (status == "error") {
icon = <XmarkIcon className="fail" />;
icon = <i className="fail fa-sharp fa-solid fa-xmark" />;
iconTitle = "error";
} else if (status == "running" || "detached") {
icon = <RotateIcon className="warning spin" />;
} else if (status == "running" || status == "detached") {
icon = <i className="warning fa-sharp fa-solid fa-rotate fa-spin" />;
iconTitle = "running";
} else {
icon = <QuestionIcon />;
icon = <i className="fail fa-sharp fa-solid fa-question" />;
iconTitle = "unknown";
}
return (
<div
onContextMenu={this.props.onRightClick}
title={iconTitle}
className={cn("simple-line-status", "status-" + status, rtnstate ? "has-rtnstate" : null)}
className={cn("simple-line-status", "status-" + status)}
>
<span className="linenum">{lineNumStr}</span>
<div className="avatar">{icon}</div>
@ -88,52 +79,22 @@ class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRigh
}
@mobxReact.observer
class LineCmd extends React.Component<
{
screen: LineContainerType;
line: LineType;
width: number;
staticRender: boolean;
visible: OV<boolean>;
onHeightChange: LineHeightChangeCallbackType;
renderMode: RenderModeType;
overrideCollapsed: OV<boolean>;
noSelect?: boolean;
showHints?: boolean;
},
{}
> {
lineRef: React.RefObject<any> = React.createRef();
cmdTextRef: React.RefObject<any> = React.createRef();
class RtnState extends React.Component<{ cmd: Cmd; line: LineType }> {
rtnStateDiff: mobx.IObservableValue<string> = mobx.observable.box(null, {
name: "linecmd-rtn-state-diff",
});
rtnStateDiffFetched: boolean = false;
lastHeight: number;
isOverflow: OV<boolean> = mobx.observable.box(false, {
name: "line-overflow",
});
isCmdExpanded: OV<boolean> = mobx.observable.box(false, {
name: "cmd-expanded",
});
constructor(props) {
super(props);
componentDidMount() {
this.componentDidUpdate();
}
componentDidUpdate() {
this.checkStateDiffLoad();
}
checkStateDiffLoad(): void {
const { screen, line, staticRender, visible } = this.props;
if (staticRender) {
return;
}
if (!visible.get()) {
if (this.rtnStateDiffFetched) {
this.rtnStateDiffFetched = false;
this.setRtnStateDiff(null);
}
return;
}
const cmd = screen.getCmd(line);
let cmd = this.props.cmd;
if (cmd == null || !cmd.getRtnState() || this.rtnStateDiffFetched) {
return;
}
@ -179,6 +140,68 @@ class LineCmd extends React.Component<
})();
}
render() {
let { cmd } = this.props;
const rsdiff = this.rtnStateDiff.get();
const termFontSize = GlobalModel.getTermFontSize();
let rtnStateDiffSize = termFontSize - 2;
if (rtnStateDiffSize < 10) {
rtnStateDiffSize = Math.max(termFontSize, 10);
}
return (
<div
key="rtnstate"
className="cmd-rtnstate"
style={{
visibility: cmd.getStatus() == "done" ? "visible" : "hidden",
}}
>
<If condition={rsdiff == null || rsdiff == ""}>
<div className="cmd-rtnstate-label">state unchanged</div>
<div className="cmd-rtnstate-sep"></div>
</If>
<If condition={rsdiff != null && rsdiff != ""}>
<div className="cmd-rtnstate-label">new state</div>
<div className="cmd-rtnstate-sep"></div>
<div className="cmd-rtnstate-diff" style={{ fontSize: rtnStateDiffSize }}>
<div className="cmd-rtnstate-diff-inner">{this.rtnStateDiff.get()}</div>
</div>
</If>
</div>
);
}
}
@mobxReact.observer
class LineCmd extends React.Component<
{
screen: LineContainerType;
line: LineType;
width: number;
staticRender: boolean;
visible: OV<boolean>;
onHeightChange: LineHeightChangeCallbackType;
renderMode: RenderModeType;
overrideCollapsed: OV<boolean>;
noSelect?: boolean;
showHints?: boolean;
},
{}
> {
lineRef: React.RefObject<any> = React.createRef();
cmdTextRef: React.RefObject<any> = React.createRef();
lastHeight: number;
isOverflow: OV<boolean> = mobx.observable.box(false, {
name: "line-overflow",
});
isCmdExpanded: OV<boolean> = mobx.observable.box(false, {
name: "cmd-expanded",
});
constructor(props) {
super(props);
}
componentDidMount() {
this.componentDidUpdate(null, null, null);
this.checkCmdText();
@ -241,7 +264,6 @@ class LineCmd extends React.Component<
componentDidUpdate(prevProps, prevState, snapshot: { height: number }): void {
this.handleHeightChange();
this.checkStateDiffLoad();
this.checkCmdText();
}
@ -385,7 +407,7 @@ class LineCmd extends React.Component<
let height = 45 + 24; // height of zero height terminal
const usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
if (usedRows > 0) {
height = 48 + 24 + termHeightFromRows(usedRows, GlobalModel.termFontSize.get(), cmd.getTermMaxRows());
height = 48 + 24 + termHeightFromRows(usedRows, GlobalModel.getTermFontSize(), cmd.getTermMaxRows());
}
return height;
}
@ -452,7 +474,6 @@ class LineCmd extends React.Component<
renderMeta1(cmd: Cmd) {
let { line } = this.props;
let termOpts = cmd.getTermOpts();
let formattedTime: string = "";
let restartTs = cmd.getRestartTs();
let timeTitle: string = null;
@ -472,16 +493,10 @@ class LineCmd extends React.Component<
<div>&nbsp;</div>
<If condition={!isBlank(renderer) && renderer != "terminal"}>
<div className="renderer">
<FillIcon />
<i className="fa-sharp fa-solid fa-fill" />
{renderer}&nbsp;
</div>
</If>
<div className="termopts">
({termOpts.rows}x{termOpts.cols})
</div>
<div className="settings hoverEffect" onClick={this.handleLineSettings}>
<GearIcon />
</div>
</div>
);
}
@ -583,6 +598,7 @@ class LineCmd extends React.Component<
</div>
);
}
const isRtnState = cmd.getRtnState() && false; // turning off rtnstate for now
const isSelected = mobx
.computed(() => screen.getSelectedLine() == line.linenum, {
name: "computed-isSelected",
@ -621,14 +637,13 @@ class LineCmd extends React.Component<
.get();
const isRunning = cmd.isRunning();
const isExpanded = this.isCmdExpanded.get();
const rsdiff = this.rtnStateDiff.get();
const mainDivCn = cn(
"line",
"line-cmd",
{ selected: isSelected },
{ active: isSelected && isFocused },
{ "cmd-done": !isRunning },
{ "has-rtnstate": cmd.getRtnState() }
{ "has-rtnstate": isRtnState }
);
let rendererPlugin: RendererPluginType = null;
const isNoneRenderer = line.renderer == "none";
@ -637,11 +652,7 @@ class LineCmd extends React.Component<
}
const rendererType = lineutil.getRendererType(line);
const hidePrompt = rendererPlugin?.hidePrompt;
const termFontSize = GlobalModel.termFontSize.get();
let rtnStateDiffSize = termFontSize - 2;
if (rtnStateDiffSize < 10) {
rtnStateDiffSize = Math.max(termFontSize, 10);
}
const termFontSize = GlobalModel.getTermFontSize();
const containerType = screen.getContainerType();
return (
<div
@ -700,6 +711,14 @@ class LineCmd extends React.Component<
>
<i className="fa-sharp fa-solid fa-right-to-line" />
</div>
<div
key="settings"
title="Line Settings"
className="line-icon"
onClick={this.handleLineSettings}
>
<i className="fa-sharp fa-regular fa-ellipsis-vertical" />
</div>
</If>
<If condition={containerType == appconst.LineContainer_Sidebar}>
<div
@ -711,7 +730,7 @@ class LineCmd extends React.Component<
</div>
</If>
</div>
<If condition={isInSidebar}>
<If condition={!isMinimized && isInSidebar}>
<div className="sidebar-message" style={{ fontSize: termFontSize }}>
&nbsp;&nbsp;showing in sidebar =&gt;
</div>
@ -752,26 +771,8 @@ class LineCmd extends React.Component<
/>
</If>
</ErrorBoundary>
<If condition={cmd.getRtnState()}>
<div
key="rtnstate"
className="cmd-rtnstate"
style={{
visibility: cmd.getStatus() == "done" ? "visible" : "hidden",
}}
>
<If condition={rsdiff == null || rsdiff == ""}>
<div className="cmd-rtnstate-label">state unchanged</div>
<div className="cmd-rtnstate-sep"></div>
</If>
<If condition={rsdiff != null && rsdiff != ""}>
<div className="cmd-rtnstate-label">new state</div>
<div className="cmd-rtnstate-sep"></div>
<div className="cmd-rtnstate-diff" style={{ fontSize: rtnStateDiffSize }}>
<div className="cmd-rtnstate-diff-inner">{this.rtnStateDiff.get()}</div>
</div>
</If>
</div>
<If condition={isRtnState}>
<RtnState cmd={cmd} line={line} />
</If>
<If condition={isSelected && !isFocused && rendererType == "terminal"}>
<div className="cmd-hints">

View File

@ -1,462 +1,5 @@
@import "@/common/themes/themes.less";
.line.line-text {
flex-direction: row;
padding-top: 5px;
.line-content {
display: flex;
flex-direction: column;
flex-grow: 1;
margin-left: 10px;
}
}
.line .load-error-text {
color: @term-red;
padding-top: 5px;
}
.line .renderer-loading {
padding-top: 5px;
}
.line.line-cmd {
flex-direction: column;
scroll-margin-bottom: 20px;
position: relative;
.plus {
opacity: 0.5;
}
&:hover {
.plus {
opacity: 1;
}
}
.sidebar-message {
color: @term-yellow;
}
.line-header {
display: flex;
flex-direction: row;
padding-bottom: 0.7rem;
width: 100%;
line-height: 1.2;
&.is-expanded {
height: auto;
}
&.hide-prompt {
height: 32px;
}
.line-icon {
display: block;
visibility: hidden;
cursor: pointer;
padding: 3px;
border-radius: 50%;
font-size: 14px;
}
.line-icon-show {
visibility: visible;
}
.line-icon + .line-icon {
margin-left: 5px;
}
.line-icon.active {
visibility: visible;
display: block;
}
&:hover .line-icon {
visibility: visible;
display: block;
opacity: 1;
}
}
.meta.meta-line1 {
color: rgba(@base-color, 0.6) !important;
font-size: 11px;
}
.meta.meta-line2 {
margin-left: -2px;
}
&.has-rtnstate .terminal-wrapper {
padding-bottom: 0;
}
.image-wrapper {
.loading-div {
height: 20px;
margin-left: 50px;
}
}
.terminal-wrapper {
line-height: normal;
&.zero-height {
padding: 0;
margin: 0;
}
.terminal-connectelem {
overflow-y: hidden;
overflow-x: hidden;
}
&.cmd-done .terminal .xterm-cursor {
display: none;
}
.terminal-loading-message {
position: absolute;
top: calc(40% - 8px);
left: 50px;
height: 20px;
}
}
.cmd-rtnstate {
position: relative;
.cmd-rtnstate-label {
font-family: var(--termfontfamily);
position: relative;
font-size: 10px;
z-index: 2;
margin: 6px 0 2px 10px;
padding: 2px 5px 0px 5px;
display: inline-block;
font-size: 10px;
color: @term-white;
background-color: #151715;
}
.cmd-rtnstate-diff {
font-family: var(--termfontfamily);
color: @term-white;
white-space: pre;
margin-left: 10px;
padding-left: 10px;
padding-bottom: 1px;
font-size: 11px;
line-height: 1.2;
max-height: 50px;
overflow-y: auto;
direction: rtl;
.cmd-rtnstate-diff-inner {
direction: ltr;
}
}
.cmd-rtnstate-sep {
height: 1px;
border-bottom: 1px solid #333;
position: relative;
top: -10px;
width: min(300px, 50%);
margin-bottom: -4px;
}
}
}
.line {
margin: 1rem 1rem 0 1rem;
padding: 1rem;
border-radius: 6px;
display: flex;
overflow: hidden;
flex-shrink: 0;
position: relative;
background: @base-background;
border: 1px solid transparent;
&.selected {
border: 1px solid rgba(@base-color, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
&.active {
border: 1px solid rgba(@wave-green, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
.focus-indicator {
height: calc(100% - 7px);
top: 5px;
}
&.line-simple {
font-size: 11px;
line-height: 1.2;
color: rgba(@base-color, 0.6);
.simple-line-header {
display: flex;
margin-top: 5px;
flex-direction: row;
}
.ts {
display: flex;
}
}
.simple-line-status {
.linenum {
cursor: pointer;
}
&.has-rtnstate {
.linenum {
font-weight: bold;
}
}
.avatar {
display: inline-block;
width: 1em;
height: 1em;
margin: 0 0.5em;
vertical-align: text-top;
fill: @base-color;
}
.success {
fill: @wave-green;
}
.fail {
fill: @error-red;
}
.warning {
fill: @warning-yellow;
}
}
&.top-border {
border-top: 1px solid #777;
padding: 10px;
}
&:nth-child(2) {
margin: 0px 5px 5px 5px;
padding: 0px 5px 5px 12px;
border-top: none;
}
&:hover .meta .termopts {
display: block;
}
&:hover .meta .settings {
display: block;
}
.avatar {
max-height: 38px;
width: 38px;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
font-weight: bold;
color: @term-white;
border-radius: 5px;
position: relative;
&.rtnstate {
border: 1px solid white;
}
.status-icon {
position: absolute;
top: 2px;
right: 2px;
}
.comment-icon {
position: absolute;
top: 0px;
right: -4px;
}
&.status-done {
background-color: #555;
}
&.ephemeral {
opacity: 0.5;
outline: 1px solid white;
}
&.status-error {
.status-icon {
color: @error-red;
}
}
&.status-hangup {
.status-icon {
color: @term-yellow;
}
}
&.status-running {
background-color: @soft-blue;
}
&.status-waiting {
background-color: @term-yellow;
}
&.status-detached {
background-color: @soft-blue;
.status-icon {
top: 3px;
right: 3px;
color: @term-white;
}
}
}
.meta-wrap {
flex: 1 1 0px;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: flex-start;
line-height: 1.2;
}
.meta {
display: flex;
flex-direction: row;
.simple-line-status {
margin-top: 5px;
}
.user {
color: lighten(@soft-blue, 10%);
font-weight: bold;
margin-top: 1px;
margin-right: 10px;
}
.ts,
.termopts,
.renderer {
display: flex;
margin-top: 5px;
}
.renderer {
margin-left: 3px;
margin-top: 5px;
svg {
width: 1em;
margin-right: 0.5em;
fill: rgba(@base-color, 0.6);
}
}
.settings {
display: none;
margin-left: 0.5em;
margin-top: 0.65em;
cursor: pointer;
width: 1em;
height: 1em;
border-radius: 50%;
line-height: 1em;
svg {
fill: rgba(@base-color, 0.6);
&:hover {
fill: @base-color;
}
}
}
.termopts {
margin-top: 5px;
display: none;
.resize-button {
cursor: pointer;
padding-left: 3px;
padding-right: 3px;
}
}
.metapart-mono {
margin-left: 8px;
margin-top: 4px;
white-space: nowrap;
}
.cmdtext {
overflow: hidden;
margin-left: 0;
}
.cmdtext-overflow {
flex-shrink: 0;
padding-right: 2px;
color: @term-white;
cursor: pointer;
margin-top: 4px;
}
}
.cmdtext-expanded-wrapper {
margin-top: 2px;
padding-left: 6px;
overflow-y: auto;
max-height: 60px;
border-left: 2px solid #333;
margin-left: 3px;
.cmdtext-expanded {
white-space: pre;
color: @term-white;
padding-bottom: 5px;
}
}
.cmd-hints {
position: absolute;
bottom: 0px;
right: 0px;
display: none;
.hint-item {
border-radius: 3px;
}
}
}
.line.line-invalid {
color: @term-white;
margin-left: 5px;
}
.lines {
display: flex;
flex-direction: column;

View File

@ -6,11 +6,10 @@
display: flex;
flex-direction: column;
position: absolute;
bottom: 12px;
right: 12px;
width: calc(100% - 24px);
padding: 12px;
padding-bottom: 6px;
bottom: var(--termfontsize);
right: var(--termfontsize);
width: calc(100% - 2 * var(--termfontsize));
padding: var(--termfontsize);
z-index: 100;
background: @background-session-components;
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5), 0px 3px 8px 0px rgba(0, 0, 0, 0.35),
@ -24,7 +23,7 @@
}
&.has-info {
padding-top: 10px;
padding-top: var(--termpad);
}
.focus-indicator {
@ -34,7 +33,7 @@
}
&.has-history {
padding-top: 5px;
padding-top: var(--termpad);
height: max(300px, 40%);
}
@ -101,7 +100,11 @@
.cmd-input-field {
position: relative;
padding-right: 17em;
padding-right: var(--termpad);
font-family: var(--termfontfamily);
font-weight: normal;
line-height: var(--termlineheight);
font-size: var(--termfontsize);
.cmd-hints {
position: absolute;
@ -115,10 +118,15 @@
.textareainput-div {
position: relative;
&.control {
padding: var(--termpad) 0;
}
.shelltag {
position: absolute;
bottom: 4px;
right: 3px;
// 13px = 10px height + 3px padding. subtract termpad to account for textareainput-div padding
bottom: calc(-13px + var(--termpad));
right: 0px;
font-size: 10px;
color: @text-secondary;
line-height: 1;
@ -131,14 +139,15 @@
textarea {
color: @term-bright-white;
background-color: @textarea-background;
padding: 0.5em;
padding: var(--termpad);
resize: none;
overflow: auto;
overflow-wrap: anywhere;
border-color: transparent;
border: none;
font-family: var(--termfontfamily);
font-weight: normal;
line-height: var(--termlineheight);
font-size: var(--termfontsize);
&.display-disabled {
background-color: #444;
}
@ -350,7 +359,7 @@
.cmd-input-info {
flex-shrink: 1;
overflow-y: auto;
margin-bottom: 5px;
margin-bottom: var(--termpad);
.info-msg {
color: @soft-blue;
@ -377,6 +386,9 @@
flex-direction: row;
flex-wrap: wrap;
padding-bottom: 5px;
font-weight: normal;
font-family: var(--termfontfamily);
font-size: var(--termfontsize);
.info-comp {
min-width: 200px;

View File

@ -214,37 +214,6 @@ class CmdInput extends React.Component<{}, {}> {
screen={screen}
onHeightChange={this.handleInnerHeightUpdate}
/>
<div className="control cmd-exec">
{/**<div onClick={inputModel.toggleExpandInput} className="hint-item color-white">
{inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")})
</div>**/}
{!focusVal && (
<div onClick={this.clickFocusInputHint} className="cmd-btn hoverEffect">
<div className="hint-elem">focus input ({renderCmdText("I")})</div>
</div>
)}
{focusVal && (
<div className="cmd-btn hoverEffect">
<If condition={historyShow}>
<div className="hint-elem" onMouseDown={this.clickHistoryHint}>
close (esc)
</div>
</If>
<If condition={!historyShow}>
<div className="hint-elem" onMouseDown={this.clickHistoryHint}>
history (ctrl-r)
</div>
<div className="hint-elem" onMouseDown={this.clickAIHint}>
AI (ctrl-space)
</div>
</If>
</div>
)}
<ExecIcon
onClick={inputModel.uiSubmitCommand}
className={`icon ${inputModel.getCurLine().trim() === "" ? "disabled" : "hoverEffect"}`}
/>
</div>
</div>
</div>
);

View File

@ -579,11 +579,11 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
if (activeScreen != null) {
activeScreen.focusType.get(); // for reaction
}
let termFontSize = 14;
// fontSize*1.5 (line-height) + 2 * 0.5em padding
let computedInnerHeight = displayLines * (termFontSize * 1.5) + 2 * 0.5 * termFontSize;
// inner height + 2*1em padding
let computedOuterHeight = computedInnerHeight + 2 * 1.0 * termFontSize;
let termFontSize = GlobalModel.getTermFontSize();
let fontSize = getMonoFontSize(termFontSize);
let termPad = Math.floor(fontSize.height / 2);
let computedInnerHeight = displayLines * fontSize.height + 2 * termPad;
let computedOuterHeight = computedInnerHeight + 2 * termPad;
let shellType: string = "";
let screen = GlobalModel.getActiveScreen();
if (screen != null) {

View File

@ -108,7 +108,7 @@ class ScreenView extends React.Component<{ session: Session; screen: Screen }, {
if (screenWidth == null) {
return <div className="screen-view" ref={this.screenViewRef}></div>;
}
let fontSize = GlobalModel.termFontSize.get();
let fontSize = GlobalModel.getTermFontSize();
let dprStr = sprintf("%0.3f", GlobalModel.devicePixelRatio.get());
let viewOpts = screen.viewOpts.get();
let hasSidebar = viewOpts?.sidebar?.open;

View File

@ -28,7 +28,7 @@ class ForwardLineContainer {
this.winSize = winSize;
let termWrap = this.getTermWrap(this.lineId);
if (termWrap != null) {
let fontSize = this.globalModel.termFontSize.get();
let fontSize = this.globalModel.getTermFontSize();
let cols = windowWidthToCols(winSize.width, fontSize);
let rows = windowHeightToRows(winSize.height, fontSize);
termWrap.resizeCols(cols);

View File

@ -114,8 +114,8 @@ class HistoryViewModel {
this.specialLineContainer = null;
} else {
this.activeItem.set(hitem.historyid);
let width = termWidthFromCols(80, this.globalModel.termFontSize.get());
let height = termHeightFromRows(25, this.globalModel.termFontSize.get(), 25);
let width = termWidthFromCols(80, this.globalModel.getTermFontSize());
let height = termHeightFromRows(25, this.globalModel.getTermFontSize(), 25);
this.specialLineContainer = new SpecialLineContainer(
this,
{ width, height },

View File

@ -33,7 +33,7 @@ import { MainSidebarModel } from "./mainsidebar";
import { Screen } from "./screen";
import { Cmd } from "./cmd";
import { GlobalCommandRunner } from "./global";
import { clearMonoFontCache } from "@/util/textmeasure";
import { clearMonoFontCache, getMonoFontSize } from "@/util/textmeasure";
import type { TermWrap } from "@/plugins/terminal/term";
type SWLinePtr = {
@ -338,19 +338,29 @@ class Model {
return this.termFontSize.get();
}
setTermFontSize(fontSize: number) {
updateTermFontSizeVars(fontSize: number, force: boolean) {
if (!force && fontSize == this.termFontSize.get()) {
return;
}
if (fontSize < appconst.MinFontSize) {
fontSize = appconst.MinFontSize;
}
if (fontSize > appconst.MaxFontSize) {
fontSize = appconst.MaxFontSize;
}
const monoFontSize = getMonoFontSize(fontSize);
mobx.action(() => {
this.termFontSize.set(fontSize);
this.bumpRenderVersion();
this.setStyleVar("--termfontsize", fontSize + "px");
this.setStyleVar("--termlineheight", monoFontSize.height + "px");
this.setStyleVar("--termpad", Math.floor(monoFontSize.height / 2) + "px");
})();
}
setStyleVar(name: string, value: string) {
document.documentElement.style.setProperty(name, value);
}
getBaseWsHostPort(): string {
if (this.isDev) {
return appconst.DevServerWsEndpoint;
@ -1131,6 +1141,16 @@ class Model {
}
setClientData(clientData: ClientDataType) {
let newFontFamily = clientData?.feopts?.termfontfamily;
if (newFontFamily == null) {
newFontFamily = "JetBrains Mono";
}
let newFontSize = clientData?.feopts?.termfontsize;
if (newFontSize == null) {
newFontSize = appconst.DefaultTermFontSize;
}
const ffUpdated = newFontFamily != this.getTermFontFamily();
const fsUpdated = newFontSize != this.getTermFontSize();
mobx.action(() => {
this.clientData.set(clientData);
})();
@ -1139,15 +1159,17 @@ class Model {
shortcut = clientData?.clientopts?.globalshortcut;
}
getApi().reregisterGlobalShortcut(shortcut);
let fontFamily = clientData?.feopts?.termfontfamily;
if (fontFamily == null) {
fontFamily = "JetBrains Mono";
if (ffUpdated) {
// this also updates fontSize vars
loadFonts(newFontFamily);
document.fonts.ready.then(() => {
clearMonoFontCache();
this.updateTermFontSizeVars(this.termFontSize.get(), true); // forces an update of css vars
this.bumpRenderVersion();
});
} else if (fsUpdated) {
this.updateTermFontSizeVars(newFontSize, true);
}
loadFonts(fontFamily);
document.fonts.ready.then(() => {
clearMonoFontCache();
this.bumpRenderVersion();
});
}
submitCommandPacket(cmdPk: FeCmdPacketType, interactive: boolean): Promise<CommandRtnType> {

View File

@ -402,15 +402,15 @@ class Screen {
return;
}
this.lastScreenSize = winSize;
let cols = windowWidthToCols(winSize.width, this.globalModel.termFontSize.get());
let rows = windowHeightToRows(winSize.height, this.globalModel.termFontSize.get());
let cols = windowWidthToCols(winSize.width, this.globalModel.getTermFontSize());
let rows = windowHeightToRows(winSize.height, this.globalModel.getTermFontSize());
this._termSizeCallback(rows, cols);
}
getMaxContentSize(): WindowSize {
if (this.lastScreenSize == null) {
let width = termWidthFromCols(80, this.globalModel.termFontSize.get());
let height = termHeightFromRows(25, this.globalModel.termFontSize.get(), 25);
let width = termWidthFromCols(80, this.globalModel.getTermFontSize());
let height = termHeightFromRows(25, this.globalModel.getTermFontSize(), 25);
return { width, height };
}
let winSize = this.lastScreenSize;
@ -423,8 +423,8 @@ class Screen {
getIdealContentSize(): WindowSize {
if (this.lastScreenSize == null) {
let width = termWidthFromCols(80, this.globalModel.termFontSize.get());
let height = termHeightFromRows(25, this.globalModel.termFontSize.get(), 25);
let width = termWidthFromCols(80, this.globalModel.getTermFontSize());
let height = termHeightFromRows(25, this.globalModel.getTermFontSize(), 25);
return { width, height };
}
let winSize = this.lastScreenSize;

View File

@ -127,7 +127,7 @@ class SpecialLineContainer {
}
let termWrap = this.getTermWrap(cmd.lineId);
if (termWrap == null) {
let cols = windowWidthToCols(width, this.globalModel.termFontSize.get());
let cols = windowWidthToCols(width, this.globalModel.getTermFontSize());
let usedRows = this.globalModel.getContentHeight(context);
if (usedRows != null) {
return usedRows;

View File

@ -307,7 +307,7 @@ class SourceCodeRenderer extends React.Component<
let allowEditing = this.getAllowEditing();
if (!allowEditing) {
const noOfLines = Math.max(this.state.code.split("\n").length, 5);
const lineHeight = Math.ceil(GlobalModel.termFontSize.get() * 1.5);
const lineHeight = Math.ceil(GlobalModel.getTermFontSize() * 1.5);
_editorHeight = Math.min(noOfLines * lineHeight + 10, fullWindowHeight);
}
this.setState({ editorHeight: _editorHeight }, () => {
@ -434,7 +434,7 @@ class SourceCodeRenderer extends React.Component<
<div
className="code-renderer"
style={{
fontSize: GlobalModel.termFontSize.get(),
fontSize: GlobalModel.getTermFontSize(),
color: "white",
}}
>

View File

@ -7,6 +7,7 @@ import * as mobx from "mobx";
import { debounce } from "throttle-debounce";
import * as util from "@/util/util";
import { GlobalModel } from "@/models";
import cn from "classnames";
class SimpleBlobRendererModel {
context: RendererContext;
@ -246,7 +247,7 @@ class SimpleBlobRenderer extends React.Component<
return (
<div
ref={this.wrapperDivRef}
className="renderer-loading"
className={cn("renderer-loading", { "zero-height": height == 0 })}
style={{ minHeight: height, fontSize: model.opts.termFontSize }}
>
loading content <i className="fa fa-ellipsis fa-fade" />
@ -259,7 +260,7 @@ class SimpleBlobRenderer extends React.Component<
}
let { festate, cmdstr, exitcode } = this.props.initParams.rawCmd;
return (
<div ref={this.wrapperDivRef} className="sr-wrapper">
<div ref={this.wrapperDivRef} className={cn("sr-wrapper", { "zero-height": model.savedHeight == 0 })}>
<Comp
cwd={festate.cwd}
cmdstr={cmdstr}

View File

@ -182,7 +182,7 @@ const CSVRenderer: FC<Props> = (props: Props) => {
if (isFileTooLarge) {
return (
<div className="csv-renderer" style={{ fontSize: GlobalModel.termFontSize.get() }}>
<div className="csv-renderer" style={{ fontSize: GlobalModel.getTermFontSize() }}>
<div className="load-error-text">The file size exceeds 10MB and cannot be displayed.</div>
</div>
);

View File

@ -1,7 +1,6 @@
@import "@/common/themes/themes.less";
.image-renderer {
padding: 10px;
img {
display: block;
}

View File

@ -2,7 +2,6 @@
.markdown-renderer {
color: @term-white;
margin-bottom: 10px;
.scroller {
overflow-y: auto;

View File

@ -148,7 +148,7 @@ class TerminalRenderer extends React.Component<
.get();
let cmd = screen.getCmd(line); // will not be null
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
let termHeight = termHeightFromRows(usedRows, GlobalModel.termFontSize.get(), cmd.getTermMaxRows());
let termHeight = termHeightFromRows(usedRows, GlobalModel.getTermFontSize(), cmd.getTermMaxRows());
if (usedRows === 0) {
termHeight = 0;
}