tos, also redo screen settings so settings take affect immediately, working on smoother webshare flow

This commit is contained in:
sawka
2023-04-04 21:54:20 -07:00
parent 84da8264e8
commit cd8e17413d
9 changed files with 207 additions and 103 deletions
+24 -1
View File
@@ -247,4 +247,27 @@ class Markdown extends React.Component<{text : string, style? : any, extraClassN
}
}
export {CmdStrCode, Toggle, renderCmdText, RemoteStatusLight, InlineSettingsTextEdit, InfoMessage, Markdown};
@mobxReact.observer
class SettingsError extends React.Component<{errorMessage : OV<string>}, {}> {
@boundMethod
dismissError() : void {
mobx.action(() => {
this.props.errorMessage.set(null);
})();
}
render() {
if (this.props.errorMessage.get() == null) {
return null;
}
return (
<div className="settings-field settings-error">
<div>Error: {this.props.errorMessage.get()}</div>
<div className="flex-spacer"/>
<div onClick={this.dismissError} className="error-dismiss"><i className="fa-sharp fa-solid fa-xmark"/></div>
</div>
);
}
}
export {CmdStrCode, Toggle, renderCmdText, RemoteStatusLight, InlineSettingsTextEdit, InfoMessage, Markdown, SettingsError};
+5 -9
View File
@@ -13,7 +13,7 @@ import type * as T from "./types";
import localizedFormat from 'dayjs/plugin/localizedFormat';
import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, riToRPtr, TabColors, RemoteColors} from "./model";
import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols, getMonoFontSize} from "./textmeasure";
import {isModKeyPress, boundInt, sortAndFilterRemotes} from "./util";
import {isModKeyPress, boundInt, sortAndFilterRemotes, makeExternLink, isBlank} from "./util";
import {BookmarksView} from "./bookmarks";
import {HistoryView} from "./history";
import {Line, Prompt} from "./linecomps";
@@ -21,6 +21,7 @@ import {ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSett
import {RemotesModal} from "./remotes";
import {renderCmdText, RemoteStatusLight, Markdown} from "./elements";
import {LinesView} from "./linesview";
import {TosModal} from "./modals";
dayjs.extend(localizedFormat)
@@ -44,14 +45,6 @@ type InterObsValue = {
timeoutid? : any,
};
function isBlank(s : string) : boolean {
return (s == null || s == "");
}
function makeExternLink(url : string) : string {
return "https://extern?" + encodeURIComponent(url);
}
function scrollDiv(div : any, amt : number) {
if (div == null) {
return;
@@ -1976,6 +1969,9 @@ class Main extends React.Component<{}, {}> {
<BookmarksView/>
</div>
<AlertModal/>
<If condition={GlobalModel.needsTos()}>
<TosModal/>
</If>
<If condition={GlobalModel.welcomeModalOpen.get()}>
<WelcomeModal/>
</If>
+53
View File
@@ -0,0 +1,53 @@
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, When, Otherwise, Choose} from "tsx-control-statements/components";
import cn from "classnames";
import {GlobalModel, GlobalCommandRunner} from "./model";
import * as util from "./util";
type OV<V> = mobx.IObservableValue<V>;
class TosModal extends React.Component<{}, {}> {
@boundMethod
acceptTos() : void {
GlobalCommandRunner.clientAcceptTos();
}
render() {
return (
<div className={cn("modal tos-modal prompt-modal is-active")}>
<div className="modal-background"/>
<div className="modal-content">
<header>
<div className="modal-title">Welcome to [prompt]</div>
</header>
<div className="inner-content">
<div className="content">
<p>Thank you for downloading Prompt!</p>
<p>
Prompt is a new terminal designed to help you save time and organize your command life.
Prompt is currently in beta. If you'd like to give feedback, run into problems, have questions,
or need help, please join the Prompt <a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}>discord&nbsp;server</a>.
</p>
<p>
Prompt is free to use, no email or registration required (unless you're using the cloud features).
</p>
<p>
<a target="_blank" href={util.makeExternLink("https://www.getprompt.dev/tos.html")}>Full Terms of Service</a>
</p>
</div>
</div>
<footer>
<div className="flex-spacer"/>
<div onClick={this.acceptTos} className="button is-prompt-green is-outlined is-small">Accept Terms of Service</div>
</footer>
</div>
</div>
);
}
}
export {TosModal};
+18 -6
View File
@@ -2448,6 +2448,14 @@ class Model {
setTimeout(() => this.getClientDataLoop(1), 10);
}
needsTos() : boolean {
let cdata = this.clientData.get();
if (cdata == null) {
return false;
}
return (cdata.clientopts == null || !cdata.clientopts.acceptedtos);
}
refreshClient() : void {
getApi().reloadWindow();
}
@@ -3364,14 +3372,14 @@ class CommandRunner {
GlobalModel.submitCommand("screen", "resize", null, {"nohist": "1", "screen": screenId, "cols": String(cols), "rows": String(rows)}, false);
}
screenArchive(screenId : string, shouldArchive : boolean) {
GlobalModel.submitCommand("screen", "archive", [screenId, (shouldArchive ? "1" : "0")], {"nohist": "1"}, false);
screenArchive(screenId : string, shouldArchive : boolean) : Promise<CommandRtnType> {
return GlobalModel.submitCommand("screen", "archive", [screenId, (shouldArchive ? "1" : "0")], {"nohist": "1"}, false);
}
screenWebShare(screenId : string, shouldShare : boolean) {
screenWebShare(screenId : string, shouldShare : boolean) : Promise<CommandRtnType> {
let kwargs : Record<string, string> = {"nohist": "1"};
kwargs["screen"] = screenId;
GlobalModel.submitCommand("screen", "webshare", [(shouldShare ? "1" : "0")], kwargs, true);
return GlobalModel.submitCommand("screen", "webshare", [(shouldShare ? "1" : "0")], kwargs, false);
}
showRemote(remoteid : string) {
@@ -3456,11 +3464,11 @@ class CommandRunner {
GlobalModel.submitCommand("screen", "set", null, {"focus": focusVal, "nohist": "1"}, false);
}
screenSetSettings(screenId : string, settings : {tabcolor? : string, name? : string}) : void {
screenSetSettings(screenId : string, settings : {tabcolor? : string, name? : string}, interactive : boolean) : Promise<CommandRtnType> {
let kwargs = Object.assign({}, settings);
kwargs["nohist"] = "1";
kwargs["screen"] = screenId;
GlobalModel.submitCommand("screen", "set", null, kwargs, true);
return GlobalModel.submitCommand("screen", "set", null, kwargs, interactive);
}
sessionSetSettings(sessionId : string, settings : {name? : string}) : void {
@@ -3527,6 +3535,10 @@ class CommandRunner {
GlobalModel.submitCommand("client", "set", null, kwargs, true);
}
clientAcceptTos() : void {
GlobalModel.submitCommand("client", "accepttos", null, {"nohist": "1"}, true);
}
editBookmark(bookmarkId : string, desc : string, cmdstr : string) {
let kwargs = {
"nohist": "1",
+1 -1
View File
@@ -370,7 +370,7 @@ class CreateRemote extends React.Component<{model : RemotesModalModel, remoteEdi
</div>
</div>
<If condition={!util.isBlank(this.getErrorStr())}>
<div className="remoteedit-error">
<div className="settings-field settings-error">
Error: {this.getErrorStr()}
</div>
</If>
+83 -81
View File
@@ -6,8 +6,8 @@ import {boundMethod} from "autobind-decorator";
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
import cn from "classnames";
import {GlobalModel, GlobalCommandRunner, TabColors} from "./model";
import {Toggle, RemoteStatusLight, InlineSettingsTextEdit} from "./elements";
import {LineType, RendererPluginType, ClientDataType} from "./types";
import {Toggle, RemoteStatusLight, InlineSettingsTextEdit, SettingsError} from "./elements";
import {LineType, RendererPluginType, ClientDataType, CommandRtnType} from "./types";
import {PluginModel} from "./plugins";
import * as util from "./util";
@@ -24,13 +24,29 @@ const VERSION = __PROMPT_VERSION__;
// @ts-ignore
const BUILD = __PROMPT_BUILD__;
const WebShareMarkdown = `
You are about to share a terminal tab on the web. Please make sure that you do
NOT share any private information, keys, passwords, or other sensitive information.
You are responsible for what you are sharing, be smart.
See [Terms of Service](https://www.getprompt.dev/tos.html) for more details.
`.trim();
function commandRtnHandler(prtn : Promise<CommandRtnType>, errorMessage : OV<string>) {
prtn.then((crtn) => {
if (crtn.success) {
return;
}
mobx.action(() => {
errorMessage.set(crtn.error);
})();
});
}
@mobxReact.observer
class ScreenSettingsModal extends React.Component<{sessionId : string, screenId : string}, {}> {
tempName : OV<string>;
tempTabColor : OV<string>;
tempArchived : OV<boolean>;
tempWebShared : OV<boolean>;
shareCopied : OV<boolean> = mobx.observable.box(false, {name: "sw-shareCopied"});
shareCopied : OV<boolean> = mobx.observable.box(false, {name: "ScreenSettings-shareCopied"});
errorMessage : OV<string> = mobx.observable.box(null, {name: "ScreenSettings-errorMessage"});
constructor(props : any) {
super(props);
@@ -39,10 +55,6 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
if (screen == null) {
return;
}
this.tempName = mobx.observable.box(screen.name.get(), {name: "screenSettings-tempName"});
this.tempTabColor = mobx.observable.box(screen.getTabColor(), {name: "screenSettings-tempTabColor"});
this.tempArchived = mobx.observable.box(screen.archived.get(), {name: "screenSettings-tempArchived"});
this.tempWebShared = mobx.observable.box(screen.isWebShared(), {name: "screenSettings-tempWebShare"});
}
@boundMethod
@@ -53,58 +65,45 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
}
@boundMethod
handleOK() : void {
mobx.action(() => {
GlobalModel.screenSettingsModal.set(null);
})();
let screen = GlobalModel.getScreenById(this.props.sessionId, this.props.screenId);
selectTabColor(color : string) : void {
let {sessionId, screenId} = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
return;
}
let settings : {tabcolor? : string, name? : string} = {};
if (this.tempTabColor.get() != screen.getTabColor()) {
settings.tabcolor = this.tempTabColor.get();
if (screen.getTabColor() == color) {
return;
}
if (this.tempName.get() != screen.name.get()) {
settings.name = this.tempName.get();
}
if (Object.keys(settings).length > 0) {
GlobalCommandRunner.screenSetSettings(this.props.screenId, settings);
}
if (this.tempArchived.get() != screen.archived.get()) {
GlobalCommandRunner.screenArchive(screen.screenId, this.tempArchived.get());
}
if (this.tempWebShared.get() != screen.isWebShared()) {
GlobalCommandRunner.screenWebShare(screen.screenId, this.tempWebShared.get());
}
}
@boundMethod
handleChangeName(e : any) : void {
mobx.action(() => {
this.tempName.set(e.target.value);
})();
}
@boundMethod
selectTabColor(color : string) : void {
mobx.action(() => {
this.tempTabColor.set(color);
})();
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, {tabcolor: color}, false);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeArchived(val : boolean) : void {
mobx.action(() => {
this.tempArchived.set(val);
})();
let {sessionId, screenId} = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
return;
}
if (screen.archived.get() == val) {
return;
}
let prtn = GlobalCommandRunner.screenArchive(this.props.screenId, val);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeWebShare(val : boolean) : void {
mobx.action(() => {
this.tempWebShared.set(val);
})();
let {sessionId, screenId} = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
return;
}
if (screen.isWebShared() == val) {
return;
}
let prtn = GlobalCommandRunner.screenWebShare(screen.screenId, val);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
@@ -129,13 +128,33 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
}, 600)
}
webSharedUpdated() : boolean {
@boundMethod
inlineUpdateName(val : string) : void {
let {sessionId, screenId} = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
return null;
return;
}
return screen.isWebShared() != this.tempWebShared.get();
console.log("inline update name", val);
if (val == screen.name.get()) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, {name: val}, false);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
setErrorMessage(msg : string) : void {
mobx.action(() => {
this.errorMessage.set(msg);
})();
}
@boundMethod
dismissError() : void {
mobx.action(() => {
this.errorMessage.set(null);
})();
}
render() {
@@ -172,7 +191,7 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
Name
</div>
<div className="settings-input">
<input type="text" placeholder="Tab Name" onChange={this.handleChangeName} value={this.tempName.get()} maxLength={50}/>
<InlineSettingsTextEdit placeholder="name" text={screen.name.get() ?? "(none)"} value={screen.name.get() ?? ""} onChange={this.inlineUpdateName} maxLength={50} showIcon={true}/>
</div>
</div>
<div className="settings-field">
@@ -182,10 +201,10 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
<div className="settings-input">
<div className="tab-colors">
<div className="tab-color-cur">
<span className={cn("icon tab-color-icon", "color-" + this.tempTabColor.get())}>
<span className={cn("icon tab-color-icon", "color-" + screen.getTabColor())}>
<i className="fa-sharp fa-solid fa-square"/>
</span>
<span>{this.tempTabColor.get()}</span>
<span>{screen.getTabColor()}</span>
</div>
<div className="tab-color-sep">|</div>
<For each="color" of={TabColors}>
@@ -203,11 +222,7 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
Archived
</div>
<div className="settings-input">
<Toggle checked={this.tempArchived.get()} onChange={this.handleChangeArchived}/>
<div className="action-text">
<If condition={this.tempArchived.get() && this.tempArchived.get() != screen.archived.get()}>will be archived</If>
<If condition={!this.tempArchived.get() && this.tempArchived.get() != screen.archived.get()}>will be un-archived</If>
</div>
<Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived}/>
</div>
</div>
<div className="settings-field">
@@ -215,34 +230,21 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
Web Sharing
</div>
<div className="settings-input">
<Toggle checked={this.tempWebShared.get()} onChange={this.handleChangeWebShare}/>
<div className="action-text">
<If condition={this.tempWebShared.get() && this.webSharedUpdated()}>will be web-shared</If>
<If condition={!this.tempWebShared.get() && this.webSharedUpdated()}>will stop being web-shared</If>
<If condition={screen.isWebShared() && !this.webSharedUpdated()}>
<div className="button settings-share-link is-prompt-green is-outlined is-small" onClick={this.copyShareLink}>
<span>copy share link</span>
<span className="icon">
<i className="fa-sharp fa-solid fa-copy"/>
</span>
</div>
</If>
</div>
<Toggle checked={screen.isWebShared()} onChange={this.handleChangeWebShare}/>
</div>
</div>
<If condition={this.tempWebShared.get() && false}>
<If condition={screen.isWebShared()}>
<div className="settings-field sub-field">
<div className="settings-label">
Share Link
</div>
<div className="settings-input">
<div className="button is-prompt-green is-outlined is-small" onClick={this.copyShareLink}>
<a href={util.makeExternLink(screen.getWebShareUrl())} target="_blank" className="button is-prompt-green is-outlined is-small a-block">
<span>open in browser</span>
<span className="icon">
<i className="fa-sharp fa-solid fa-up-right-from-square"/>
</span>
</div>
</a>
<div className="button is-prompt-green is-outlined is-small ml-4" onClick={this.copyShareLink}>
<span>copy link</span>
<span className="icon">
@@ -252,10 +254,10 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
</div>
</div>
</If>
<SettingsError errorMessage={this.errorMessage}/>
</div>
<footer>
<div onClick={this.closeModal} className="button is-prompt-cancel is-outlined is-small">Cancel</div>
<div onClick={this.handleOK} className="button is-prompt-green is-outlined is-small">OK</div>
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">Close</div>
</footer>
</div>
</div>
+17 -4
View File
@@ -51,6 +51,10 @@
html, body, #app, #main {
background-color: #000;
height: 100vh;
a:hover {
color: #485fc7;
}
}
body {
@@ -985,10 +989,6 @@ body::-webkit-scrollbar {
a {
color: #bbb;
}
a:hover {
color: #3273dc;
}
}
p.menu-label {
@@ -2471,6 +2471,16 @@ input[type=checkbox] {
.mono-font(inherit, 700);
}
.tos-modal {
a:hover {
text-decoration: underline;
}
.content {
.mono-font(13px);
}
}
.disconnected-modal {
.modal-content {
footer {
@@ -3666,3 +3676,6 @@ body.prompt-webshare #main {
}
}
a.a-block {
display: block;
}
+1
View File
@@ -439,6 +439,7 @@ type FeOptsType = {
type ClientOptsType = {
notelemetry : boolean,
acceptedtos : number,
};
type ClientDataType = {
+5 -1
View File
@@ -329,4 +329,8 @@ function sortAndFilterRemotes(origRemotes : RemoteType[]) : RemoteType[] {
return remotes;
}
export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, parseEnv0, boundInt, isModKeyPress, incObs, isBlank, loadFonts, getTodayStr, getYesterdayStr, getDateStr, sortAndFilterRemotes};
function makeExternLink(url : string) : string {
return "https://extern?" + encodeURIComponent(url);
}
export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, parseEnv0, boundInt, isModKeyPress, incObs, isBlank, loadFonts, getTodayStr, getYesterdayStr, getDateStr, sortAndFilterRemotes, makeExternLink};