diff --git a/src/assets/icons/add.svg b/src/assets/icons/add.svg index ece3c601..d7f5c75c 100644 --- a/src/assets/icons/add.svg +++ b/src/assets/icons/add.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/icons/apps.svg b/src/assets/icons/apps.svg index 191f0e92..804a3999 100644 --- a/src/assets/icons/apps.svg +++ b/src/assets/icons/apps.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/src/assets/icons/chevron_left.svg b/src/assets/icons/chevron_left.svg index 4af1a556..586a9105 100644 --- a/src/assets/icons/chevron_left.svg +++ b/src/assets/icons/chevron_left.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/icons/connections.svg b/src/assets/icons/connections.svg index c66d8bd2..2f403b23 100644 --- a/src/assets/icons/connections.svg +++ b/src/assets/icons/connections.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/icons/exec.svg b/src/assets/icons/exec.svg new file mode 100644 index 00000000..5f870e02 --- /dev/null +++ b/src/assets/icons/exec.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/favourites.svg b/src/assets/icons/favourites.svg index b9bb9804..5c53a34c 100644 --- a/src/assets/icons/favourites.svg +++ b/src/assets/icons/favourites.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/icons/folder.svg b/src/assets/icons/folder.svg new file mode 100644 index 00000000..17df160b --- /dev/null +++ b/src/assets/icons/folder.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/help.svg b/src/assets/icons/help.svg index 86ede135..a850a124 100644 --- a/src/assets/icons/help.svg +++ b/src/assets/icons/help.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/icons/history.svg b/src/assets/icons/history.svg index d9c03bdc..cbde5888 100644 --- a/src/assets/icons/history.svg +++ b/src/assets/icons/history.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/icons/minus.svg b/src/assets/icons/minus.svg new file mode 100644 index 00000000..61671532 --- /dev/null +++ b/src/assets/icons/minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/pin.svg b/src/assets/icons/pin.svg new file mode 100644 index 00000000..b6368b34 --- /dev/null +++ b/src/assets/icons/pin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/plus.svg b/src/assets/icons/plus.svg new file mode 100644 index 00000000..5f948b03 --- /dev/null +++ b/src/assets/icons/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/settings.svg b/src/assets/icons/settings.svg index 513213f3..1da691e4 100644 --- a/src/assets/icons/settings.svg +++ b/src/assets/icons/settings.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/icons/tab/actions.svg b/src/assets/icons/tab/actions.svg new file mode 100644 index 00000000..4100a770 --- /dev/null +++ b/src/assets/icons/tab/actions.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/icons/tab/sparkle.svg b/src/assets/icons/tab/sparkle.svg new file mode 100644 index 00000000..bfc68308 --- /dev/null +++ b/src/assets/icons/tab/sparkle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/three_dots.svg b/src/assets/icons/three_dots.svg index 3c7c2119..b3327e09 100644 --- a/src/assets/icons/three_dots.svg +++ b/src/assets/icons/three_dots.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/icons/tick.svg b/src/assets/icons/tick.svg new file mode 100644 index 00000000..b02f9c13 --- /dev/null +++ b/src/assets/icons/tick.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/workspaces.svg b/src/assets/icons/workspaces.svg index d685ccbe..87dfb28c 100644 --- a/src/assets/icons/workspaces.svg +++ b/src/assets/icons/workspaces.svg @@ -1,4 +1,4 @@ - + diff --git a/src/common/common.less b/src/common/common.less index 3cb37327..b079a6dd 100644 --- a/src/common/common.less +++ b/src/common/common.less @@ -128,65 +128,6 @@ visibility: visible !important; } } -.terminal-wrapper { - position: relative; - - .term-block { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: transparent; - z-index: 10; - } - - .xterm-screen { - &::-webkit-scrollbar { - display: none; - } - } - - &.focus .xterm { - .xterm-screen { - overflow-y: scroll; - overscroll-behavior: contain; - } - - .xterm-viewport { - overscroll-behavior: contain; - } - } - - &.focus .xterm-viewport { - &::-webkit-scrollbar { - background-color: #777; - width: 5px; - height: 5px; - } - - &::-webkit-scrollbar-thumb { - background: white; - } - } - - .xterm-viewport { - &::-webkit-scrollbar { - background-color: #222; - width: 5px; - height: 5px; - } - - &::-webkit-scrollbar-thumb { - background: #555; - } - } -} - -body .xterm .xterm-viewport { - overflow-y: auto; - width: calc(100% + 5px); -} .checkbox-toggle { position: relative; @@ -565,70 +506,6 @@ body .xterm .xterm-viewport { background-color: #222; } -#main .term-prompt { - i { - margin-right: 3px; - } - - .term-prompt-branch { - color: @term-white; - } - - .term-prompt-python { - color: @term-bright-magenta; - } - - .term-prompt-remote { - i { - margin-right: 0; - } - } - - .term-prompt-remote { - color: @term-bright-green; - - &.color-green { - color: @term-bright-green; - } - - &.color-red { - color: @term-bright-red; - } - - &.color-blue { - color: @term-bright-blue; - } - - &.color-yellow { - color: @term-bright-yellow; - } - - &.color-magenta { - color: @term-bright-magenta; - } - - &.color-cyan { - color: @term-bright-cyan; - } - - &.color-white { - color: @term-bright-white; - } - - &.color-orange { - color: @tab-orange; - } - } - - .term-prompt-cwd { - color: @term-bright-green; - } - - .term-prompt-end { - color: @term-bright-green; - } -} - .remote-status { margin-right: 5px; position: relative; diff --git a/src/common/common.tsx b/src/common/common.tsx index c0e249a6..2ba14b62 100644 --- a/src/common/common.tsx +++ b/src/common/common.tsx @@ -257,23 +257,6 @@ class InfoMessage extends React.Component<{ width: number; children: React.React } } -function LinkRenderer(props: any): any { - let newUrl = "https://extern?" + encodeURIComponent(props.href); - return ( - - {props.children} - - ); -} - -function HeaderRenderer(props: any, hnum: number): any { - return
{props.children}
; -} - -function CodeRenderer(props: any): any { - return {props.children}; -} - @mobxReact.observer class Markdown extends React.Component<{ text: string; style?: any; extraClassName?: string }, {}> { render() { diff --git a/src/common/terminal/Terminal.tsx b/src/common/terminal/Terminal.tsx index 2031f372..fc23474c 100644 --- a/src/common/terminal/Terminal.tsx +++ b/src/common/terminal/Terminal.tsx @@ -135,7 +135,6 @@ class TerminalRenderer extends React.Component< render() { let { screen, line, width, staticRender, visible, collapsed } = this.props; - let isVisible = visible.get(); // for reaction let isPhysicalFocused = mobx .computed(() => screen.getIsFocused(line.linenum), { name: "computed-getIsFocused", @@ -152,7 +151,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()); + let termHeight = termHeightFromRows(usedRows <= 1 ? usedRows : usedRows + 3, GlobalModel.termFontSize.get()); let termLoaded = this.termLoaded.get(); return (
; onHeightChange: LineHeightChangeCallbackType; - topBorder: boolean; renderMode: RenderModeType; overrideCollapsed: OV; noSelect?: boolean; @@ -460,12 +466,7 @@ class LineCmd extends React.Component< } getTerminalRendererHeight(cmd: Cmd): number { - let { screen, line, width, topBorder, renderMode } = this.props; - // header is 36px tall, padding+border = 6px - // zero-terminal is 0px - // terminal-wrapper overhead is 11px (margin/padding) - // inner-height, if zero-lines => 42 - // else: 53+(lines*lineheight) + let { screen, line, width, renderMode } = this.props; let height = 36 + 6; // height of zero height terminal let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); if (usedRows > 0) { @@ -490,7 +491,7 @@ class LineCmd extends React.Component< } renderSimple() { - let { screen, line, topBorder } = this.props; + let { screen, line } = this.props; let cmd = screen.getCmd(line); let height: number = 0; if (isBlank(line.renderer) || line.renderer == "terminal") { @@ -503,9 +504,7 @@ class LineCmd extends React.Component< height = (hidePrompt ? 16 + 6 : 36 + 6) + usedRows; } let formattedTime = lineutil.getLineDateTimeStr(line.ts); - let mainDivCn = cn("line", "line-cmd", "line-simple", { - "top-border": topBorder, - }); + let mainDivCn = cn("line", "line-cmd", "line-simple"); return (
-
- +
- +
- + {this.isMinimised.get() ? : }
@@ -798,16 +791,13 @@ class LineCmd extends React.Component< className="cmd-rtnstate" style={{ visibility: cmd.getStatus() == "done" ? "visible" : "hidden", - fontSize: GlobalModel.termFontSize.get(), }} >
state unchanged
-
new state
-
{this.rtnStateDiff.get()}
@@ -833,7 +823,6 @@ class Line extends React.Component< visible: OV; onHeightChange: LineHeightChangeCallbackType; overrideCollapsed: OV; - topBorder: boolean; renderMode: RenderModeType; noSelect?: boolean; }, @@ -854,85 +843,12 @@ class Line extends React.Component< } } -@mobxReact.observer -class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record }, {}> { - render() { - let rptr = this.props.rptr; - if (rptr == null || isBlank(rptr.remoteid)) { - return  ; - } - let remote = GlobalModel.getRemote(this.props.rptr.remoteid); - let remoteStr = getRemoteStr(rptr); - let festate = this.props.festate ?? {}; - let cwd = getCwdStr(remote, festate); - let isRoot = false; - if (remote && remote.remotevars) { - if (remote.remotevars["sudo"] || remote.remotevars["bestuser"] == "root") { - isRoot = true; - } - } - let remoteColorClass = isRoot ? "color-red" : "color-green"; - if (remote && remote.remoteopts && remote.remoteopts.color) { - remoteColorClass = "color-" + remote.remoteopts.color; - } - // TESTING cwd shortening with triple colon character - // if (cwd.startsWith("~/work/gopath/src/github.com/scripthaus-dev")) { - // cwd = cwd.replace("~/work/gopath/src/github.com/scripthaus-dev", "\u22EEscripthaus-dev"); - // } - let remoteTitle: string = null; - if (remote && remote.remotecanonicalname) { - remoteTitle = "connected to " + remote.remotecanonicalname; - } - let cwdElem = ( - - - {cwd} - - ); - let remoteElem = ( - - [{remoteStr}]{" "} - - ); - let rootIndicatorElem = {isRoot ? "#" : "$"}; - let branchElem = null; - let pythonElem = null; - if (!isBlank(festate["PROMPTVAR_GITBRANCH"])) { - let branchName = festate["PROMPTVAR_GITBRANCH"]; - branchElem = ( - - - {branchName}{" "} - - ); - } - if (!isBlank(festate["VIRTUAL_ENV"])) { - let venvDir = festate["VIRTUAL_ENV"]; - let venv = getShortVEnv(venvDir); - pythonElem = ( - - - {venv}{" "} - - ); - } - return ( - - {remoteElem} {pythonElem} - {branchElem} - {cwdElem} {rootIndicatorElem} - - ); - } -} - @mobxReact.observer class LineText extends React.Component< { screen: LineContainerModel; line: LineType; renderMode: RenderModeType; - topBorder: boolean; noSelect?: boolean; }, {} @@ -962,7 +878,7 @@ class LineText extends React.Component< } render() { - let { screen, line, topBorder, renderMode } = this.props; + let { screen, line, renderMode } = this.props; let formattedTime = lineutil.getLineDateTimeStr(line.ts); let isSelected = mobx .computed(() => screen.getSelectedLine() == line.linenum, { @@ -974,9 +890,7 @@ class LineText extends React.Component< name: "computed-isFocused", }) .get(); - let mainClass = cn("line", "line-text", "focus-parent", { - "top-border": topBorder, - }); + let mainClass = cn("line", "line-text", "focus-parent"); return (
{ let inputMode: string = inputModel.inputMode.get(); let textAreaInputKey = screen == null ? "null" : screen.screenId; return ( -
-
+
@@ -160,28 +153,23 @@ class CmdInput extends React.Component<{}, {}> {
-
-
- - - -
-
-
-
+
+ {/**
{inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")}) -
- -
+
**/} + {!focusVal && ( +
focus input ({renderCmdText("I")})
-
- -
- {" "} + )} + {focusVal && ( +
{historyShow ? "close history (esc)" : "show history (ctrl-r)"}
- + )} +
diff --git a/src/main/sessionview/TextareaInput.tsx b/src/main/sessionview/CmdWindow/TextareaInput.tsx similarity index 96% rename from src/main/sessionview/TextareaInput.tsx rename to src/main/sessionview/CmdWindow/TextareaInput.tsx index 9fd36df6..95b25d56 100644 --- a/src/main/sessionview/TextareaInput.tsx +++ b/src/main/sessionview/CmdWindow/TextareaInput.tsx @@ -3,10 +3,10 @@ import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; import { boundMethod } from "autobind-decorator"; import cn from "classnames"; -import { GlobalModel, GlobalCommandRunner } from "../../model"; -import { getMonoFontSize } from "../../util/textmeasure"; -import { isModKeyPress, hasNoModifiers } from "../../util/util"; -import "./sessionview.less"; +import { GlobalModel, GlobalCommandRunner } from "../../../model"; +import { getMonoFontSize } from "../../../util/textmeasure"; +import { isModKeyPress, hasNoModifiers } from "../../../util/util"; +import "../sessionview.less"; function pageSize(div: any): number { if (div == null) { @@ -534,9 +534,10 @@ class TextAreaInput extends React.Component<{ onHeightChange: () => void }, {}> if (activeScreen != null) { activeScreen.focusType.get(); // for reaction } - let computedHeight = displayLines * 24 + 14 + 2; // 24 = height of line, 14 = padding, 2 = border + let computedInnerHeight = (displayLines + 1) * GlobalModel.termFontSize.get(); + let computedOuterHeight = (displayLines + 2) * GlobalModel.termFontSize.get(); return ( -
+
= mobx.IObservableValue; + +@mobxReact.observer +class ScreenView extends React.Component<{ screen: Screen }, {}> { + render() { + let { screen } = this.props; + if (screen == null) { + return
(no screen found)
; + } + let fontSize = GlobalModel.termFontSize.get(); + return ( +
+ +
+ ); + } +} + +// screen is not null +@mobxReact.observer +class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { + rszObs: any; + windowViewRef: React.RefObject; + + width: mobx.IObservableValue = mobx.observable.box(0, { name: "sw-view-width" }); + height: mobx.IObservableValue = mobx.observable.box(0, { name: "sw-view-height" }); + setSize_debounced: (width: number, height: number) => void; + + renderMode: OV = mobx.observable.box("normal", { name: "renderMode" }); + shareCopied: OV = mobx.observable.box(false, { name: "sw-shareCopied" }); + + constructor(props: any) { + super(props); + this.setSize_debounced = debounce(1000, this.setSize.bind(this)); + this.windowViewRef = React.createRef(); + } + + setSize(width: number, height: number): void { + let { screen } = this.props; + if (screen == null) { + return; + } + if (width == null || height == null || width == 0 || height == 0) { + return; + } + mobx.action(() => { + this.width.set(width); + this.height.set(height); + screen.screenSizeCallback({ height: height, width: width }); + })(); + } + + componentDidMount() { + let wvElem = this.windowViewRef.current; + if (wvElem != null) { + let width = wvElem.offsetWidth; + let height = wvElem.offsetHeight; + this.setSize(width, height); + this.rszObs = new ResizeObserver(this.handleResize.bind(this)); + this.rszObs.observe(wvElem); + } + } + + componentWillUnmount() { + if (this.rszObs) { + this.rszObs.disconnect(); + } + } + + handleResize(entries: any) { + if (entries.length == 0) { + return; + } + let entry = entries[0]; + let width = entry.target.offsetWidth; + let height = entry.target.offsetHeight; + mobx.action(() => { + this.setSize_debounced(width, height); + })(); + } + + getScreenLines(): ScreenLines { + let { screen } = this.props; + let win = GlobalModel.getScreenLinesById(screen.screenId); + if (win == null) { + win = GlobalModel.loadScreenLines(screen.screenId); + } + return win; + } + + @boundMethod + toggleRenderMode() { + let renderMode = this.renderMode.get(); + mobx.action(() => { + this.renderMode.set(renderMode == "normal" ? "collapsed" : "normal"); + })(); + } + + renderError(message: string, fade: boolean) { + let { screen } = this.props; + return ( +
+
+
+
{message}
+
+
+ ); + } + + @boundMethod + copyShareLink(): void { + let { screen } = this.props; + let shareLink = screen.getWebShareUrl(); + if (shareLink == null) { + return; + } + navigator.clipboard.writeText(shareLink); + mobx.action(() => { + this.shareCopied.set(true); + })(); + setTimeout(() => { + mobx.action(() => { + this.shareCopied.set(false); + })(); + }, 600); + } + + @boundMethod + openScreenSettings(): void { + let { screen } = this.props; + mobx.action(() => { + GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId }); + })(); + } + + @boundMethod + buildLineComponent(lineProps: LineFactoryProps): JSX.Element { + let { screen } = this.props; + let { line, ...restProps } = lineProps; + let realLine: LineType = line as LineType; + return ; + } + + render() { + let { screen } = this.props; + let win = this.getScreenLines(); + if (win == null || !win.loaded.get()) { + return this.renderError("...", true); + } + if (win.loadError.get() != null) { + return this.renderError(sprintf("(%s)", win.loadError.get()), false); + } + if (this.width.get() == 0) { + return this.renderError("", false); + } + let cdata = GlobalModel.clientData.get(); + if (cdata == null) { + return this.renderError("loading client data", true); + } + let idx = 0; + let line: LineType = null; + let session = GlobalModel.getSessionById(screen.sessionId); + let isActive = screen.isActive(); + let selectedLine = screen.getSelectedLine(); + let lines = win.getNonArchivedLines(); + let renderMode = this.renderMode.get(); + return ( +
+
+
+ + + + + + +
+
+ +
+ +
+ +
+ web shared +
+
+
+ copy link + + + +
+
+ open settings + + + +
+
+
+
+ 0}> + + + +
+
+ + [session="{session.name.get()}" screen="{screen.name.get()}"] + +
+
+
+
+ ); + } +} + +@mobxReact.observer +class ScreenTabs extends React.Component<{ session: Session }, {}> { + tabsRef: React.RefObject = React.createRef(); + lastActiveScreenId: string = null; + scrolling: OV = mobx.observable.box(false, { name: "screentabs-scrolling" }); + + stopScrolling_debounced: () => void; + + constructor(props: any) { + super(props); + this.stopScrolling_debounced = debounce(1500, this.stopScrolling.bind(this)); + } + + @boundMethod + handleNewScreen() { + let { session } = this.props; + GlobalCommandRunner.createNewScreen(); + } + + @boundMethod + handleSwitchScreen(screenId: string) { + let { session } = this.props; + if (session == null) { + return; + } + if (session.activeScreenId.get() == screenId) { + return; + } + let screen = session.getScreenById(screenId); + if (screen == null) { + return; + } + GlobalCommandRunner.switchScreen(screenId); + } + + componentDidMount(): void { + this.componentDidUpdate(); + } + + componentDidUpdate(): void { + let { session } = this.props; + let activeScreenId = session.activeScreenId.get(); + if (activeScreenId != this.lastActiveScreenId && this.tabsRef.current) { + let tabElem = this.tabsRef.current.querySelector( + sprintf('.screen-tab[data-screenid="%s"]', activeScreenId) + ); + if (tabElem != null) { + tabElem.scrollIntoView(); + } + } + this.lastActiveScreenId = activeScreenId; + } + + stopScrolling(): void { + mobx.action(() => { + this.scrolling.set(false); + })(); + } + + @boundMethod + handleScroll() { + if (!this.scrolling.get()) { + mobx.action(() => { + this.scrolling.set(true); + })(); + } + this.stopScrolling_debounced(); + } + + @boundMethod + openScreenSettings(e: any, screen: Screen): void { + e.preventDefault(); + e.stopPropagation(); + mobx.action(() => { + GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId }); + })(); + } + + renderTab(screen: Screen, activeScreenId: string, index: number): any { + let tabIndex = null; + if (index + 1 <= 9) { + tabIndex =
{renderCmdText(String(index + 1))}
; + } + let settings = ( +
this.openScreenSettings(e, screen)} title="Actions" className="tab-gear"> + +
+ ); + let archived = screen.archived.get() ? ( + + ) : null; + + let webShared = screen.isWebShared() ? ( + + ) : null; + return ( +
this.handleSwitchScreen(screen.screenId)} + onContextMenu={(event) => this.openScreenSettings(event, screen)} + > + +
+ {archived} + {webShared} + {screen.name.get()} +
+ {tabIndex} + {settings} +
+ ); + } + + render() { + let { session } = this.props; + if (session == null) { + return null; + } + let screen: Screen = null; + let index = 0; + let showingScreens = []; + let activeScreenId = session.activeScreenId.get(); + let screens = GlobalModel.getSessionScreens(session.sessionId); + for (let screen of screens) { + if (!screen.archived.get() || activeScreenId == screen.screenId) { + showingScreens.push(screen); + } + } + showingScreens.sort((a, b) => { + let aidx = a.screenIdx.get(); + let bidx = b.screenIdx.get(); + if (aidx < bidx) { + return -1; + } + if (aidx > bidx) { + return 1; + } + return 0; + }); + return ( +
+
+ + {this.renderTab(screen, activeScreenId, index)} + +
+ +
+
+ {/**
+
move left {renderCmdText("[")}
+
move right {renderCmdText("]")}
+
new tab {renderCmdText("T")}
+
*/} +
+ ); + } +} + +export { ScreenView, ScreenTabs }; diff --git a/src/main/sessionview/tabs.less b/src/main/sessionview/Screen/tabs.less similarity index 71% rename from src/main/sessionview/tabs.less rename to src/main/sessionview/Screen/tabs.less index 2f2246c2..b0ce7f5b 100644 --- a/src/main/sessionview/tabs.less +++ b/src/main/sessionview/Screen/tabs.less @@ -1,6 +1,21 @@ -@import "../../index.less"; +@import "../../../index.less"; #main .screen-tabs .screen-tab { + &.color-default { + color: @tab-white-text; + &.is-active { + color: @tab-white-text; + border-radius: 12px 0px 0px 0px; + border-top: 1px solid #58c142; + background: linear-gradient( + 180deg, + rgba(88, 193, 66, 0.2) 9.34%, + rgba(88, 193, 66, 0.03) 44.16%, + rgba(88, 193, 66, 0) 86.79% + ); + } + } + &.color-green { color: @tab-white-text; background-color: desaturate(@tab-green, 50%); @@ -46,7 +61,6 @@ &.is-active { background-color: @tab-yellow; color: @term-black; - box-shadow: 0 3px 0 #fff inset, 0 4px 0 #000 inset; } } @@ -95,7 +109,6 @@ &.is-active { color: @term-black; background-color: @tab-white; - box-shadow: 0 3px 0 #fff inset, 0 4px 0 #000 inset; } } @@ -118,31 +131,9 @@ } } -.screen-tabs-container { - position: relative; - - &:hover { - z-index: 200; - } - - &:hover .cmd-hints { - display: flex; - } - - .cmd-hints { - position: absolute; - bottom: -18px; - left: 0px; - display: flex; - } -} - .screen-tabs { - height: 30px; display: flex; flex-direction: row; - border-top: 1px solid #666; - border-right: 1px solid #eee; overflow-x: overlay; overflow-y: visible; align-items: center; @@ -164,31 +155,24 @@ } .screen-tab { - height: 30px; - min-width: 80px; - width: 150px; - flex-shrink: 1; display: flex; - justify-content: center; + height: 3em; + min-width: 14em; + max-width: 14em; align-items: center; - - font-weight: 500; - padding-left: 0; - padding-right: 0; cursor: pointer; position: relative; - border-right: 1px solid #777; + + .icon { + width: 1.1em; + margin: 0 1em; + } .tab-name { - flex-grow: 1; - flex-basis: 50px; - padding-left: 2px; - padding-right: 2px; - text-align: center; + width: 8rem; } &.is-active { - box-shadow: 0 3px 0 #fff inset; border-top: none; opacity: 1; } @@ -199,49 +183,24 @@ } } - .tab-index { - position: absolute; - right: 2px; - top: 4px; - bottom: 0; - font-weight: normal; - display: none; - padding-left: 4px; - padding-top: 6px; - background-color: inherit; - } - + .tab-index, .tab-gear { - position: absolute; - right: 5px; - top: 4px; - bottom: 0; - font-weight: normal; display: none; - padding-left: 5px; - padding-top: 5px; - background-color: inherit; + .icon { + border-radius: 50%; + } } &:hover { .tab-gear { display: block; - - &:hover { - display: block; - top: 4px; - padding-top: 4px; - right: 3px; - padding-left: 4px; - i { - } - } } } } &:hover .screen-tab .tab-index { display: block; + font-size: 0.8em; } &:hover .screen-tab:hover .tab-index { @@ -249,16 +208,33 @@ } .screen-tab.new-screen { - width: 30px; - min-width: 30px; - background-color: @term-black; - border-right: none; - color: @term-white; + width: 3em; + min-width: 3em; cursor: pointer; - &:hover { - background-color: #666; - border-right: 1px solid #ccc; + .icon { + width: 1.1em; + height: 1.1em; + border-radius: 50%; } } } + +.screen-tabs-container { + position: relative; + + &:hover { + z-index: 200; + } + + &:hover .cmd-hints { + display: flex; + } + + .cmd-hints { + position: absolute; + bottom: -18px; + left: 0px; + display: flex; + } +} diff --git a/src/main/sessionview/SessionView.tsx b/src/main/sessionview/SessionView.tsx index 3aab954c..af8346be 100644 --- a/src/main/sessionview/SessionView.tsx +++ b/src/main/sessionview/SessionView.tsx @@ -1,21 +1,13 @@ import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; -import { sprintf } from "sprintf-js"; -import { boundMethod } from "autobind-decorator"; -import { If, For } from "tsx-control-statements/components"; import cn from "classnames"; -import { debounce } from "throttle-debounce"; import dayjs from "dayjs"; -import type { LineType, RenderModeType, LineFactoryProps } from "../../types/types"; import localizedFormat from "dayjs/plugin/localizedFormat"; -import { GlobalModel, GlobalCommandRunner, Session, ScreenLines, Screen } from "../../model"; -import { Line } from "../line/linecomps"; -import { renderCmdText } from "../../common/common"; -import { LinesView } from "../line/linesview"; -import { CmdInput } from "./CmdInput"; +import { GlobalModel } from "../../model"; +import { CmdInput } from "./CmdWindow/CmdInput"; +import { ScreenView, ScreenTabs } from "./Screen/ScreenView"; import "./sessionview.less"; -import "./tabs.less"; dayjs.extend(localizedFormat); @@ -37,8 +29,8 @@ class SessionView extends React.Component<{}, {}> { let isHidden = GlobalModel.activeMainView.get() != "session"; return (
- +
@@ -46,412 +38,4 @@ class SessionView extends React.Component<{}, {}> { } } -@mobxReact.observer -class ScreenView extends React.Component<{ screen: Screen }, {}> { - render() { - let { screen } = this.props; - if (screen == null) { - return
(no screen found)
; - } - let fontSize = GlobalModel.termFontSize.get(); - return ( -
- -
- ); - } -} - -// screen is not null -@mobxReact.observer -class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { - rszObs: any; - windowViewRef: React.RefObject; - - width: mobx.IObservableValue = mobx.observable.box(0, { name: "sw-view-width" }); - height: mobx.IObservableValue = mobx.observable.box(0, { name: "sw-view-height" }); - setSize_debounced: (width: number, height: number) => void; - - renderMode: OV = mobx.observable.box("normal", { name: "renderMode" }); - shareCopied: OV = mobx.observable.box(false, { name: "sw-shareCopied" }); - - constructor(props: any) { - super(props); - this.setSize_debounced = debounce(1000, this.setSize.bind(this)); - this.windowViewRef = React.createRef(); - } - - setSize(width: number, height: number): void { - let { screen } = this.props; - if (screen == null) { - return; - } - if (width == null || height == null || width == 0 || height == 0) { - return; - } - mobx.action(() => { - this.width.set(width); - this.height.set(height); - screen.screenSizeCallback({ height: height, width: width }); - })(); - } - - componentDidMount() { - let wvElem = this.windowViewRef.current; - if (wvElem != null) { - let width = wvElem.offsetWidth; - let height = wvElem.offsetHeight; - this.setSize(width, height); - this.rszObs = new ResizeObserver(this.handleResize.bind(this)); - this.rszObs.observe(wvElem); - } - } - - componentWillUnmount() { - if (this.rszObs) { - this.rszObs.disconnect(); - } - } - - handleResize(entries: any) { - if (entries.length == 0) { - return; - } - let entry = entries[0]; - let width = entry.target.offsetWidth; - let height = entry.target.offsetHeight; - mobx.action(() => { - this.setSize_debounced(width, height); - })(); - } - - getScreenLines(): ScreenLines { - let { screen } = this.props; - let win = GlobalModel.getScreenLinesById(screen.screenId); - if (win == null) { - win = GlobalModel.loadScreenLines(screen.screenId); - } - return win; - } - - getWindowViewStyle(): any { - return { position: "absolute", width: "100%", height: "100%", overflowX: "hidden" }; - } - - @boundMethod - toggleRenderMode() { - let renderMode = this.renderMode.get(); - mobx.action(() => { - this.renderMode.set(renderMode == "normal" ? "collapsed" : "normal"); - })(); - } - - renderError(message: string, fade: boolean) { - let { screen } = this.props; - return ( -
-
-
-
{message}
-
-
- ); - } - - @boundMethod - copyShareLink(): void { - let { screen } = this.props; - let shareLink = screen.getWebShareUrl(); - if (shareLink == null) { - return; - } - navigator.clipboard.writeText(shareLink); - mobx.action(() => { - this.shareCopied.set(true); - })(); - setTimeout(() => { - mobx.action(() => { - this.shareCopied.set(false); - })(); - }, 600); - } - - @boundMethod - openScreenSettings(): void { - let { screen } = this.props; - mobx.action(() => { - GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId }); - })(); - } - - @boundMethod - buildLineComponent(lineProps: LineFactoryProps): JSX.Element { - let { screen } = this.props; - let { line, ...restProps } = lineProps; - let realLine: LineType = line as LineType; - return ; - } - - render() { - let { screen } = this.props; - let win = this.getScreenLines(); - if (win == null || !win.loaded.get()) { - return this.renderError("...", true); - } - if (win.loadError.get() != null) { - return this.renderError(sprintf("(%s)", win.loadError.get()), false); - } - if (this.width.get() == 0) { - return this.renderError("", false); - } - let cdata = GlobalModel.clientData.get(); - if (cdata == null) { - return this.renderError("loading client data", true); - } - let idx = 0; - let line: LineType = null; - let session = GlobalModel.getSessionById(screen.sessionId); - let isActive = screen.isActive(); - let selectedLine = screen.getSelectedLine(); - let lines = win.getNonArchivedLines(); - let renderMode = this.renderMode.get(); - return ( -
-
-
- - - - - - -
-
- -
- -
- -
- web shared -
-
-
- copy link - - - -
-
- open settings - - - -
-
-
-
- 0}> - - - -
-
- - [session="{session.name.get()}" screen="{screen.name.get()}"] - -
-
-
-
- ); - } -} - -@mobxReact.observer -class ScreenTabs extends React.Component<{ session: Session }, {}> { - tabsRef: React.RefObject = React.createRef(); - lastActiveScreenId: string = null; - scrolling: OV = mobx.observable.box(false, { name: "screentabs-scrolling" }); - - stopScrolling_debounced: () => void; - - constructor(props: any) { - super(props); - this.stopScrolling_debounced = debounce(1500, this.stopScrolling.bind(this)); - } - - @boundMethod - handleNewScreen() { - let { session } = this.props; - GlobalCommandRunner.createNewScreen(); - } - - @boundMethod - handleSwitchScreen(screenId: string) { - let { session } = this.props; - if (session == null) { - return; - } - if (session.activeScreenId.get() == screenId) { - return; - } - let screen = session.getScreenById(screenId); - if (screen == null) { - return; - } - GlobalCommandRunner.switchScreen(screenId); - } - - componentDidMount(): void { - this.componentDidUpdate(); - } - - componentDidUpdate(): void { - let { session } = this.props; - let activeScreenId = session.activeScreenId.get(); - if (activeScreenId != this.lastActiveScreenId && this.tabsRef.current) { - let tabElem = this.tabsRef.current.querySelector( - sprintf('.screen-tab[data-screenid="%s"]', activeScreenId) - ); - if (tabElem != null) { - tabElem.scrollIntoView(); - } - } - this.lastActiveScreenId = activeScreenId; - } - - stopScrolling(): void { - mobx.action(() => { - this.scrolling.set(false); - })(); - } - - @boundMethod - handleScroll() { - if (!this.scrolling.get()) { - mobx.action(() => { - this.scrolling.set(true); - })(); - } - this.stopScrolling_debounced(); - } - - @boundMethod - openScreenSettings(e: any, screen: Screen): void { - e.preventDefault(); - e.stopPropagation(); - mobx.action(() => { - GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId }); - })(); - } - - renderTab(screen: Screen, activeScreenId: string, index: number): any { - let tabIndex = null; - if (index + 1 <= 9) { - tabIndex =
{renderCmdText(String(index + 1))}
; - } - let settings = ( -
this.openScreenSettings(e, screen)} title="Settings" className="tab-gear"> - -
- ); - let archived = screen.archived.get() ? ( - - ) : null; - - let webShared = screen.isWebShared() ? ( - - ) : null; - return ( -
this.handleSwitchScreen(screen.screenId)} - onContextMenu={(event) => this.openScreenSettings(event, screen)} - > -
- {archived} - {webShared} - {screen.name.get()} -
- {tabIndex} - {settings} -
- ); - } - - render() { - let { session } = this.props; - if (session == null) { - return null; - } - let screen: Screen = null; - let index = 0; - let showingScreens = []; - let activeScreenId = session.activeScreenId.get(); - let screens = GlobalModel.getSessionScreens(session.sessionId); - for (let screen of screens) { - if (!screen.archived.get() || activeScreenId == screen.screenId) { - showingScreens.push(screen); - } - } - showingScreens.sort((a, b) => { - let aidx = a.screenIdx.get(); - let bidx = b.screenIdx.get(); - if (aidx < bidx) { - return -1; - } - if (aidx > bidx) { - return 1; - } - return 0; - }); - return ( -
-
- - {this.renderTab(screen, activeScreenId, index)} - -
- -
-
-
-
move left {renderCmdText("[")}
-
move right {renderCmdText("]")}
-
new tab {renderCmdText("T")}
-
-
- ); - } -} - export { SessionView }; diff --git a/src/main/sessionview/sessionview.less b/src/main/sessionview/sessionview.less index f6a7c2e8..8d0e48bd 100644 --- a/src/main/sessionview/sessionview.less +++ b/src/main/sessionview/sessionview.less @@ -1,371 +1,15 @@ @import "../../index.less"; -.cmd-input-info, -.cmd-history { - &::-webkit-scrollbar { - background-color: #777; - width: 5px; - height: 5px; - } - - &::-webkit-scrollbar-thumb { - background: white; - } -} - -.cmd-input { - border-radius: 0; - border-top: 4px solid #ccc; - border-right: 1px solid #ccc; - border-bottom: 1px solid #ccc; - border-bottom-right-radius: 10px; - max-height: max(300px, 40%); +.session-view { + flex-grow: 1; display: flex; flex-direction: column; - position: absolute; - bottom: 0; - width: 100%; - padding: 20px 25px 20px 20px; - z-index: 100; - - &.has-info { - padding-top: 10px; - } - - .focus-indicator { - height: 90%; - top: 5%; - left: 4px; - } - - &.has-history { - padding-top: 5px; - height: max(300px, 40%); - } - - &.has-remote { - max-height: max(300px, 70%); - } - - .remote-status-warning { - display: flex; - flex-direction: row; - color: @term-yellow; - align-items: center; - - .button { - margin-left: 10px; - } - - .remote-name { - } - } - - .input-minmax-control { - position: absolute; - top: 5px; - right: 5px; - color: @term-white; - - padding: 5px; - cursor: pointer; - } - - .cmd-input-grow-spacer { - flex-grow: 1; - } - - .cmd-input-context { - color: #fff; - white-space: nowrap; - } - - .cmd-input-field { - position: relative; - - .cmd-hints { - position: absolute; - bottom: -14px; - right: 0px; - } - - textarea { - color: @term-white; - background-color: @term-black; - padding-bottom: calc(0.5em - 1px); - padding-top: calc(0.5em - 1px); - resize: none; - overflow-wrap: anywhere; - - &:active, - &:focus { - border-color: @term-white !important; - } - - &.display-disabled { - background-color: #444; - } - } - - input.history-input { - border: 0; - padding: 0; - height: 0; - } - - .cmd-quick-context .button { - background-color: #000 !important; - color: @term-white; - } - - &.inputmode-global .cmd-quick-context .button { - color: @term-black; - background-color: @tab-green !important; - } - - &.inputmode-comment .cmd-quick-context .button { - color: @term-black; - background-color: @tab-blue !important; - } - - .cmd-exec .button { - background-color: #000 !important; - color: #d3d7cf; - } - } - - .cmd-history { - color: @term-white; - margin-bottom: 5px; - overflow: auto; - flex-shrink: 1; - - .history-title { - position: absolute; - z-index: 102; - top: 5px; - left: 20px; - background-color: @term-black; - color: @soft-blue; - padding-bottom: 4px; - display: flex; - flex-direction: row; - width: calc(100% - 40px); - overflow-x: auto; - - .history-opt { - white-space: nowrap; - } - - .history-clickable-opt { - white-space: nowrap; - cursor: pointer; - } - - .grow-spacer { - flex: 1 0 10px; - } - - .spacer { - flex: 0 0 10px; - } - } - - .history-items { - margin-top: 24px; - color: @term-white; - - padding-bottom: 6px; - - .history-line { - white-space: pre; - } - - .history-item.history-haderror { - color: mix(@term-red, @term-white, 50%); - } - - .history-line:first-child { - margin-left: 0 !important; - } - - .history-item { - padding-left: 5px; - cursor: pointer; - - &:hover { - background-color: #222; - } - } - - .history-item.is-selected { - font-weight: bold; - color: @term-bright-white; - background-color: #444; - } - - .history-item.is-selected.history-haderror { - color: mix(@term-bright-red, @term-bright-white, 50%); - } - } - } - - .cmd-input-info { - flex-shrink: 1; - overflow-y: auto; - margin-bottom: 5px; - - .info-msg { - color: @soft-blue; - padding-bottom: 2px; - - a { - color: @term-blue; - } - } - - .info-title { - color: @soft-blue; - padding-bottom: 2px; - } - - .info-lines { - color: @term-white; - white-space: pre; - padding-bottom: 6px; - } - - .info-comps { - display: flex; - flex-direction: row; - flex-wrap: wrap; - padding-bottom: 5px; - - .info-comp { - min-width: 200px; - color: @term-white; - margin-right: 10px; - - &.has-space { - text-decoration: underline dotted #777; - } - } - - .metacmd-comp { - color: @term-bright-green; - } - } - - .info-error { - color: @term-red; - padding-bottom: 2px; - } - - .info-remote-showall { - table.remotes-table { - th { - color: @term-white; - font-weight: bold; - } - - th, - td { - padding: 3px 8px 3px 8px; - } - - td { - cursor: pointer; - } - - tr:hover td { - background-color: #333; - } - } - } - - .info-remote { - color: #d3d7cf; - - .info-remote-title { - font-weight: bold; - color: @term-cyan; - } - - .info-error, - .info-msg { - margin-top: 5px; - padding: 5px; - } - - .remote-field { - display: flex; - flex-direction: row; - - .remote-field-def { - white-space: pre; - width: 120px; - } - - .remote-field-val { - white-space: pre; - display: flex; - flex-direction: row; - } - } - - .remote-input-field { - display: flex; - flex-direction: row; - height: 25px; - align-items: center; - - .remote-field-label { - white-space: pre; - width: 140px; - font-weight: bold; - color: @term-bright-white; - } - - .undo-icon { - margin-left: 4px; - cursor: pointer; - padding: 2px; - } - - .remote-field-control { - &.text-control { - } - - &.text-input { - input[type="text"], - input[type="number"], - input[type="password"] { - background-color: @term-black; - color: @term-white; - width: 200px; - } - } - - &.checkbox-input { - input[type="checkbox"] { - position: relative; - top: 3px; - } - } - - &.select-input { - select { - width: 200px; - background-color: @term-black; - color: @term-white; - } - } - } - } - } - - .info-remote-showall { - color: #d3d7cf; - } + position: relative; + &.is-hidden { + display: none; } + background: @background-session; + border: 1px solid @base-border; + border-radius: 8px; + margin: 0 16px 16px 0; } diff --git a/src/main/sidebar/MainSideBar.tsx b/src/main/sidebar/MainSideBar.tsx index 3508b687..0933173d 100644 --- a/src/main/sidebar/MainSideBar.tsx +++ b/src/main/sidebar/MainSideBar.tsx @@ -153,18 +153,17 @@ class MainSideBar extends React.Component<{}, {}> { } return sessionList.map((session, index) => { const isActive = GlobalModel.activeMainView.get() == "session" && activeSessionId == session.sessionId; + /** @TODO: Handle archived sessions and talk to Mike about session settings */ return ( - <> - {/** @TODO: Handle archived sessions and talk to Mike about session settings */} -
this.handleSessionClick(session.sessionId)} - > - ^⌘ - {index + 1} - {session.name.get()} -
- +
this.handleSessionClick(session.sessionId)} + > + ^⌘ + {index + 1} + {session.name.get()} +
); }); } @@ -196,7 +195,7 @@ class MainSideBar extends React.Component<{}, {}> { return (
- +
@@ -228,7 +227,7 @@ class MainSideBar extends React.Component<{}, {}> {
-
{this.getSessions()}
+
{this.getSessions()}
diff --git a/src/main/sidebar/sidebar.less b/src/main/sidebar/sidebar.less index ed698876..ddb7f23a 100644 --- a/src/main/sidebar/sidebar.less +++ b/src/main/sidebar/sidebar.less @@ -31,6 +31,11 @@ border-radius: 50%; padding: 2px; transition: transform 0.3s ease-in-out; + + svg { + width: 16px; + height: 16px; + } } .top { @@ -44,7 +49,7 @@ } .middle { - max-height: calc(100vh - 30em); + max-height: calc(100vh - 32em); overflow-y: auto; padding: 8px 6px; border-bottom: 1px solid @base-border; @@ -73,7 +78,7 @@ .bottom { position: absolute; - bottom: 6px; + bottom: 2em; left: 0; width: 100%; } @@ -88,6 +93,8 @@ .icon { margin: 0 8px 0 6px; vertical-align: middle; + width: 16px; + height: 16px; } .hotkey { float: right; diff --git a/src/model.ts b/src/model.ts index fd9ae36a..41cb6d54 100644 --- a/src/model.ts +++ b/src/model.ts @@ -12,7 +12,6 @@ import { isModKeyPress, } from "./util/util"; import { TermWrap } from "./common/terminal/term"; -import { v4 as uuidv4 } from "uuid"; import type { SessionDataType, LineType, @@ -451,7 +450,7 @@ class Screen { } getTabColor(): string { - let tabColor = "green"; + let tabColor = "default"; let screenOpts = this.opts.get(); if (screenOpts != null && !isBlank(screenOpts.tabcolor)) { tabColor = screenOpts.tabcolor; diff --git a/src/plugins/code/index.tsx b/src/plugins/code/index.tsx index c3b88699..e81f70fa 100644 --- a/src/plugins/code/index.tsx +++ b/src/plugins/code/index.tsx @@ -329,8 +329,8 @@ class SourceCodeRenderer extends React.Component< onMount={this.handleEditorDidMount} options={{ scrollBeyondLastLine: false, - fontSize: GlobalModel.termFontSize.get(), - /* fontFamily: "JetBrains Mono", @check:font */ + fontSize: GlobalModel.termFontSize.get() * 0.9, + /* fontFamily: "Martian Mono", */ readOnly: !this.getAllowEditing(), }} onChange={this.handleEditorChange} @@ -379,7 +379,10 @@ class SourceCodeRenderer extends React.Component< {allowEditing && (
-
+
this.doSave()} + className={`hint-item ${isSave ? "save-enabled" : "save-disabled"}`} + > {`save (`} {renderCmdText("S")} {`)`} diff --git a/src/plugins/plugins.less b/src/plugins/plugins.less index ebb470f6..03552bc4 100644 --- a/src/plugins/plugins.less +++ b/src/plugins/plugins.less @@ -62,6 +62,7 @@ } .renderer-container { + margin-top: 1em; .error-container { color: @term-red; diff --git a/src/terminal/prompt.tsx b/src/terminal/prompt.tsx new file mode 100644 index 00000000..c956731a --- /dev/null +++ b/src/terminal/prompt.tsx @@ -0,0 +1,156 @@ +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import dayjs from "dayjs"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import { GlobalModel, LineContainerModel } from "../model"; +import type { LineType, RemoteType, RemotePtrType, LineHeightChangeCallbackType } from "../../types/types"; +import cn from "classnames"; +import { isBlank } from "../util/util"; +import { ReactComponent as FolderIcon } from "../assets/icons/folder.svg"; + +import "./terminal.less"; + +dayjs.extend(localizedFormat); + +type OV = mobx.IObservableValue; +type OArr = mobx.IObservableArray; +type OMap = mobx.ObservableMap; + +type RendererComponentProps = { + screen: LineContainerModel; + line: LineType; + width: number; + staticRender: boolean; + visible: OV; + onHeightChange: LineHeightChangeCallbackType; + collapsed: boolean; +}; +type RendererComponentType = { + new (props: RendererComponentProps): React.Component; +}; + +function getShortVEnv(venvDir: string): string { + if (isBlank(venvDir)) { + return ""; + } + let lastSlash = venvDir.lastIndexOf("/"); + if (lastSlash == -1) { + return venvDir; + } + return venvDir.substr(lastSlash + 1); +} + +function makeFullRemoteRef(ownerName: string, remoteRef: string, name: string): string { + if (isBlank(ownerName) && isBlank(name)) { + return remoteRef; + } + if (!isBlank(ownerName) && isBlank(name)) { + return ownerName + ":" + remoteRef; + } + if (isBlank(ownerName) && !isBlank(name)) { + return remoteRef + ":" + name; + } + return ownerName + ":" + remoteRef + ":" + name; +} + +function getRemoteStr(rptr: RemotePtrType): string { + if (rptr == null || isBlank(rptr.remoteid)) { + return "(invalid remote)"; + } + let username = isBlank(rptr.ownerid) ? null : GlobalModel.resolveUserIdToName(rptr.ownerid); + let remoteRef = GlobalModel.resolveRemoteIdToRef(rptr.remoteid); + let fullRef = makeFullRemoteRef(username, remoteRef, rptr.name); + return fullRef; +} + +function replaceHomePath(path: string, homeDir: string): string { + if (path == homeDir) { + return "~"; + } + if (path.startsWith(homeDir + "/")) { + return "~" + path.substr(homeDir.length); + } + return path; +} + +function getCwdStr(remote: RemoteType, state: Record): string { + if (state == null || isBlank(state.cwd)) { + return "~"; + } + let cwd = state.cwd; + if (remote && remote.remotevars.home) { + cwd = replaceHomePath(cwd, remote.remotevars.home); + } + return cwd; +} + +@mobxReact.observer +class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record }, {}> { + render() { + let rptr = this.props.rptr; + if (rptr == null || isBlank(rptr.remoteid)) { + return  ; + } + let remote = GlobalModel.getRemote(this.props.rptr.remoteid); + let remoteStr = getRemoteStr(rptr); + let festate = this.props.festate ?? {}; + let cwd = getCwdStr(remote, festate); + let isRoot = false; + if (remote && remote.remotevars) { + if (remote.remotevars["sudo"] || remote.remotevars["bestuser"] == "root") { + isRoot = true; + } + } + let remoteColorClass = isRoot ? "color-red" : "color-green"; + if (remote && remote.remoteopts && remote.remoteopts.color) { + remoteColorClass = "color-" + remote.remoteopts.color; + } + let remoteTitle: string = null; + if (remote && remote.remotecanonicalname) { + remoteTitle = "connected to " + remote.remotecanonicalname; + } + let cwdElem = ( + + + {cwd} + + ); + let remoteElem = ( + + [{remoteStr}]{" "} + + ); + let rootIndicatorElem = {isRoot ? "#" : "$"}; + let branchElem = null; + let pythonElem = null; + if (!isBlank(festate["PROMPTVAR_GITBRANCH"])) { + let branchName = festate["PROMPTVAR_GITBRANCH"]; + branchElem = ( + + + {branchName}{" "} + + ); + } + if (!isBlank(festate["VIRTUAL_ENV"])) { + let venvDir = festate["VIRTUAL_ENV"]; + let venv = getShortVEnv(venvDir); + pythonElem = ( + + + {venv}{" "} + + ); + } + return ( + + {remoteElem} {pythonElem} + {branchElem} + {cwdElem} {rootIndicatorElem} + + ); + } +} + +export { Prompt }; diff --git a/src/terminal/terminal.less b/src/terminal/terminal.less new file mode 100644 index 00000000..793847a7 --- /dev/null +++ b/src/terminal/terminal.less @@ -0,0 +1,194 @@ +@import "../index.less"; + +.term-prompt { + .icon { + margin: 0 4px 0 2px; + vertical-align: middle; + width: 1.2em; + height: 1.2em; + fill: @prompt-green; + } + + .term-prompt-branch { + color: @term-white; + } + + .term-prompt-python { + color: @term-bright-magenta; + } + + .term-prompt-remote { + i { + margin-right: 0; + } + } + + .term-prompt-remote { + color: @term-bright-green; + + &.color-green { + color: @term-bright-green; + } + + &.color-red { + color: @term-bright-red; + } + + &.color-blue { + color: @term-bright-blue; + } + + &.color-yellow { + color: @term-bright-yellow; + } + + &.color-magenta { + color: @term-bright-magenta; + } + + &.color-cyan { + color: @term-bright-cyan; + } + + &.color-white { + color: @term-bright-white; + } + + &.color-orange { + color: @tab-orange; + } + } + + .term-prompt-cwd { + color: @term-bright-green; + } + + .term-prompt-end { + color: @term-bright-green; + } +} + +.terminal-wrapper { + position: relative; + margin-top: 1em; + + .term-block { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: transparent; + z-index: 10; + } + + .xterm-screen { + &::-webkit-scrollbar { + display: none; + } + } + + &.focus .xterm { + .xterm-screen { + overflow-y: scroll; + overscroll-behavior: contain; + } + + .xterm-viewport { + overscroll-behavior: contain; + } + } + + &.focus .xterm-viewport { + &::-webkit-scrollbar { + background-color: #777; + width: 5px; + height: 5px; + } + + &::-webkit-scrollbar-thumb { + background: white; + } + } + + .xterm-viewport { + &::-webkit-scrollbar { + background-color: #222; + width: 5px; + height: 5px; + } + + &::-webkit-scrollbar-thumb { + background: #555; + } + } +} + +body .xterm .xterm-viewport { + overflow-y: auto; + width: calc(100% + 5px); +} + +#main .term-prompt { + i { + margin-right: 3px; + } + + .term-prompt-branch { + color: @term-white; + } + + .term-prompt-python { + color: @term-bright-magenta; + } + + .term-prompt-remote { + i { + margin-right: 0; + } + } + + .term-prompt-remote { + color: @term-bright-green; + + &.color-green { + color: @term-bright-green; + } + + &.color-red { + color: @term-bright-red; + } + + &.color-blue { + color: @term-bright-blue; + } + + &.color-yellow { + color: @term-bright-yellow; + } + + &.color-magenta { + color: @term-bright-magenta; + } + + &.color-cyan { + color: @term-bright-cyan; + } + + &.color-white { + color: @term-bright-white; + } + + &.color-orange { + color: @tab-orange; + } + } + + .term-prompt-cwd { + color: @term-bright-green; + } + + .term-prompt-end { + color: @term-bright-green; + } +}