diff --git a/src/app/app.less b/src/app/app.less index 554e13bc0..b32d939ab 100644 --- a/src/app/app.less +++ b/src/app/app.less @@ -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, diff --git a/src/app/common/elements/tabicon.tsx b/src/app/common/elements/tabicon.tsx index 94717f332..156aa1aff 100644 --- a/src/app/common/elements/tabicon.tsx +++ b/src/app/common/elements/tabicon.tsx @@ -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"; diff --git a/src/app/common/prompt/prompt.less b/src/app/common/prompt/prompt.less index a7df41e17..f596dfb3e 100644 --- a/src/app/common/prompt/prompt.less +++ b/src/app/common/prompt/prompt.less @@ -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); } } diff --git a/src/app/common/prompt/prompt.tsx b/src/app/common/prompt/prompt.tsx index 70ea848a5..21cef0946 100644 --- a/src/app/common/prompt/prompt.tsx +++ b/src/app/common/prompt/prompt.tsx @@ -94,11 +94,7 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record - {cwd} - - ); + let cwdElem = {cwd}; let remoteElem = null; if (remoteStr != "local") { remoteElem = ( @@ -107,12 +103,6 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record ); } - let rootIndicatorElem = null; - if (isRoot) { - rootIndicatorElem = #; - } else { - rootIndicatorElem = $; - } let branchElem = null; let pythonElem = null; let condaElem = null; @@ -142,9 +132,14 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record - {remoteElem} {condaElem} {pythonElem} - {cwdElem} {branchElem} {rootIndicatorElem} + + {remoteElem} {cwdElem} {branchElem} {condaElem} {pythonElem} ); } diff --git a/src/app/common/themes/themes.ts b/src/app/common/themes/themes.ts deleted file mode 100644 index 808e1ff9a..000000000 --- a/src/app/common/themes/themes.ts +++ /dev/null @@ -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 }; diff --git a/src/app/line/line.less b/src/app/line/line.less index f6bb8aecf..661cda984 100644 --- a/src/app/line/line.less +++ b/src/app/line/line.less @@ -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; + } } } diff --git a/src/app/line/linecomps.tsx b/src/app/line/linecomps.tsx index 26ee876cf..6fc113f24 100644 --- a/src/app/line/linecomps.tsx +++ b/src/app/line/linecomps.tsx @@ -24,8 +24,7 @@ import { Prompt } from "@/common/prompt/prompt"; import * as lineutil from "./lineutil"; import { ErrorBoundary } from "@/common/error/errorboundary"; import * as appconst from "@/app/appconst"; - -import { ReactComponent as FillIcon } from "@/assets/icons/line/fill.svg"; +import * as util from "@/util/util"; import "./line.less"; @@ -35,6 +34,225 @@ function cmdHasError(cmd: Cmd): boolean { return cmd.getStatus() == "error" || cmd.getExitCode() != 0; } +function getIsHidePrompt(line: LineType): boolean { + let rendererPlugin: RendererPluginType = null; + const isNoneRenderer = line.renderer == "none"; + if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) { + rendererPlugin = PluginModel.getRendererPluginByName(line.renderer); + } + const hidePrompt = rendererPlugin?.hidePrompt; + return hidePrompt; +} + +@mobxReact.observer +class LineActions extends React.Component<{ screen: LineContainerType; line: LineType; cmd: Cmd }, {}> { + @boundMethod + clickStar() { + const { line } = this.props; + if (!line.star || line.star == 0) { + GlobalCommandRunner.lineStar(line.lineid, 1); + } else { + GlobalCommandRunner.lineStar(line.lineid, 0); + } + } + + @boundMethod + clickPin() { + const { line } = this.props; + if (!line.pinned) { + GlobalCommandRunner.linePin(line.lineid, true); + } else { + GlobalCommandRunner.linePin(line.lineid, false); + } + } + + @boundMethod + clickBookmark() { + const { line } = this.props; + GlobalCommandRunner.lineBookmark(line.lineid); + } + + @boundMethod + clickDelete() { + const { line } = this.props; + GlobalCommandRunner.lineDelete(line.lineid, true); + } + + @boundMethod + clickRestart() { + const { line } = this.props; + GlobalCommandRunner.lineRestart(line.lineid, true); + } + + @boundMethod + clickMinimize() { + const { line } = this.props; + const isMinimized = line.linestate["wave:min"]; + GlobalCommandRunner.lineMinimize(line.lineid, !isMinimized, true); + } + + @boundMethod + clickMoveToSidebar() { + const { line } = this.props; + GlobalCommandRunner.screenSidebarAddLine(line.lineid); + } + + @boundMethod + clickRemoveFromSidebar() { + GlobalCommandRunner.screenSidebarRemove(); + } + + @boundMethod + handleResizeButton() { + console.log("resize button"); + } + + @boundMethod + handleLineSettings(e: any): void { + e.preventDefault(); + e.stopPropagation(); + let { line } = this.props; + if (line != null) { + mobx.action(() => { + GlobalModel.lineSettingsModal.set(line.linenum); + })(); + GlobalModel.modalsModel.pushModal(appconst.LINE_SETTINGS); + } + } + + render() { + let { line, screen } = this.props; + const isMinimized = line.linestate["wave:min"]; + const containerType = screen.getContainerType(); + return ( +
+
+ +
+
+ +
+
+ +
+ +
+ + + + + + +
+
+ +
+
+ +
+
+ +
+ +
+
+
+ ); + } +} + +@mobxReact.observer +class LineHeader extends React.Component<{ screen: LineContainerType; line: LineType; cmd: Cmd }, {}> { + renderCmdText(cmd: Cmd): any { + if (cmd == null) { + return ( +
+ (cmd not found) +
+ ); + } + const isMultiLine = lineutil.isMultiLineCmdText(cmd.getCmdStr()); + return ( + +
+ {lineutil.getFullCmdText(cmd.getCmdStr())} +
+
+ ); + } + + renderMeta1(cmd: Cmd) { + let { line } = this.props; + let formattedTime: string = ""; + let restartTs = cmd.getRestartTs(); + let timeTitle: string = null; + if (restartTs != null && restartTs > 0) { + formattedTime = "restarted @ " + lineutil.getLineDateTimeStr(restartTs); + timeTitle = "original start time " + lineutil.getLineDateTimeStr(line.ts); + } else { + formattedTime = lineutil.getLineDateTimeStr(line.ts); + } + let renderer = line.renderer; + let durationMs = cmd.getDurationMs(); + return ( +
+ +
|
+ +
|
+
+ {formattedTime} 0}>({util.formatDuration(durationMs)}) +
+ +
|
+
+ + {renderer} +
+
+
+ ); + } + + render() { + let { line, cmd } = this.props; + const hidePrompt = getIsHidePrompt(line); + return ( +
+ {this.renderMeta1(cmd)} + {this.renderCmdText(cmd)} +
+ ); + } +} + @mobxReact.observer class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRightClick?: (e: any) => void }, {}> { render() { @@ -193,14 +411,7 @@ class LineCmd extends React.Component< {} > { lineRef: React.RefObject = React.createRef(); - cmdTextRef: React.RefObject = React.createRef(); lastHeight: number; - isOverflow: OV = mobx.observable.box(false, { - name: "line-overflow", - }); - isCmdExpanded: OV = mobx.observable.box(false, { - name: "cmd-expanded", - }); constructor(props) { super(props); @@ -208,53 +419,6 @@ class LineCmd extends React.Component< componentDidMount() { this.componentDidUpdate(null, null, null); - this.checkCmdText(); - } - - @boundMethod - handleExpandCmd(): void { - mobx.action(() => { - this.isCmdExpanded.set(true); - })(); - } - - renderCmdText(cmd: Cmd): any { - if (cmd == null) { - return ( -
- (cmd not found) -
- ); - } - if (this.isCmdExpanded.get()) { - return ( - -
-
- -
-
-
-
{lineutil.getFullCmdText(cmd.getCmdStr())}
-
-
- ); - } - const isMultiLine = lineutil.isMultiLineCmdText(cmd.getCmdStr()); - return ( -
-
- - - {lineutil.getSingleLineCmdText(cmd.getCmdStr())} -
- -
- ...▼ -
-
-
- ); } // TODO: this might not be necessary anymore because we're using this.lastHeight @@ -268,34 +432,6 @@ class LineCmd extends React.Component< componentDidUpdate(prevProps, prevState, snapshot: { height: number }): void { this.handleHeightChange(); - this.checkCmdText(); - } - - checkCmdText() { - const metaElem = this.cmdTextRef.current; - if (metaElem == null || metaElem.childNodes.length == 0) { - return; - } - const metaElemWidth = metaElem.offsetWidth; - if (metaElemWidth == 0) { - return; - } - const metaChild = metaElem.firstChild; - if (metaChild == null) { - return; - } - const children = metaChild.childNodes; - let childWidth = 0; - for (let i = 0; i < children.length; i++) { - let ch = children[i]; - childWidth += ch.offsetWidth; - } - const isOverflow = childWidth > metaElemWidth; - if (isOverflow && isOverflow != this.isOverflow.get()) { - mobx.action(() => { - this.isOverflow.set(isOverflow); - })(); - } } @boundMethod @@ -334,78 +470,6 @@ class LineCmd extends React.Component< GlobalCommandRunner.screenSelectLine(String(line.linenum), "cmd"); } - @boundMethod - clickStar() { - const { line } = this.props; - if (!line.star || line.star == 0) { - GlobalCommandRunner.lineStar(line.lineid, 1); - } else { - GlobalCommandRunner.lineStar(line.lineid, 0); - } - } - - @boundMethod - clickPin() { - const { line } = this.props; - if (!line.pinned) { - GlobalCommandRunner.linePin(line.lineid, true); - } else { - GlobalCommandRunner.linePin(line.lineid, false); - } - } - - @boundMethod - clickBookmark() { - const { line } = this.props; - GlobalCommandRunner.lineBookmark(line.lineid); - } - - @boundMethod - clickDelete() { - const { line } = this.props; - GlobalCommandRunner.lineDelete(line.lineid, true); - } - - @boundMethod - clickRestart() { - const { line } = this.props; - GlobalCommandRunner.lineRestart(line.lineid, true); - } - - @boundMethod - clickMinimize() { - const { line } = this.props; - const isMinimized = line.linestate["wave:min"]; - GlobalCommandRunner.lineMinimize(line.lineid, !isMinimized, true); - } - - @boundMethod - clickMoveToSidebar() { - const { line } = this.props; - GlobalCommandRunner.screenSidebarAddLine(line.lineid); - } - - @boundMethod - clickRemoveFromSidebar() { - GlobalCommandRunner.screenSidebarRemove(); - } - - @boundMethod - handleResizeButton() { - console.log("resize button"); - } - - getIsHidePrompt(): boolean { - const { line } = this.props; - let rendererPlugin: RendererPluginType = null; - const isNoneRenderer = line.renderer == "none"; - if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) { - rendererPlugin = PluginModel.getRendererPluginByName(line.renderer); - } - const hidePrompt = rendererPlugin?.hidePrompt; - return hidePrompt; - } - getTerminalRendererHeight(cmd: Cmd): number { const { screen, line, width } = this.props; let height = 45 + 24; // height of zero height terminal @@ -440,7 +504,7 @@ class LineCmd extends React.Component< } else { // header is 16px tall with hide-prompt, 36px otherwise const { screen, line, width } = this.props; - const hidePrompt = this.getIsHidePrompt(); + const hidePrompt = getIsHidePrompt(line); const usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); height = (hidePrompt ? 16 + 6 : 36 + 6) + usedRows; } @@ -463,48 +527,6 @@ class LineCmd extends React.Component< ); } - @boundMethod - handleLineSettings(e: any): void { - e.preventDefault(); - e.stopPropagation(); - let { line } = this.props; - if (line != null) { - mobx.action(() => { - GlobalModel.lineSettingsModal.set(line.linenum); - })(); - GlobalModel.modalsModel.pushModal(appconst.LINE_SETTINGS); - } - } - - renderMeta1(cmd: Cmd) { - let { line } = this.props; - let formattedTime: string = ""; - let restartTs = cmd.getRestartTs(); - let timeTitle: string = null; - if (restartTs != null && restartTs > 0) { - formattedTime = "restarted @ " + lineutil.getLineDateTimeStr(restartTs); - timeTitle = "original start time " + lineutil.getLineDateTimeStr(line.ts); - } else { - formattedTime = lineutil.getLineDateTimeStr(line.ts); - } - let renderer = line.renderer; - return ( -
- -
- {formattedTime} -
-
 
- -
- - {renderer}  -
-
-
- ); - } - getRendererOpts(cmd: Cmd): RendererOpts { const { screen } = this.props; return { @@ -583,7 +605,6 @@ class LineCmd extends React.Component< render() { const { screen, line, width, staticRender, visible } = this.props; - const isMinimized = line.linestate["wave:min"]; const isVisible = visible.get(); if (staticRender || !isVisible) { return this.renderSimple(); @@ -640,7 +661,6 @@ class LineCmd extends React.Component< ) .get(); const isRunning = cmd.isRunning(); - const isExpanded = this.isCmdExpanded.get(); const cmdError = cmdHasError(cmd); const mainDivCn = cn( "line", @@ -660,6 +680,7 @@ class LineCmd extends React.Component< const hidePrompt = rendererPlugin?.hidePrompt; const termFontSize = GlobalModel.getTermFontSize(); const containerType = screen.getContainerType(); + const isMinimized = line.linestate["wave:min"] && containerType == appconst.LineContainer_Main; return (
-
-
- {this.renderMeta1(cmd)} - {this.renderCmdText(cmd)} -
-
- -
-
- -
-
- -
- -
- - - - - - -
-
- -
-
- -
-
- -
- -
-
-
+ +
  showing in sidebar => @@ -872,7 +828,7 @@ class LineText extends React.Component< name: "computed-isFocused", }) .get(); - const mainClass = cn("line", "line-text", "focus-parent"); + const mainClass = cn("line", "line-text", "focus-parent", { selected: isSelected }); return (
-
-
-
+ +
+
+
+
+
|
{formattedTime}
-
{line.text}
+
+
+ {line.text}
); diff --git a/src/app/line/lines.less b/src/app/line/lines.less index 93d66a043..f241976c6 100644 --- a/src/app/line/lines.less +++ b/src/app/line/lines.less @@ -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 { diff --git a/src/app/root.less b/src/app/root.less index cc8b08d33..9295a7c6d 100644 --- a/src/app/root.less +++ b/src/app/root.less @@ -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 diff --git a/src/app/sidebar/sidebar.tsx b/src/app/sidebar/sidebar.tsx index 38b5407b8..f094775f5 100644 --- a/src/app/sidebar/sidebar.tsx +++ b/src/app/sidebar/sidebar.tsx @@ -324,7 +324,7 @@ class MainSideBar extends React.Component { ]} />