From 6016302814b42d1c70cfc2192ca8110a5deb11ab Mon Sep 17 00:00:00 2001 From: anandamarsh Date: Mon, 23 Oct 2023 23:22:18 -0700 Subject: [PATCH] PE 125 new tab flow (#43) * please sync * can select connection. now need add conection * latest for discussion * solved "autoselect connection" issue * ready for PR * pair programming, fix some issues with new tab flow * final updates for new connection flow. integrate add new connection with error messages. add an option to cr to not create the line/history (non-interactive use) --------- Co-authored-by: sawka --- src/app/app.less | 2 +- src/app/common/modals/modals.less | 2 - src/app/common/modals/settings.tsx | 112 +++++++++++-------- src/app/connections/connections.less | 65 +++++++++++ src/app/connections/connections.tsx | 136 +++++++++++++++++++++-- src/app/sidebar/MainSideBar.tsx | 7 +- src/app/workspace/screen/screenview.less | 29 ++--- src/app/workspace/screen/screenview.tsx | 26 ++--- src/app/workspace/screen/tabs.tsx | 5 - src/model/model.ts | 47 ++++++-- wavesrv/pkg/cmdrunner/cmdrunner.go | 18 ++- webpack/webpack.common.js | 2 +- webpack/webpack.electron.prod.js | 2 +- webpack/webpack.prod.js | 2 +- 14 files changed, 342 insertions(+), 113 deletions(-) diff --git a/src/app/app.less b/src/app/app.less index ad94b9a46..fdd9dd781 100644 --- a/src/app/app.less +++ b/src/app/app.less @@ -58,7 +58,7 @@ svg.icon { .hover-effect-base:hover { cursor: pointer; - + .hover-effect-target { box-shadow: 0px 2px 2px 0 rgba(255, 255, 255, 0.1), 0px 4px 5px 0 rgba(255, 255, 255, 0.2), 0px 0px 5px 2.5px rgba(255, 255, 255, 0.5); diff --git a/src/app/common/modals/modals.less b/src/app/common/modals/modals.less index 5de143130..62fd40604 100644 --- a/src/app/common/modals/modals.less +++ b/src/app/common/modals/modals.less @@ -158,8 +158,6 @@ display: inline; width: 1em; height: 1em; - margin: 0 0.25em 0 1em; - vertical-align: middle; } &.is-hidden { display: none; diff --git a/src/app/common/modals/settings.tsx b/src/app/common/modals/settings.tsx index 1a1da1dcf..3811718b0 100644 --- a/src/app/common/modals/settings.tsx +++ b/src/app/common/modals/settings.tsx @@ -10,6 +10,7 @@ import cn from "classnames"; import { GlobalModel, GlobalCommandRunner, TabColors } from "../../../model/model"; import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common"; import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types"; +import { RemotesSelector } from "../../connections/connections"; import { PluginModel } from "../../../plugins/plugins"; import * as util from "../../../util/util"; import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg"; @@ -59,7 +60,7 @@ function commandRtnHandler(prtn: Promise, errorMessage: OV { +class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string; inline?: boolean }, {}> { shareCopied: OV = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" }); errorMessage: OV = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" }); @@ -204,30 +205,38 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: } render() { - let { sessionId, screenId } = this.props; + let { sessionId, screenId, inline } = this.props; let screen = GlobalModel.getScreenById(sessionId, screenId); if (screen == null) { return null; } let color: string = null; return ( -
-
-
- -
- -
-
screen settings ({screen.name.get()})
-
- -
-
+
+ {!inline &&
} +
+ {this.shareCopied.get() &&
} + {!inline && ( +
+
screen settings ({screen.name.get()})
+
+ +
+
+ )}
-
-
Screen Id
-
{screen.screenId}
-
+ {!inline && ( +
+
Screen Id
+
{screen.screenId}
+
+ )}
Name
@@ -263,39 +272,52 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
-
-
Archived
- - Archive will hide the screen tab. Commands and output will be retained in history. - -
+
Connection
- +
-
-
-
Actions
- - Delete will remove the screen, removing all commands and output from history. - -
-
-
- Delete Screen + {!inline && ( +
+
+
Archived
+ + Archive will hide the screen tab. Commands and output will be retained in + history. + +
+
+
-
+ )} + {!inline && ( +
+
+
Actions
+ + Delete will remove the screen, removing all commands and output from history. + +
+
+
+ Delete Screen +
+
+
+ )}
-
-
- Close -
-
+ {!inline && ( +
+
+ Close +
+
+ )}
); @@ -628,7 +650,7 @@ class ClientSettingsModal extends React.Component<{}, {}> { renderFontSizeDropdown(): any { let availableFontSizes = [8, 9, 10, 11, 12, 13, 14, 15]; let fsize: number = 0; - let curSize = GlobalModel.termFontSize.get() + let curSize = GlobalModel.termFontSize.get(); return (
diff --git a/src/app/connections/connections.less b/src/app/connections/connections.less index 10e8adf62..7a039405b 100644 --- a/src/app/connections/connections.less +++ b/src/app/connections/connections.less @@ -67,6 +67,7 @@ .remote-status-light { width: 2em; margin-top: 0.7em; + margin-right: 0.7em; font-size: 0.8em; } @@ -76,6 +77,7 @@ .remote-name-primary { font-weight: bold; max-width: 10em; + margin-right: 1em; } .remote-name-secondary { @@ -267,3 +269,66 @@ } } } + +.remotes-inline { + .icon { + width: 1em; + height: 1em; + fill: @base-color; + margin: 0 0 0 1em !important; + vertical-align: middle; + } + .dropdown { + margin-top: 1em; + .button { + color: @base-color; + border: none !important; + padding: 0 1em 0 0.2em; + &:hover, + &:focus { + border: none !important; + box-shadow: none; + } + .remote-name { + vertical-align: bottom; + .remote-status { + top: -2px; + left: 4px; + vertical-align: middle; + font-size: 0.85em; + } + } + } + .dropdown-content { + background: @background-session-components-solid; + } + .dropdown-item { + min-width: max-content; + } + &.is-active:hover { + box-shadow: none; + } + } + .remote-status-light { + display: inline; + margin-right: 0.5em; + } + .remote-name { + display: inline; + flex-grow: 1; + vertical-align: super; + + .remote-name-primary { + color: @base-color; + max-width: inherit; + margin-right: 1em; + display: inline; + } + + .remote-name-secondary { + color: @disabled-color; + max-width: inherit; + display: inline; + } + } +} diff --git a/src/app/connections/connections.tsx b/src/app/connections/connections.tsx index 58165ba4f..50aa3cad5 100644 --- a/src/app/connections/connections.tsx +++ b/src/app/connections/connections.tsx @@ -226,7 +226,29 @@ class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdi kwargs["autoinstall"] = this.tempAutoInstall.get() ? "1" : "0"; kwargs["visual"] = "1"; kwargs["submit"] = "1"; - GlobalCommandRunner.createRemote(cname, kwargs); + let model = this.props.model; + let shouldCr = model.onlyAddNewRemote.get(); + let prtn = GlobalCommandRunner.createRemote(cname, kwargs, false); + prtn.then((crtn) => { + if (crtn.success) { + if (shouldCr) { + let crRtn = GlobalCommandRunner.screenSetRemote(cname, true, false); + crRtn.then((crcrtn) => { + if (crcrtn.success) { + model.closeModal(); + return; + } + mobx.action(() => { + this.errorStr.set(crcrtn.error); + })(); + }); + } + return; + } + mobx.action(() => { + this.errorStr.set(crtn.error); + })(); + }); } @boundMethod @@ -1105,7 +1127,7 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> { renderRemoteMenuItem(remote: RemoteType, selectedId: string): any { return (
this.selectRemote(remote.remoteid)} className={cn("remote-menu-item", { "is-selected": remote.remoteid == selectedId })} > @@ -1153,6 +1175,7 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> { let isAuthEditMode = model.isAuthEditMode(); let selectedRemote = GlobalModel.getRemote(selectedRemoteId); let remoteEdit = model.remoteEdit.get(); + let onlyAddNewRemote = model.onlyAddNewRemote.get(); return (
@@ -1164,19 +1187,21 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> {
-
- {this.renderAddRemoteMenuItem()} - - {this.renderRemoteMenuItem(remote, selectedRemoteId)} - -
+ +
+ {this.renderAddRemoteMenuItem()} + + {this.renderRemoteMenuItem(remote, selectedRemoteId)} + +
{" "} +
{this.renderEmptyDetail()} - + { } } -export { RemotesModal }; +@mobxReact.observer +class RemotesSelector extends React.Component<{ model: RemotesModalModel; isChangeRemoteOnSelect?: boolean }, { isOpen: boolean }> { + constructor(props: any) { + super(props); + this.state = { + isOpen: false, + }; + } + + @boundMethod + selectRemote(remoteid: string, remotecanonicalname: string): void { + this.props.model.selectRemote(remoteid); + if (this.props.isChangeRemoteOnSelect) { + let prtn = GlobalCommandRunner.screenSetRemote(remotecanonicalname, true, false); + // TODO: see settings.tsx. use prtn to set error message + } + this.setState({ isOpen: false }); + } + + @boundMethod + clickAddRemote(): void { + GlobalModel.remotesModalModel.openModalForEdit({remoteedit: true}, true); + this.setState({ isOpen: false }); + } + + renderRemoteMenuItem(remote: RemoteType, selectedId: string): any { + return ( +
this.selectRemote(remote.remoteid, remote.remotecanonicalname)} + className={cn("dropdown-item remote-menu-item hoverEffect", { + "is-selected": remote.remoteid == selectedId, + })} + > +
+ +
+ +
+
{remote.remotecanonicalname}
+
+
+ +
+
{remote.remotealias}
+
{remote.remotecanonicalname}
+
+
+
+ ); + } + + render() { + const allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice()); + const remote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid); + const selectedRemoteDiv = ( +
+
+ +
+
{remote.remotealias}
+
{remote.remotecanonicalname}
+
+ ); + return ( +
+
+
+
+ +
+ +
+
+
+ ); + } +} + +export { RemotesModal, RemotesSelector }; diff --git a/src/app/sidebar/MainSideBar.tsx b/src/app/sidebar/MainSideBar.tsx index 293bed6fc..e431bf1b6 100644 --- a/src/app/sidebar/MainSideBar.tsx +++ b/src/app/sidebar/MainSideBar.tsx @@ -76,11 +76,6 @@ class MainSideBar extends React.Component<{}, {}> { GlobalCommandRunner.showRemote(remote.remoteid); } - @boundMethod - handleAddRemote(): void { - GlobalCommandRunner.openCreateRemote(); - } - @boundMethod handleHistoryClick(): void { if (GlobalModel.activeMainView.get() == "history") { @@ -200,7 +195,7 @@ class MainSideBar extends React.Component<{}, {}> { let mainView = GlobalModel.activeMainView.get(); return (
-
+
diff --git a/src/app/workspace/screen/screenview.less b/src/app/workspace/screen/screenview.less index c9b19a516..275b93e10 100644 --- a/src/app/workspace/screen/screenview.less +++ b/src/app/workspace/screen/screenview.less @@ -78,25 +78,18 @@ } } } + } - .window-empty { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - padding: 10px; - height: 100%; - color: @term-white; - - code { - color: @prompt-green; - background-color: transparent; - font-family: @fixed-font; - } - - &.should-fade { - opacity: 1; - animation: fade-in 2.5s; + .screen-settings-inline { + padding: 2em; + .settings-field { + display: block; + padding: 1.5em 1em; + margin-top: 0; + line-height: 2.5em; + border-top: 1px solid @base-border; + &:first-child { + border-top: none; } } } diff --git a/src/app/workspace/screen/screenview.tsx b/src/app/workspace/screen/screenview.tsx index 2aeef4fc7..96dddd234 100644 --- a/src/app/workspace/screen/screenview.tsx +++ b/src/app/workspace/screen/screenview.tsx @@ -6,15 +6,18 @@ 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 { If } 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 { GlobalCommandRunner } from "../../../model/model"; +import type { LineType, RenderModeType, LineFactoryProps, CommandRtnType } from "../../../types/types"; import localizedFormat from "dayjs/plugin/localizedFormat"; +import { InlineSettingsTextEdit } from "../../common/common"; import { GlobalModel, ScreenLines, Screen } from "../../../model/model"; import { Line } from "../../line/linecomps"; import { LinesView } from "../../line/linesview"; +import { ScreenSettingsModal } from "../../common/modals/settings"; import "./screenview.less"; import "./tabs.less"; @@ -181,11 +184,7 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { 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 ( @@ -204,6 +203,14 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
+ + +
@@ -240,13 +247,6 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { lineFactory={this.buildLineComponent} /> - -
-
- [workspace="{session.name.get()}" screen="{screen.name.get()}"] -
-
-
); } diff --git a/src/app/workspace/screen/tabs.tsx b/src/app/workspace/screen/tabs.tsx index a151c7207..90f6cac9c 100644 --- a/src/app/workspace/screen/tabs.tsx +++ b/src/app/workspace/screen/tabs.tsx @@ -194,11 +194,6 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
- {/**
-
move left {renderCmdText("[")}
-
move right {renderCmdText("]")}
-
new tab {renderCmdText("T")}
-
*/}
); } diff --git a/src/model/model.ts b/src/model/model.ts index a2024a01e..7c5cb8df0 100644 --- a/src/model/model.ts +++ b/src/model/model.ts @@ -75,6 +75,7 @@ import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; import customParseFormat from "dayjs/plugin/customParseFormat"; import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil"; +import { sortAndFilterRemotes } from "../util/util"; dayjs.extend(customParseFormat); dayjs.extend(localizedFormat); @@ -2426,6 +2427,9 @@ class RemotesModalModel { openState: OV = mobx.observable.box(false, { name: "RemotesModalModel-isOpen", }); + onlyAddNewRemote: OV = mobx.observable.box(false, { + name: "RemotesModalModel-onlyAddNewRemote", + }); selectedRemoteId: OV = mobx.observable.box(null, { name: "RemotesModalModel-selectedRemoteId", }); @@ -2467,8 +2471,9 @@ class RemotesModalModel { })(); } - openModalForEdit(redit: RemoteEditType): void { + openModalForEdit(redit: RemoteEditType, onlyAddNewRemote: boolean): void { mobx.action(() => { + this.onlyAddNewRemote.set(onlyAddNewRemote); this.openState.set(true); this.selectedRemoteId.set(redit.remoteid); this.remoteEdit.set(redit); @@ -2497,6 +2502,11 @@ class RemotesModalModel { cancelEditAuth(): void { mobx.action(() => { this.remoteEdit.set(null); + if (this.onlyAddNewRemote.get()) { + this.onlyAddNewRemote.set(false); + this.openState.set(false); + return; + } if (this.selectedRemoteId.get() == null) { this.openModal(); } @@ -2519,6 +2529,7 @@ class RemotesModalModel { this.openState.set(false); this.selectedRemoteId.set(null); this.remoteEdit.set(null); + this.onlyAddNewRemote.set(false); })(); setTimeout(() => GlobalModel.refocus(), 10); } @@ -3204,7 +3215,7 @@ class Model { if (rview.remoteshowall) { this.remotesModalModel.openModal(); } else if (rview.remoteedit != null) { - this.remotesModalModel.openModalForEdit(rview.remoteedit); + this.remotesModalModel.openModalForEdit(rview.remoteedit, false); } else if (rview.ptyremoteid) { this.remotesModalModel.openModal(rview.ptyremoteid); } @@ -3441,7 +3452,15 @@ class Model { uicontext: this.getUIContext(), interactive: interactive, }; - // console.log("CMD", pk.metacmd + (pk.metasubcmd != null ? ":" + pk.metasubcmd : ""), pk.args, pk.kwargs, pk.interactive); + /** + console.log( + "CMD", + pk.metacmd + (pk.metasubcmd != null ? ":" + pk.metasubcmd : ""), + pk.args, + pk.kwargs, + pk.interactive + ); + */ return this.submitCommandPacket(pk, interactive); } @@ -3455,7 +3474,7 @@ class Model { interactive: interactive, rawstr: cmdStr, }; - if (!addToHistory) { + if (!addToHistory && pk.kwargs) { pk.kwargs["nohist"] = "1"; } return this.submitCommandPacket(pk, interactive); @@ -3810,14 +3829,28 @@ class CommandRunner { GlobalModel.submitCommand("remote", "installcancel", null, { nohist: "1", remote: remoteid }, true); } - createRemote(cname: string, kwargsArg: Record) { + createRemote(cname: string, kwargsArg: Record, interactive: boolean): Promise { let kwargs = Object.assign({}, kwargsArg); kwargs["nohist"] = "1"; - GlobalModel.submitCommand("remote", "new", [cname], kwargs, true); + return GlobalModel.submitCommand("remote", "new", [cname], kwargs, interactive); } openCreateRemote(): void { - GlobalModel.submitCommand("remote", "new", null, { nohist: "1", visual: "1" }, true); + GlobalModel.submitCommand( + "remote", + "new", + null, + { nohist: "1", visual: "1" }, + true + ); + } + + screenSetRemote(remoteArg: string, nohist: boolean, interactive: boolean): Promise { + let kwargs = {}; + if (nohist) { + kwargs["nohist"] = "1"; + } + return GlobalModel.submitCommand("connect", null, [remoteArg], kwargs, interactive); } editRemote(remoteid: string, kwargsArg: Record): void { diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index 94514dca9..a340675cb 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -21,6 +21,7 @@ import ( "time" "unicode" + "github.com/google/uuid" "github.com/wavetermdev/waveterm/waveshell/pkg/base" "github.com/wavetermdev/waveterm/waveshell/pkg/packet" "github.com/wavetermdev/waveterm/waveshell/pkg/shexec" @@ -33,7 +34,6 @@ import ( "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore" "github.com/wavetermdev/waveterm/wavesrv/pkg/utilfn" - "github.com/google/uuid" ) const ( @@ -1116,7 +1116,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } editArgs, err := parseRemoteEditArgs(true, pk, false) if err != nil { - return makeRemoteEditErrorReturn_new(visualEdit, fmt.Errorf("/remote:new %v", err)) + return nil, fmt.Errorf("/remote:new %v", err) } r := &sstore.RemoteType{ RemoteId: scbase.GenPromptUUID(), @@ -1134,7 +1134,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss } err = remote.AddRemote(ctx, r, true) if err != nil { - return makeRemoteEditErrorReturn_new(visualEdit, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err)) + return nil, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err) } // SUCCESS return &sstore.ModelUpdate{ @@ -1580,6 +1580,18 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up if err != nil { return nil, fmt.Errorf("/%s error: cannot update curremote: %w", GetCmdStr(pk), err) } + noHist := resolveBool(pk.Kwargs["nohist"], false) + if noHist { + screen, err := sstore.GetScreenById(ctx, ids.ScreenId) + if err != nil { + return nil, fmt.Errorf("/% error: cannot resolve screen for update: %w", err) + } + update := &sstore.ModelUpdate{ + Screens: []*sstore.ScreenType{screen}, + Interactive: pk.Interactive, + } + return update, nil + } outputStr := fmt.Sprintf("connected to %s", GetFullRemoteDisplayName(rptr, rstate)) cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr)) if err != nil { diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 35362feba..a03e248da 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -8,7 +8,7 @@ module.exports = { prompt: ["./src/index.ts", "./src/app/app.less"], }, output: { - path: path.resolve(__dirname, "dist"), + path: path.resolve(__dirname, "../dist"), filename: "[name].js", }, module: { diff --git a/webpack/webpack.electron.prod.js b/webpack/webpack.electron.prod.js index 6e79d0232..daaea6694 100644 --- a/webpack/webpack.electron.prod.js +++ b/webpack/webpack.electron.prod.js @@ -16,7 +16,7 @@ const BUILD = makeBuildStr(); let merged = merge.merge(common, { mode: "production", output: { - path: path.resolve(__dirname, "dist"), + path: path.resolve(__dirname, "../dist"), filename: "[name].js", }, devtool: "source-map", diff --git a/webpack/webpack.prod.js b/webpack/webpack.prod.js index 16fafedcf..aa56bf838 100644 --- a/webpack/webpack.prod.js +++ b/webpack/webpack.prod.js @@ -21,7 +21,7 @@ if (process.env.WEBPACK_ANALYZE) { let merged = merge.merge(common, { mode: "production", output: { - path: path.resolve(__dirname, "dist"), + path: path.resolve(__dirname, "../dist"), filename: "[name].js", }, devtool: false,