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
This commit is contained in:
anandamarsh 2023-10-23 23:22:18 -07:00 committed by GitHub
parent 4000baa6c5
commit 6016302814
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 342 additions and 113 deletions

View File

@ -58,7 +58,7 @@ svg.icon {
.hover-effect-base:hover { .hover-effect-base:hover {
cursor: pointer; cursor: pointer;
.hover-effect-target { .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), 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); 0px 0px 5px 2.5px rgba(255, 255, 255, 0.5);

View File

@ -158,8 +158,6 @@
display: inline; display: inline;
width: 1em; width: 1em;
height: 1em; height: 1em;
margin: 0 0.25em 0 1em;
vertical-align: middle;
} }
&.is-hidden { &.is-hidden {
display: none; display: none;

View File

@ -10,6 +10,7 @@ import cn from "classnames";
import { GlobalModel, GlobalCommandRunner, TabColors } from "../../../model/model"; import { GlobalModel, GlobalCommandRunner, TabColors } from "../../../model/model";
import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common"; import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common";
import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types"; import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types";
import { RemotesSelector } from "../../connections/connections";
import { PluginModel } from "../../../plugins/plugins"; import { PluginModel } from "../../../plugins/plugins";
import * as util from "../../../util/util"; import * as util from "../../../util/util";
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg"; import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
@ -59,7 +60,7 @@ function commandRtnHandler(prtn: Promise<CommandRtnType>, errorMessage: OV<strin
} }
@mobxReact.observer @mobxReact.observer
class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string }, {}> { class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string; inline?: boolean }, {}> {
shareCopied: OV<boolean> = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" }); shareCopied: OV<boolean> = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" });
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" }); errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
@ -204,30 +205,38 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
} }
render() { render() {
let { sessionId, screenId } = this.props; let { sessionId, screenId, inline } = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId); let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) { if (screen == null) {
return null; return null;
} }
let color: string = null; let color: string = null;
return ( return (
<div className={cn("modal screen-settings-modal settings-modal prompt-modal is-active")}> <div
<div className="modal-background" /> className={
<div className="modal-content"> inline
<If condition={this.shareCopied.get()}> ? "screen-settings-inline"
<div className="copied-indicator" /> : cn("modal screen-settings-modal settings-modal prompt-modal is-active")
</If> }
<header> >
<div className="modal-title">screen settings ({screen.name.get()})</div> {!inline && <div className="modal-background" />}
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}> <div className={inline ? "inline-content" : "modal-content"}>
<XmarkIcon /> {this.shareCopied.get() && <div className="copied-indicator" />}
</div> {!inline && (
</header> <header>
<div className="modal-title">screen settings ({screen.name.get()})</div>
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
<XmarkIcon />
</div>
</header>
)}
<div className="inner-content"> <div className="inner-content">
<div className="settings-field"> {!inline && (
<div className="settings-label">Screen Id</div> <div className="settings-field">
<div className="settings-input">{screen.screenId}</div> <div className="settings-label">Screen Id</div>
</div> <div className="settings-input">{screen.screenId}</div>
</div>
)}
<div className="settings-field"> <div className="settings-field">
<div className="settings-label">Name</div> <div className="settings-label">Name</div>
<div className="settings-input"> <div className="settings-input">
@ -263,39 +272,52 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
</div> </div>
</div> </div>
<div className="settings-field"> <div className="settings-field">
<div className="settings-label"> <div className="settings-label">Connection</div>
<div>Archived</div>
<InfoMessage width={400}>
Archive will hide the screen tab. Commands and output will be retained in history.
</InfoMessage>
</div>
<div className="settings-input"> <div className="settings-input">
<Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived} /> <RemotesSelector model={GlobalModel.remotesModalModel} isChangeRemoteOnSelect={true} />
</div> </div>
</div> </div>
<div className="settings-field"> {!inline && (
<div className="settings-label"> <div className="settings-field">
<div>Actions</div> <div className="settings-label">
<InfoMessage width={400}> <div>Archived</div>
Delete will remove the screen, removing all commands and output from history. <InfoMessage width={400}>
</InfoMessage> Archive will hide the screen tab. Commands and output will be retained in
</div> history.
<div className="settings-input"> </InfoMessage>
<div </div>
onClick={this.handleDeleteScreen} <div className="settings-input">
className="button is-prompt-danger is-outlined is-small" <Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived} />
>
Delete Screen
</div> </div>
</div> </div>
</div> )}
{!inline && (
<div className="settings-field">
<div className="settings-label">
<div>Actions</div>
<InfoMessage width={400}>
Delete will remove the screen, removing all commands and output from history.
</InfoMessage>
</div>
<div className="settings-input">
<div
onClick={this.handleDeleteScreen}
className="button is-prompt-danger is-outlined is-small"
>
Delete Screen
</div>
</div>
</div>
)}
<SettingsError errorMessage={this.errorMessage} /> <SettingsError errorMessage={this.errorMessage} />
</div> </div>
<footer> {!inline && (
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small"> <footer>
Close <div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
</div> Close
</footer> </div>
</footer>
)}
</div> </div>
</div> </div>
); );
@ -628,7 +650,7 @@ class ClientSettingsModal extends React.Component<{}, {}> {
renderFontSizeDropdown(): any { renderFontSizeDropdown(): any {
let availableFontSizes = [8, 9, 10, 11, 12, 13, 14, 15]; let availableFontSizes = [8, 9, 10, 11, 12, 13, 14, 15];
let fsize: number = 0; let fsize: number = 0;
let curSize = GlobalModel.termFontSize.get() let curSize = GlobalModel.termFontSize.get();
return ( return (
<div className={cn("dropdown", "font-size-dropdown", { "is-active": this.fontSizeDropdownActive.get() })}> <div className={cn("dropdown", "font-size-dropdown", { "is-active": this.fontSizeDropdownActive.get() })}>
<div className="dropdown-trigger"> <div className="dropdown-trigger">

View File

@ -67,6 +67,7 @@
.remote-status-light { .remote-status-light {
width: 2em; width: 2em;
margin-top: 0.7em; margin-top: 0.7em;
margin-right: 0.7em;
font-size: 0.8em; font-size: 0.8em;
} }
@ -76,6 +77,7 @@
.remote-name-primary { .remote-name-primary {
font-weight: bold; font-weight: bold;
max-width: 10em; max-width: 10em;
margin-right: 1em;
} }
.remote-name-secondary { .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;
}
}
}

View File

@ -226,7 +226,29 @@ class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdi
kwargs["autoinstall"] = this.tempAutoInstall.get() ? "1" : "0"; kwargs["autoinstall"] = this.tempAutoInstall.get() ? "1" : "0";
kwargs["visual"] = "1"; kwargs["visual"] = "1";
kwargs["submit"] = "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 @boundMethod
@ -1105,7 +1127,7 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> {
renderRemoteMenuItem(remote: RemoteType, selectedId: string): any { renderRemoteMenuItem(remote: RemoteType, selectedId: string): any {
return ( return (
<div <div
key={remote.remoteid} key={remote.remotecanonicalname}
onClick={() => this.selectRemote(remote.remoteid)} onClick={() => this.selectRemote(remote.remoteid)}
className={cn("remote-menu-item", { "is-selected": remote.remoteid == selectedId })} 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 isAuthEditMode = model.isAuthEditMode();
let selectedRemote = GlobalModel.getRemote(selectedRemoteId); let selectedRemote = GlobalModel.getRemote(selectedRemoteId);
let remoteEdit = model.remoteEdit.get(); let remoteEdit = model.remoteEdit.get();
let onlyAddNewRemote = model.onlyAddNewRemote.get();
return ( return (
<div className={cn("modal remotes-modal settings-modal prompt-modal is-active")}> <div className={cn("modal remotes-modal settings-modal prompt-modal is-active")}>
<div className="modal-background" /> <div className="modal-background" />
@ -1164,19 +1187,21 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> {
</div> </div>
</header> </header>
<div className="inner-content"> <div className="inner-content">
<div className="remotes-menu"> <If condition={!onlyAddNewRemote}>
{this.renderAddRemoteMenuItem()} <div className="remotes-menu">
<For each="remote" of={allRemotes}> {this.renderAddRemoteMenuItem()}
{this.renderRemoteMenuItem(remote, selectedRemoteId)} <For each="remote" of={allRemotes}>
</For> {this.renderRemoteMenuItem(remote, selectedRemoteId)}
</div> </For>
</div>{" "}
</If>
<If condition={selectedRemote == null}> <If condition={selectedRemote == null}>
<If condition={remoteEdit != null}> <If condition={remoteEdit != null}>
<CreateRemote model={model} remoteEdit={remoteEdit} /> <CreateRemote model={model} remoteEdit={remoteEdit} />
</If> </If>
<If condition={remoteEdit == null}>{this.renderEmptyDetail()}</If> <If condition={remoteEdit == null}>{this.renderEmptyDetail()}</If>
</If> </If>
<If condition={selectedRemote != null}> <If condition={selectedRemote != null && !onlyAddNewRemote}>
<If condition={!isAuthEditMode}> <If condition={!isAuthEditMode}>
<RemoteDetailView <RemoteDetailView
key={"remotedetail-" + selectedRemoteId} key={"remotedetail-" + selectedRemoteId}
@ -1200,4 +1225,95 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> {
} }
} }
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 (
<div
key={remote.remoteid}
onClick={() => this.selectRemote(remote.remoteid, remote.remotecanonicalname)}
className={cn("dropdown-item remote-menu-item hoverEffect", {
"is-selected": remote.remoteid == selectedId,
})}
>
<div className="remote-status-light">
<RemoteStatusLight remote={remote} />
</div>
<If condition={util.isBlank(remote.remotealias)}>
<div className="remote-name">
<div className="remote-name-primary">{remote.remotecanonicalname}</div>
</div>
</If>
<If condition={!util.isBlank(remote.remotealias)}>
<div className="remote-name">
<div className="remote-name-primary">{remote.remotealias}</div>
<div className="remote-name-secondary">{remote.remotecanonicalname}</div>
</div>
</If>
</div>
);
}
render() {
const allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice());
const remote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
const selectedRemoteDiv = (
<div className="remote-name">
<div className="remote-status-light">
<RemoteStatusLight remote={remote} />
</div>
<div className="remote-name-primary">{remote.remotealias}</div>
<div className="remote-name-secondary">{remote.remotecanonicalname}</div>
</div>
);
return (
<div className={"remotes-inline"}>
<div className="remotes-menu">
<div className={`dropdown ${this.state.isOpen ? "is-active" : ""}`}>
<div className="dropdown-trigger">
<button className="button" onClick={() => this.setState({ isOpen: !this.state.isOpen })}>
{selectedRemoteDiv}
<AngleDownIcon className="icon" />
</button>
</div>
<div className="dropdown-menu" id="dropdown-menu3" role="menu">
<div className="dropdown-content">
{allRemotes
.filter(({ remoteid }) => remoteid !== remote.remoteid)
.map((remote) => this.renderRemoteMenuItem(remote, remote.remoteid))}
<div onClick={this.clickAddRemote} className=".dropdown-item hoverEffect">
<AddIcon className="icon" /> Add SSH Connection
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export { RemotesModal, RemotesSelector };

View File

@ -76,11 +76,6 @@ class MainSideBar extends React.Component<{}, {}> {
GlobalCommandRunner.showRemote(remote.remoteid); GlobalCommandRunner.showRemote(remote.remoteid);
} }
@boundMethod
handleAddRemote(): void {
GlobalCommandRunner.openCreateRemote();
}
@boundMethod @boundMethod
handleHistoryClick(): void { handleHistoryClick(): void {
if (GlobalModel.activeMainView.get() == "history") { if (GlobalModel.activeMainView.get() == "history") {
@ -200,7 +195,7 @@ class MainSideBar extends React.Component<{}, {}> {
let mainView = GlobalModel.activeMainView.get(); let mainView = GlobalModel.activeMainView.get();
return ( return (
<div className={cn("main-sidebar", { collapsed: isCollapsed }, { "is-dev": GlobalModel.isDev })}> <div className={cn("main-sidebar", { collapsed: isCollapsed }, { "is-dev": GlobalModel.isDev })}>
<div className="title-bar-drag"/> <div className="title-bar-drag" />
<div className="arrow-container hoverEffect" onClick={this.toggleCollapsed}> <div className="arrow-container hoverEffect" onClick={this.toggleCollapsed}>
<LeftChevronIcon className="icon" /> <LeftChevronIcon className="icon" />
</div> </div>

View File

@ -78,25 +78,18 @@
} }
} }
} }
}
.window-empty { .screen-settings-inline {
display: flex; padding: 2em;
align-items: center; .settings-field {
justify-content: center; display: block;
width: 100%; padding: 1.5em 1em;
padding: 10px; margin-top: 0;
height: 100%; line-height: 2.5em;
color: @term-white; border-top: 1px solid @base-border;
&:first-child {
code { border-top: none;
color: @prompt-green;
background-color: transparent;
font-family: @fixed-font;
}
&.should-fade {
opacity: 1;
animation: fade-in 2.5s;
} }
} }
} }

View File

@ -6,15 +6,18 @@ import * as mobxReact from "mobx-react";
import * as mobx from "mobx"; import * as mobx from "mobx";
import { sprintf } from "sprintf-js"; import { sprintf } from "sprintf-js";
import { boundMethod } from "autobind-decorator"; 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 cn from "classnames";
import { debounce } from "throttle-debounce"; import { debounce } from "throttle-debounce";
import dayjs from "dayjs"; 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 localizedFormat from "dayjs/plugin/localizedFormat";
import { InlineSettingsTextEdit } from "../../common/common";
import { GlobalModel, ScreenLines, Screen } from "../../../model/model"; import { GlobalModel, ScreenLines, Screen } from "../../../model/model";
import { Line } from "../../line/linecomps"; import { Line } from "../../line/linecomps";
import { LinesView } from "../../line/linesview"; import { LinesView } from "../../line/linesview";
import { ScreenSettingsModal } from "../../common/modals/settings";
import "./screenview.less"; import "./screenview.less";
import "./tabs.less"; import "./tabs.less";
@ -181,11 +184,7 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
if (cdata == null) { if (cdata == null) {
return this.renderError("loading client data", true); 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 isActive = screen.isActive();
let selectedLine = screen.getSelectedLine();
let lines = win.getNonArchivedLines(); let lines = win.getNonArchivedLines();
let renderMode = this.renderMode.get(); let renderMode = this.renderMode.get();
return ( return (
@ -204,6 +203,14 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
</If> </If>
</div> </div>
</div> </div>
<If condition={lines.length == 0}>
<ScreenSettingsModal
key={screen.sessionId + ":" + screen.screenId}
sessionId={screen.sessionId}
screenId={screen.screenId}
inline={true}
/>
</If>
<If condition={screen.isWebShared()}> <If condition={screen.isWebShared()}>
<div key="share-tag" className="share-tag"> <div key="share-tag" className="share-tag">
<If condition={this.shareCopied.get()}> <If condition={this.shareCopied.get()}>
@ -240,13 +247,6 @@ class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
lineFactory={this.buildLineComponent} lineFactory={this.buildLineComponent}
/> />
</If> </If>
<If condition={lines.length == 0}>
<div key="window-empty" className="window-empty">
<div>
<code>[workspace="{session.name.get()}" screen="{screen.name.get()}"]</code>
</div>
</div>
</If>
</div> </div>
); );
} }

View File

@ -194,11 +194,6 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
<AddIcon className="icon hoverEffect" /> <AddIcon className="icon hoverEffect" />
</div> </div>
</div> </div>
{/**<div className="cmd-hints">
<div className="hint-item color-green">move left {renderCmdText("[")}</div>
<div className="hint-item color-green">move right {renderCmdText("]")}</div>
<div className="hint-item color-green">new tab {renderCmdText("T")}</div>
</div>*/}
</div> </div>
); );
} }

View File

@ -75,6 +75,7 @@ import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat"; import localizedFormat from "dayjs/plugin/localizedFormat";
import customParseFormat from "dayjs/plugin/customParseFormat"; import customParseFormat from "dayjs/plugin/customParseFormat";
import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil"; import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil";
import { sortAndFilterRemotes } from "../util/util";
dayjs.extend(customParseFormat); dayjs.extend(customParseFormat);
dayjs.extend(localizedFormat); dayjs.extend(localizedFormat);
@ -2426,6 +2427,9 @@ class RemotesModalModel {
openState: OV<boolean> = mobx.observable.box(false, { openState: OV<boolean> = mobx.observable.box(false, {
name: "RemotesModalModel-isOpen", name: "RemotesModalModel-isOpen",
}); });
onlyAddNewRemote: OV<boolean> = mobx.observable.box(false, {
name: "RemotesModalModel-onlyAddNewRemote",
});
selectedRemoteId: OV<string> = mobx.observable.box(null, { selectedRemoteId: OV<string> = mobx.observable.box(null, {
name: "RemotesModalModel-selectedRemoteId", name: "RemotesModalModel-selectedRemoteId",
}); });
@ -2467,8 +2471,9 @@ class RemotesModalModel {
})(); })();
} }
openModalForEdit(redit: RemoteEditType): void { openModalForEdit(redit: RemoteEditType, onlyAddNewRemote: boolean): void {
mobx.action(() => { mobx.action(() => {
this.onlyAddNewRemote.set(onlyAddNewRemote);
this.openState.set(true); this.openState.set(true);
this.selectedRemoteId.set(redit.remoteid); this.selectedRemoteId.set(redit.remoteid);
this.remoteEdit.set(redit); this.remoteEdit.set(redit);
@ -2497,6 +2502,11 @@ class RemotesModalModel {
cancelEditAuth(): void { cancelEditAuth(): void {
mobx.action(() => { mobx.action(() => {
this.remoteEdit.set(null); this.remoteEdit.set(null);
if (this.onlyAddNewRemote.get()) {
this.onlyAddNewRemote.set(false);
this.openState.set(false);
return;
}
if (this.selectedRemoteId.get() == null) { if (this.selectedRemoteId.get() == null) {
this.openModal(); this.openModal();
} }
@ -2519,6 +2529,7 @@ class RemotesModalModel {
this.openState.set(false); this.openState.set(false);
this.selectedRemoteId.set(null); this.selectedRemoteId.set(null);
this.remoteEdit.set(null); this.remoteEdit.set(null);
this.onlyAddNewRemote.set(false);
})(); })();
setTimeout(() => GlobalModel.refocus(), 10); setTimeout(() => GlobalModel.refocus(), 10);
} }
@ -3204,7 +3215,7 @@ class Model {
if (rview.remoteshowall) { if (rview.remoteshowall) {
this.remotesModalModel.openModal(); this.remotesModalModel.openModal();
} else if (rview.remoteedit != null) { } else if (rview.remoteedit != null) {
this.remotesModalModel.openModalForEdit(rview.remoteedit); this.remotesModalModel.openModalForEdit(rview.remoteedit, false);
} else if (rview.ptyremoteid) { } else if (rview.ptyremoteid) {
this.remotesModalModel.openModal(rview.ptyremoteid); this.remotesModalModel.openModal(rview.ptyremoteid);
} }
@ -3441,7 +3452,15 @@ class Model {
uicontext: this.getUIContext(), uicontext: this.getUIContext(),
interactive: interactive, 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); return this.submitCommandPacket(pk, interactive);
} }
@ -3455,7 +3474,7 @@ class Model {
interactive: interactive, interactive: interactive,
rawstr: cmdStr, rawstr: cmdStr,
}; };
if (!addToHistory) { if (!addToHistory && pk.kwargs) {
pk.kwargs["nohist"] = "1"; pk.kwargs["nohist"] = "1";
} }
return this.submitCommandPacket(pk, interactive); return this.submitCommandPacket(pk, interactive);
@ -3810,14 +3829,28 @@ class CommandRunner {
GlobalModel.submitCommand("remote", "installcancel", null, { nohist: "1", remote: remoteid }, true); GlobalModel.submitCommand("remote", "installcancel", null, { nohist: "1", remote: remoteid }, true);
} }
createRemote(cname: string, kwargsArg: Record<string, string>) { createRemote(cname: string, kwargsArg: Record<string, string>, interactive: boolean): Promise<CommandRtnType> {
let kwargs = Object.assign({}, kwargsArg); let kwargs = Object.assign({}, kwargsArg);
kwargs["nohist"] = "1"; kwargs["nohist"] = "1";
GlobalModel.submitCommand("remote", "new", [cname], kwargs, true); return GlobalModel.submitCommand("remote", "new", [cname], kwargs, interactive);
} }
openCreateRemote(): void { 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<CommandRtnType> {
let kwargs = {};
if (nohist) {
kwargs["nohist"] = "1";
}
return GlobalModel.submitCommand("connect", null, [remoteArg], kwargs, interactive);
} }
editRemote(remoteid: string, kwargsArg: Record<string, string>): void { editRemote(remoteid: string, kwargsArg: Record<string, string>): void {

View File

@ -21,6 +21,7 @@ import (
"time" "time"
"unicode" "unicode"
"github.com/google/uuid"
"github.com/wavetermdev/waveterm/waveshell/pkg/base" "github.com/wavetermdev/waveterm/waveshell/pkg/base"
"github.com/wavetermdev/waveterm/waveshell/pkg/packet" "github.com/wavetermdev/waveterm/waveshell/pkg/packet"
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec" "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/scpacket"
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore" "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
"github.com/wavetermdev/waveterm/wavesrv/pkg/utilfn" "github.com/wavetermdev/waveterm/wavesrv/pkg/utilfn"
"github.com/google/uuid"
) )
const ( const (
@ -1116,7 +1116,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
} }
editArgs, err := parseRemoteEditArgs(true, pk, false) editArgs, err := parseRemoteEditArgs(true, pk, false)
if err != nil { if err != nil {
return makeRemoteEditErrorReturn_new(visualEdit, fmt.Errorf("/remote:new %v", err)) return nil, fmt.Errorf("/remote:new %v", err)
} }
r := &sstore.RemoteType{ r := &sstore.RemoteType{
RemoteId: scbase.GenPromptUUID(), RemoteId: scbase.GenPromptUUID(),
@ -1134,7 +1134,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
} }
err = remote.AddRemote(ctx, r, true) err = remote.AddRemote(ctx, r, true)
if err != nil { 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 // SUCCESS
return &sstore.ModelUpdate{ return &sstore.ModelUpdate{
@ -1580,6 +1580,18 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up
if err != nil { if err != nil {
return nil, fmt.Errorf("/%s error: cannot update curremote: %w", GetCmdStr(pk), err) 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)) outputStr := fmt.Sprintf("connected to %s", GetFullRemoteDisplayName(rptr, rstate))
cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr)) cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr))
if err != nil { if err != nil {

View File

@ -8,7 +8,7 @@ module.exports = {
prompt: ["./src/index.ts", "./src/app/app.less"], prompt: ["./src/index.ts", "./src/app/app.less"],
}, },
output: { output: {
path: path.resolve(__dirname, "dist"), path: path.resolve(__dirname, "../dist"),
filename: "[name].js", filename: "[name].js",
}, },
module: { module: {

View File

@ -16,7 +16,7 @@ const BUILD = makeBuildStr();
let merged = merge.merge(common, { let merged = merge.merge(common, {
mode: "production", mode: "production",
output: { output: {
path: path.resolve(__dirname, "dist"), path: path.resolve(__dirname, "../dist"),
filename: "[name].js", filename: "[name].js",
}, },
devtool: "source-map", devtool: "source-map",

View File

@ -21,7 +21,7 @@ if (process.env.WEBPACK_ANALYZE) {
let merged = merge.merge(common, { let merged = merge.merge(common, {
mode: "production", mode: "production",
output: { output: {
path: path.resolve(__dirname, "dist"), path: path.resolve(__dirname, "../dist"),
filename: "[name].js", filename: "[name].js",
}, },
devtool: false, devtool: false,