webshare controls, sync selected, copy

This commit is contained in:
sawka 2023-03-30 22:26:01 -07:00
parent cf7c25e78d
commit a236348e44
3 changed files with 181 additions and 12 deletions

View File

@ -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;
}
}
}

View File

@ -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<V> = mobx.IObservableValue<V>;
type OArr<V> = mobx.IObservableArray<V>;
@ -122,6 +123,11 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
isCmdExpanded : OV<boolean> = mobx.observable.box(false, {name: "cmd-expanded"});
isOverflow : OV<boolean> = mobx.observable.box(false, {name: "line-overflow"});
cmdTextRef : React.RefObject<any> = React.createRef();
copiedIndicator : OV<boolean> = 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<children.length; i++) {
let ch = children[i];
childWidth += ch.offsetWidth;
}
let isOverflow = (childWidth > 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,
</div>
);
}
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 (
<div className={mainCn} data-lineid={line.lineid} data-linenum={line.linenum}>
<div key="focus" className={cn("focus-indicator", {"selected active": isSelected})}/>
<div className="line-header">
<div className={mainCn} data-lineid={line.lineid} data-linenum={line.linenum} onClick={this.handleClick}>
<If condition={this.copiedIndicator.get()}>
<div key="copied" className="copied-indicator">
<div>copied</div>
</div>
</If>
<div key="focus" className={cn("focus-indicator", {"selected": isSelected || isServerSelected}, {"active": isSelected})}/>
<div className={cn("line-header", {"is-expanded": isExpanded})}>
<LineAvatar line={line} cmd={cmd}/>
{this.renderMetaWrap()}
<If condition={this.copyAllowed()}>
<div key="copy" title="Copy Command" className={cn("line-icon copy-icon")} onClick={this.clickCopy} style={{marginLeft: 5}}>
<i className="fa-sharp fa-solid fa-copy"/>
</div>
</If>
</div>
<TerminalRenderer line={line} cmd={cmd} width={width} staticRender={false} visible={visObs} onHeightChange={this.handleHeightChange}/>
</div>
@ -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 (
<div id="main">
<div className="logo-header">
<div className="logo-text">
<a target="_blank" href="https://www.getprompt.dev">[prompt]</a>
</div>
<If condition={screen != null}>
<div className="screen-name">{screen.screen.sharename}</div>
</If>
<div className="flex-spacer"/>
<a href="https://getprompt.dev/download/" target="_blank" className="download-button button is-link">
<span>Download Prompt</span>
@ -489,6 +554,14 @@ class WebShareMain extends React.Component<{}, {}> {
</span>
</a>
</div>
<div className="webshare-controls">
<div className="screen-sharename">{shareName}</div>
<div className="flex-spacer"/>
<div className="sync-control">
<div>Sync Selection</div>
<Toggle checked={WebShareModel.syncSelectedLine.get()} onChange={(val) => WebShareModel.setSyncSelectedLine(val)}/>
</div>
</div>
<div className="prompt-content">
<If condition={screen != null}>
<WebScreenView/>

View File

@ -35,6 +35,8 @@ class WebShareModelClass {
contentHeightCache : Record<string, number> = {}; // lineid => height
wsControl : WebShareWSControl;
anchor : {anchorLine : number, anchorOffset : number} = {anchorLine: 0, anchorOffset: 0};
selectedLine : OV<number> = mobx.observable.box(0, {name: "selectedLine"});
syncSelectedLine : OV<boolean> = 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<T.PtyDataType> {