From a236348e44e108a5786d2cdcf0ab588c9abfaace Mon Sep 17 00:00:00 2001 From: sawka Date: Thu, 30 Mar 2023 22:26:01 -0700 Subject: [PATCH] webshare controls, sync selected, copy --- src/sh2.less | 50 +++++++++++++++++++++++-- src/webshare-elems.tsx | 85 +++++++++++++++++++++++++++++++++++++++--- src/webshare-model.ts | 58 ++++++++++++++++++++++++++-- 3 files changed, 181 insertions(+), 12 deletions(-) diff --git a/src/sh2.less b/src/sh2.less index ca77d6060..c976cbb67 100644 --- a/src/sh2.less +++ b/src/sh2.less @@ -3208,9 +3208,37 @@ body.prompt-webshare #main { } } + .webshare-controls { + display: flex; + flex-direction: row; + flex-shrink: 0; + height: 40px; + align-items: center; + padding-left: 20px; + padding-right: 20px; + background-color: darken(@prompt-green, 30%); + color: white; + + .screen-sharename { + font-weight: bold; + } + + .sync-control { + display: flex; + flex-direction: row; + color: @term-white; + font-size: 13px; + align-items: center; + + div:first-child { + margin-right: 5px; + } + } + } + .prompt-content { flex-grow: 1; - padding: 10px; + padding: 10px 0; display: flex; flex-direction: row; overflow: hidden; @@ -3221,10 +3249,11 @@ body.prompt-webshare #main { flex-grow: 1; display: flex; flex-direction: column; - - .web-lines { + + .lines { height: 100%; flex-grow: 1; + padding-top: 0; } } } @@ -3259,4 +3288,19 @@ body.prompt-webshare #main { margin-left: 0px; margin-right: 10px; } + + .lines .line.line-cmd { + .line-icon.copy-icon { + color: @term-white; + font-size: 18px; + + &:hover { + color: @term-bright-white; + } + } + + .copied-indicator { + z-index: 10; + } + } } diff --git a/src/webshare-elems.tsx b/src/webshare-elems.tsx index a321e4c75..04b5eb7bd 100644 --- a/src/webshare-elems.tsx +++ b/src/webshare-elems.tsx @@ -14,6 +14,7 @@ import * as util from "./util"; import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./textmeasure"; import {debounce, throttle} from "throttle-debounce"; import {LinesView} from "./linesview"; +import {Toggle} from "./elements"; type OV = mobx.IObservableValue; type OArr = mobx.IObservableArray; @@ -122,6 +123,11 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, isCmdExpanded : OV = mobx.observable.box(false, {name: "cmd-expanded"}); isOverflow : OV = mobx.observable.box(false, {name: "line-overflow"}); cmdTextRef : React.RefObject = React.createRef(); + copiedIndicator : OV = mobx.observable.box(false, {name: "copiedIndicator"}); + + componentDidMount() : void { + this.checkCmdText(); + } renderSimple() { let {line} = this.props; @@ -176,10 +182,36 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, ); } + checkCmdText() { + let metaElem = this.cmdTextRef.current; + if (metaElem == null || metaElem.childNodes.length == 0) { + return; + } + let metaElemWidth = metaElem.offsetWidth; + let metaChild = metaElem.firstChild; + let children = metaChild.childNodes; + let childWidth = 0; + for (let i=0; i metaElemWidth); + if (isOverflow != this.isOverflow.get()) { + mobx.action(() => { + this.isOverflow.set(isOverflow); + })(); + } + } + @boundMethod handleHeightChange() : void { } + @boundMethod + handleClick() : void { + WebShareModel.setSelectedLine(this.props.line.linenum); + } + renderMetaWrap() { let {line, cmd} = this.props; let formattedTime = lineutil.getLineDateTimeStr(line.ts); @@ -202,11 +234,32 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, ); } + + copyAllowed() : boolean { + return (navigator.clipboard != null); + } + + @boundMethod + clickCopy() : void { + if (this.copyAllowed()) { + let {cmd} = this.props; + navigator.clipboard.writeText(cmd.cmdstr); + } + mobx.action(() => { + this.copiedIndicator.set(true); + })(); + setTimeout(() => { + mobx.action(() => { + this.copiedIndicator.set(false); + })(); + }, 600); + } render() { let {line, cmd, topBorder} = this.props; let model = WebShareModel; let isSelected = mobx.computed(() => (model.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get(); + let isServerSelected = mobx.computed(() => (model.getServerSelectedLine() == line.linenum), {name: "computed-isServerSelected"}).get(); let rendererPlugin : T.RendererPluginType = null; let isNoneRenderer = (line.renderer == "none"); if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) { @@ -219,12 +272,23 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, if (width == 0) { width = 1024; } + let isExpanded = this.isCmdExpanded.get(); return ( -
-
-
+
+ +
+
copied
+
+
+
+
{this.renderMetaWrap()} + +
+ +
+
@@ -472,15 +536,16 @@ class WebShareMain extends React.Component<{}, {}> { render() { let screen = WebShareModel.screen.get(); let errMessage = WebShareModel.errMessage.get(); + let shareName = ""; + if (screen != null) { + shareName = isBlank(screen.screen.sharename) ? "(no name)" : screen.screen.sharename; + } return (
- -
{screen.screen.sharename}
-
+
+
{shareName}
+
+
+
Sync Selection
+ WebShareModel.setSyncSelectedLine(val)}/> +
+
diff --git a/src/webshare-model.ts b/src/webshare-model.ts index 0ab7449ff..bec8cbe83 100644 --- a/src/webshare-model.ts +++ b/src/webshare-model.ts @@ -35,6 +35,8 @@ class WebShareModelClass { contentHeightCache : Record = {}; // lineid => height wsControl : WebShareWSControl; anchor : {anchorLine : number, anchorOffset : number} = {anchorLine: 0, anchorOffset: 0}; + selectedLine : OV = mobx.observable.box(0, {name: "selectedLine"}); + syncSelectedLine : OV = mobx.observable.box(true, {name: "syncSelectedLine"}); constructor() { let urlParams = new URLSearchParams(window.location.search); @@ -42,6 +44,7 @@ class WebShareModelClass { this.screenId = urlParams.get("screenid"); setTimeout(() => this.loadFullScreenData(), 10); this.wsControl = new WebShareWSControl(getBaseWSUrl(), this.screenId, this.viewKey, this.wsMessageCallback.bind(this)); + document.addEventListener("keydown", this.docKeyDownHandler.bind(this)); } setErrMessage(msg : string) : void { @@ -50,19 +53,50 @@ class WebShareModelClass { })(); } + setSyncSelectedLine(val : boolean) : void { + mobx.action(() => { + this.syncSelectedLine.set(val); + if (val) { + let fullScreen = this.screen.get(); + if (fullScreen != null) { + this.selectedLine.set(fullScreen.screen.selectedline); + } + } + })(); + } + getSelectedLine() : number { + return this.selectedLine.get(); + } + + getServerSelectedLine() : number { let fullScreen = this.screen.get(); if (fullScreen != null) { return fullScreen.screen.selectedline; } - return 0; } setSelectedLine(lineNum : number) : void { + mobx.action(() => { + this.selectedLine.set(lineNum); + })(); + } + + updateSelectedLineIndex(delta : number) : void { let fullScreen = this.screen.get(); - if (fullScreen != null) { - return fullScreen.screen.selectedline = lineNum; + if (fullScreen == null) { + return; } + let lineIndex = this.getLineIndex(this.selectedLine.get()); + if (lineIndex == -1) { + return; + } + lineIndex += delta; + let lines = fullScreen.lines; + if (lineIndex < 0 || lineIndex >= lines.length) { + return; + } + this.setSelectedLine(lines[lineIndex].linenum); } setAnchorFields(anchorLine : number, anchorOffset : number, reason : string) : void { @@ -152,6 +186,9 @@ class WebShareModelClass { let fullScreen = this.screen.get(); if (msg.screen) { fullScreen.screen = msg.screen; + if (this.syncSelectedLine.get()) { + this.selectedLine.set(msg.screen.selectedline); + } } if (msg.lines != null && msg.lines.length > 0) { for (let line of msg.lines) { @@ -199,6 +236,9 @@ class WebShareModelClass { } this.screen.set(screen); this.wsControl.reconnect(true); + if (this.syncSelectedLine.get()) { + this.selectedLine.set(screen.screen.selectedline); + } })(); } @@ -373,6 +413,18 @@ class WebShareModelClass { } return null; } + + docKeyDownHandler(e : any) : void { + if (isModKeyPress(e)) { + return; + } + if (e.code == "PageUp" && e.getModifierState("Meta")) { + this.updateSelectedLineIndex(-1); + } + if (e.code == "PageDown" && e.getModifierState("Meta")) { + this.updateSelectedLineIndex(1); + } + } } function getTermPtyData(termContext : T.TermContextUnion) : Promise {