More Prompt Updates / Line Action Implementation (#357)

* termfontsize-sm (for meta1).  add some small margin.  remove rootIndicator.  switch cwd/remote to red if root

* break LineHeader out into its own react component

* actual set our --term colors as the *theme* for xtermjs

* move line-actions into its own div (absolute position).  hover/background colors

* add duration, format renderer, spacing, remove meta-wrap

* simplify cmdtext output.  simpler multi-line rendering.

* adjust position/height of line actions for better interaction with long lines

* fix horiz scrolling

* remove unused

* quick fix for text lines
This commit is contained in:
Mike Sawka
2024-02-28 18:23:14 -08:00
committed by GitHub
parent bbf471566f
commit 189e632ff7
15 changed files with 415 additions and 473 deletions
+3 -3
View File
@@ -17,12 +17,12 @@ body {
body {
&.is-dev .sidebar {
background-color: var(--sidebar-dev-bg-color);
background-color: var(--app-panel-bg-color-dev);
}
}
body .sidebar {
background-color: var(--sidebar-bg-color);
background-color: var(--app-panel-bg-color);
}
textarea {
@@ -117,7 +117,7 @@ svg.icon {
cursor: pointer;
}
.hideScrollbarUntillHover {
.scrollbar-hide-until-hover {
overflow: scroll;
&::-webkit-scrollbar-thumb,
+1 -1
View File
@@ -7,7 +7,7 @@ import cn from "classnames";
class TabIcon extends React.Component<{ icon: string; color: string }> {
render() {
let { icon, color, className } = this.props;
let { icon, color } = this.props;
let iconClass = "";
if (icon === "default" || icon === "square") {
iconClass = "fa-solid fa-square fa-fw";
+3 -8
View File
@@ -2,10 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
.term-prompt {
font-weight: normal;
font-size: var(--termfontsize);
line-height: var(--termlineheight);
// we are explicitly *not* setting font size properties here so prompt will inherit from caller
font-family: var(--termfontfamily);
font-weight: normal;
color: var(--term-gray);
i {
@@ -62,11 +61,7 @@
color: var(--term-bright-green);
}
.term-prompt-end-user {
color: var(--term-bright-green);
}
.term-prompt-end-root {
&.term-prompt-isroot .term-prompt-cwd {
color: var(--term-bright-red);
}
}
+9 -14
View File
@@ -94,11 +94,7 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
if (remote && remote.remotecanonicalname) {
remoteTitle = "connected to " + remote.remotecanonicalname;
}
let cwdElem = (
<span title="current directory" className="term-prompt-cwd">
{cwd}
</span>
);
let cwdElem = <span className="term-prompt-cwd">{cwd}</span>;
let remoteElem = null;
if (remoteStr != "local") {
remoteElem = (
@@ -107,12 +103,6 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
</span>
);
}
let rootIndicatorElem = null;
if (isRoot) {
rootIndicatorElem = <span className="term-prompt-end-root">#</span>;
} else {
rootIndicatorElem = <span className="term-prompt-end-user">$</span>;
}
let branchElem = null;
let pythonElem = null;
let condaElem = null;
@@ -142,9 +132,14 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
);
}
return (
<span className={cn("term-prompt", { "term-prompt-color": this.props.color })}>
{remoteElem} {condaElem} {pythonElem}
{cwdElem} {branchElem} {rootIndicatorElem}
<span
className={cn(
"term-prompt",
{ "term-prompt-color": this.props.color },
{ "term-prompt-isroot": isRoot }
)}
>
{remoteElem} {cwdElem} {branchElem} {condaElem} {pythonElem}
</span>
);
}
-18
View File
@@ -1,18 +0,0 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
/**
* The file contains barebones of styling to appy themes to Prompt.
* @TODO: Find a way to change the theme system-wide. atm, we are captruing colors in main.less
*/
const themes = [
{
id: "default",
terminal: { foreground: "#eceeec", background: "black" },
},
];
const getTheme = (_id = "default") => themes.find(({ id }) => id === _id);
export { getTheme };
+95 -147
View File
@@ -2,30 +2,13 @@
margin: 0;
padding: calc(var(--termpad) * 2) var(--termpad) calc(var(--termpad) * 2 + 1px) calc(var(--termpad) * 3);
display: flex;
flex-direction: column;
overflow: hidden;
flex-shrink: 0;
position: relative;
font-weight: normal;
font-family: var(--termfontfamily);
&.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;
}
}
scroll-margin-bottom: 20px;
&.line-invalid {
color: var(--term-text-white);
@@ -118,7 +101,6 @@
display: inline-block;
height: 1em;
margin-left: 0.5em;
margin-right: 0.5em;
vertical-align: text-top;
fill: var(--primary-background-color);
}
@@ -152,152 +134,118 @@
display: block;
}
.line-header {
.line-header + div:not(.zero-height) {
margin-top: calc(var(--termpad) + 2px);
}
&:hover .line-actions {
background-color: var(--app-panel-bg-color);
.line-icon {
visibility: visible;
}
}
.line-actions {
position: absolute;
right: calc(var(--termpad) * 2);
top: 0;
font-size: 14px;
color: var(--line-actions-inactive-color);
border-radius: 4px;
padding-left: 4px;
padding-right: 4px;
line-height: 1.2;
display: flex;
flex-direction: row;
align-items: center;
.line-icon {
visibility: hidden;
&.active {
visibility: visible;
}
&:hover {
color: var(--line-actions-active-color);
}
padding: 4px;
cursor: pointer;
}
.line-icon + .line-icon:not(.line-icon-shrink-left) {
margin-left: 3px;
}
}
.line-header {
display: flex;
flex-direction: column;
width: 100%;
font-weight: normal;
font-family: var(--termfontfamily);
font-size: var(--termfontsize);
line-height: var(--termlineheight);
&.is-expanded {
height: auto;
.meta + .meta {
margin-top: 2px;
}
.line-icon {
display: block;
visibility: hidden;
cursor: pointer;
padding: 3px;
border-radius: 50%;
}
.line-icon-show {
visibility: visible;
}
.line-icon + .line-icon {p
margin-left: 5px;
}
.line-icon.active {
visibility: visible;
display: block;
}
&:hover .line-icon {
visibility: visible;
display: block;
opacity: 1;
}
.meta.meta-line1 {
color: var(--term-gray);
}
.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: var(--line-meta-user-color);
font-weight: bold;
margin-top: 1px;
margin-right: 10px;
}
.ts,
.termopts,
.renderer {
.meta {
display: flex;
}
flex-direction: row;
.renderer {
margin-left: 3px;
svg {
width: 1em;
.user {
color: var(--line-meta-user-color);
font-weight: bold;
margin-top: 1px;
margin-right: 10px;
}
&.meta-line1 {
display: flex;
flex-direction: row;
font-size: var(--termfontsize-sm);
line-height: var(--termlineheight-sm);
color: var(--term-gray);
}
.meta-divider {
margin-left: var(--termpad);
margin-right: var(--termpad);
}
.ts,
.termopts,
.renderer {
display: flex;
}
.renderer .renderer-icon {
margin-right: 0.5em;
fill: var(--line-svg-fill-color);
}
.metapart-mono {
margin-left: 8px;
white-space: nowrap;
}
}
.settings {
display: none;
margin-left: 0.5em;
cursor: pointer;
width: 1em;
height: 1em;
border-radius: 50%;
line-height: 1em;
svg {
fill: var(--line-svg-fill-color);
&:hover {
fill: var(--line-svg-hover-fill-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: var(--term-text-white);
cursor: pointer;
}
.meta-cmdtext {
color: var(--term-bright-white);
}
}
.cmdtext-expanded-wrapper {
padding-left: 6px;
overflow-y: auto;
max-height: 60px;
border-left: 2px solid #333;
margin-left: 3px;
.cmdtext-expanded {
overflow: auto;
max-height: calc(var(--termlineheight) * 3.3);
white-space: pre;
color: var(--term-bright-white);
font-weight: bold;
width: calc(100% - var(--termpad) * 2);
color: var(--term-text-white);
padding-bottom: 5px;
&.is-multiline {
border-left: 2px solid #333;
margin-left: 3px;
padding-left: 6px;
}
}
}
+234 -273
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -28,12 +28,16 @@
font-size: var(--termfontsize);
}
.lines-spacer + .line-sep-labeled {
margin-top: var(--termpad);
}
.line-sep-labeled::before,
.line-sep-labeled::after {
content: "";
height: 1px;
flex-grow: 1;
background-color: var(--app-text-color);
background-color: var(--term-gray);
}
.line-sep-labeled::before {
+6 -2
View File
@@ -9,6 +9,8 @@
--termfontsize: 12px;
--termlineheight: 15px;
--termpad: 7px; // padding value (scaled to termfontsize)
--termfontsize-sm: 10px;
--termlineheight-sm: 13px;
// other fonts
--fixed-font: "Martian Mono", sans-serif;
@@ -43,6 +45,8 @@
--app-border-color: rgb(51, 51, 51);
--app-maincontent-bg-color: #333;
--app-border-radius: 10px;
--app-panel-bg-color: rgba(21, 23, 21, 1);
--app-panel-bg-color-dev: rgb(21, 23, 48);
// global generic colors
--app-black: rgb(0, 0, 0);
@@ -150,8 +154,6 @@
--hotkey-text-color: rgb(195, 200, 194);
// sidebar colors
--sidebar-bg-color: rgba(21, 23, 21, 1);
--sidebar-dev-bg-color: rgb(21, 23, 48);
--sidebar-settings-color: rgb(255, 255, 255);
--sidebar-separator-color: var(--app-border-color);
--sidebar-highlight-color: rgba(241, 246, 243, 0.08);
@@ -186,6 +188,8 @@
--line-status-success-fill: rgb(88, 193, 66);
--line-status-error-fill: #cc0000;
--line-status-warning-fill: #ffa500;
--line-actions-inactive-color: rgba(255, 255, 255, 0.5);
--line-actions-active-color: rgba(255, 255, 255, 1);
// view colors
// todo: bookmarks is a view, colors must be unified with --view* colors
+1 -1
View File
@@ -324,7 +324,7 @@ class MainSideBar extends React.Component<MainSideBarProps, {}> {
]}
/>
<div
className="middle hideScrollbarUntillHover"
className="middle scrollbar-hide-until-hover"
id="sidebar-middle"
style={{
maxHeight: `calc(100vh - ${this.middleHeightSubtractor.get()}px)`,
+1 -1
View File
@@ -182,7 +182,7 @@ class ScreenTabs extends React.Component<
{/* Inner container ensures that hovering over the scrollbar doesn't trigger the hover effect on the tabs. This prevents weird flickering of the icons when the mouse is moved over the scrollbar. */}
<div
key="container-inner"
className="screen-tabs-container-inner no-highlight-scrollbar hideScrollbarUntillHover"
className="screen-tabs-container-inner no-highlight-scrollbar scrollbar-hide-until-hover"
>
<Reorder.Group
className="screen-tabs"
+4
View File
@@ -37,6 +37,10 @@ class Cmd {
return this.data.get().restartts;
}
getDurationMs(): number {
return this.data.get().durationms;
}
getAsWebCmd(lineid: string): WebCmd {
let cmd = this.data.get();
let remote = this.model.getRemote(this.remote.remoteid);
+5 -1
View File
@@ -353,12 +353,16 @@ class Model {
if (fontSize > appconst.MaxFontSize) {
fontSize = appconst.MaxFontSize;
}
const fontSizeSm = fontSize - 2;
const monoFontSize = getMonoFontSize(fontSize);
const monoFontSizeSm = getMonoFontSize(fontSizeSm);
mobx.action(() => {
this.bumpRenderVersion();
this.setStyleVar("--termfontsize", fontSize + "px");
this.setStyleVar("--termlineheight", monoFontSize.height + "px");
this.setStyleVar("--termpad", Math.floor(monoFontSize.height / 2) + "px");
this.setStyleVar("--termpad", monoFontSize.pad + "px");
this.setStyleVar("--termfontsize-sm", fontSizeSm + "px");
this.setStyleVar("--termlineheight-sm", monoFontSizeSm.height + "px");
})();
}
+27 -3
View File
@@ -3,6 +3,7 @@
import * as mobx from "mobx";
import { Terminal } from "xterm";
import type { ITheme } from "xterm";
//TODO: replace with `@xterm/addon-web-links` when it's available as stable
import { WebLinksAddon } from "xterm-addon-web-links";
import { sprintf } from "sprintf-js";
@@ -10,7 +11,6 @@ import { boundMethod } from "autobind-decorator";
import { windowWidthToCols, windowHeightToRows } from "@/util/textmeasure";
import { boundInt } from "@/util/util";
import { GlobalModel } from "@/models";
import { getTheme } from "@/common/themes/themes";
type DataUpdate = {
data: Uint8Array;
@@ -36,6 +36,30 @@ type TermWrapOpts = {
onUpdateContentHeight: (termContext: RendererContext, height: number) => void;
};
function getThemeFromCSSVars(): ITheme {
let theme: ITheme = {};
let rootStyle = getComputedStyle(document.documentElement);
theme.foreground = rootStyle.getPropertyValue("--term-white");
theme.background = rootStyle.getPropertyValue("--term-black");
theme.black = rootStyle.getPropertyValue("--term-black");
theme.red = rootStyle.getPropertyValue("--term-red");
theme.green = rootStyle.getPropertyValue("--term-green");
theme.yellow = rootStyle.getPropertyValue("--term-yellow");
theme.blue = rootStyle.getPropertyValue("--term-blue");
theme.magenta = rootStyle.getPropertyValue("--term-magenta");
theme.cyan = rootStyle.getPropertyValue("--term-cyan");
theme.white = rootStyle.getPropertyValue("--term-white");
theme.brightBlack = rootStyle.getPropertyValue("--term-bright-black");
theme.brightRed = rootStyle.getPropertyValue("--term-bright-red");
theme.brightGreen = rootStyle.getPropertyValue("--term-bright-green");
theme.brightYellow = rootStyle.getPropertyValue("--term-bright-yellow");
theme.brightBlue = rootStyle.getPropertyValue("--term-bright-blue");
theme.brightMagenta = rootStyle.getPropertyValue("--term-bright-magenta");
theme.brightCyan = rootStyle.getPropertyValue("--term-bright-cyan");
theme.brightWhite = rootStyle.getPropertyValue("--term-bright-white");
return theme;
}
// cmd-instance
class TermWrap {
terminal: any;
@@ -86,13 +110,13 @@ class TermWrap {
let cols = windowWidthToCols(opts.winSize.width, opts.fontSize);
this.termSize = { rows: opts.termOpts.rows, cols: cols };
}
const { terminal } = getTheme();
let theme = getThemeFromCSSVars();
this.terminal = new Terminal({
rows: this.termSize.rows,
cols: this.termSize.cols,
fontSize: opts.fontSize,
fontFamily: opts.fontFamily,
theme: { foreground: terminal.foreground, background: terminal.background },
theme: theme,
});
this.terminal.loadAddon(
new WebLinksAddon((e, uri) => {
+21
View File
@@ -268,6 +268,26 @@ function getDateStr(d: Date): string {
return dowStr + " " + yearStr + "-" + monthStr + "-" + dayStr;
}
function formatDuration(ms: number): string {
if (ms < 1000) {
return ms + "ms";
}
if (ms < 10000) {
return (ms / 1000).toFixed(2) + "s";
}
if (ms < 100000) {
return (ms / 1000).toFixed(1) + "s";
}
if (ms < 60 * 60 * 1000) {
let mins = Math.floor(ms / 60000);
let secs = Math.floor((ms % 60000) / 1000);
return mins + "m" + secs + "s";
}
let hours = Math.floor(ms / (60 * 60 * 1000));
let mins = Math.floor((ms % (60 * 60 * 1000)) / 60000);
return hours + "h" + mins + "m";
}
function getRemoteConnVal(r: RemoteType): number {
switch (r.status) {
case "connected":
@@ -400,4 +420,5 @@ export {
getRemoteName,
ces,
fireAndForget,
formatDuration,
};