modals system (#106)

* init

* connections table

* view styles

* new components. header and status.

* action buttons

* use Button component in other modals

* hook add connection button

* RemoteConnDetailModal component

* refactor remotes model. read connection modal.

* remote conn detail modal layout and styles

* fix xterm styles

* use correct status message in xterm

* tone down color of settings input

* clean up

* edit remote conn modal

* fix buttons gap

* change button label

* archive and force install features

* use classnames

* add some class names and also set some widths / maxwidth for the table.  too hard to read on large screens.

* small style updates

* fix some typescript errors, other small fixups

* fix type error

* move add button to the bottom of the table

* more improvements

* adjust layout, behavior, and style accrdg to mike's feedback

* set table max-width in css

* open detail modal after creation of new remote

* new modal component. migrate about modal to new modal component.

* migrate create remote conn modal to modal component

* working modals stack

* update some working (remote -> connection).  fix typescript error in connections.  remove some console.logs

* fix a couple of mobx warnings (need to wrap in action)

* register create conn modal

* follow model naming convention

* register edit remote conn modal

* reset

* reset

* reset

* reset

* use remotes model methods and wrap pushModal calls in mobx action

* only close connect modal after update for remotes returns

* register alert modal

* fix type error in app.tsx

* migrate remote detail and alert modal to base modal component

* Revert "fix conflicts"

This reverts commit 962da77918, reversing
changes made to 34cbe34ba5.

* only wrapper ModalProvider with mobx provider

* change archive label to delete

* fix error where isOpen method does not exist

* remove registry modal

* rename ModalStoreModel to ModalsModal

* fix issue where edit remote conn modal doesn't show

* simplify modal component

* grab remoteModel from within the remote modals

* fix edit modal

* minor change

* cleanup

* more cleanup

* change confirm wording to 'delete' instead of 'archive'.  remove or-equals since isBlank is designed to check for exactly that.

* undo some of the strict typescript fixes

* undo more typescript fixes

* cleanup

* fix import

* revert build.md change
This commit is contained in:
Red J Adaya 2023-12-02 12:04:59 +08:00 committed by GitHub
parent fc79da776c
commit 23b6bb29e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 954 additions and 955 deletions

View File

@ -22,18 +22,9 @@ import {
LineSettingsModal,
ClientSettingsModal,
} from "./common/modals/settings";
import { RemotesModal } from "./connections_deprecated/connections";
import { TosModal } from "./common/modals/modals";
import { MainSideBar } from "./sidebar/sidebar";
import {
DisconnectedModal,
ClientStopModal,
AlertModal,
AboutModal,
CreateRemoteConnModal,
ViewRemoteConnDetailModal,
EditRemoteConnModal,
} from "./common/modals/modals";
import { DisconnectedModal, ClientStopModal, ModalsProvider } from "./common/modals/modals";
import { ErrorBoundary } from "./common/error/errorboundary";
import "./app.less";
@ -67,7 +58,7 @@ class App extends React.Component<{}, {}> {
opts.showCut = true;
}
let sel = window.getSelection();
if (!isBlank(sel.toString())) {
if (!isBlank(sel?.toString())) {
GlobalModel.contextEditMenu(e, opts);
} else {
if (isInNonTermInput) {
@ -89,11 +80,6 @@ class App extends React.Component<{}, {}> {
let lineSettingsModal = GlobalModel.lineSettingsModal.get();
let clientSettingsModal = GlobalModel.clientSettingsModal.get();
let remotesModel = GlobalModel.remotesModel;
let remotesModalMode = remotesModel.modalMode.get();
let selectedRemoteId = remotesModel.selectedRemoteId.get();
let selectedRemote = GlobalModel.getRemote(selectedRemoteId);
let isAuthEditMode = remotesModel.isAuthEditMode();
let remoteEdit = remotesModel.remoteEdit.get();
let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get();
let hasClientStop = GlobalModel.getHasClientStop();
let dcWait = this.dcWait.get();
@ -135,33 +121,10 @@ class App extends React.Component<{}, {}> {
<ConnectionsView model={remotesModel} />
</ErrorBoundary>
</div>
<AlertModal />
<If condition={GlobalModel.needsTos()}>
<TosModal />
</If>
<If condition={GlobalModel.aboutModalOpen.get()}>
<AboutModal />
</If>
<If condition={remoteEdit !== null && remotesModalMode === "add"}>
<CreateRemoteConnModal model={remotesModel} remoteEdit={remoteEdit} />
</If>
<If condition={selectedRemote != null}>
<If condition={!isAuthEditMode && remotesModalMode === "read"}>
<ViewRemoteConnDetailModal
key={"remotedetail-" + selectedRemoteId}
remote={selectedRemote}
model={remotesModel}
/>
</If>
<If condition={remoteEdit !== null && isAuthEditMode && remotesModalMode === "edit"}>
<EditRemoteConnModal
key={"remotedetail-" + selectedRemoteId}
remote={selectedRemote}
model={remotesModel}
remoteEdit={remoteEdit}
/>
</If>
</If>
<ModalsProvider />
<If condition={screenSettingsModal != null}>
<ScreenSettingsModal
key={screenSettingsModal.sessionId + ":" + screenSettingsModal.screenId}

5
src/app/appconst.ts Normal file
View File

@ -0,0 +1,5 @@
export const ABOUT = "about";
export const CREATE_REMOTE = "createRemote";
export const VIEW_REMOTE = "viewRemote";
export const EDIT_REMOTE = "editRemote";
export const ALERT = "alert";

View File

@ -1045,3 +1045,76 @@
background-color: @status-connecting;
}
}
.wave-modal-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 500;
.wave-modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(21, 23, 21, 0.7);
z-index: 1;
}
}
.wave-modal {
z-index: 2;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
border-radius: 10px;
background: #151715;
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45),
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
.wave-modal-content {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
.wave-modal-header {
width: 100%;
display: flex;
align-items: center;
padding: 12px 14px 12px 20px;
justify-content: space-between;
line-height: 20px;
border-bottom: 1px solid rgba(250, 250, 250, 0.1);
button {
i {
font-size: 18px;
}
}
}
.wave-modal-body {
width: 100%;
padding: 0px 20px;
}
.wave-modal-footer {
display: flex;
justify-content: flex-end;
width: 100%;
padding: 0 20px 20px;
button:first-child {
margin-right: 8px;
}
}
}
}

View File

@ -349,7 +349,6 @@ interface TextFieldState {
hasContent: boolean;
}
@mobxReact.observer
class TextField extends React.Component<TextFieldProps, TextFieldState> {
inputRef: React.RefObject<HTMLInputElement>;
state: TextFieldState;
@ -1097,6 +1096,68 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
}
}
interface ModalHeaderProps {
onClose: () => void;
title: string;
}
const ModalHeader: React.FC<ModalHeaderProps> = ({ onClose, title }) => (
<div className="wave-modal-header">
{<div>{title}</div>}
<IconButton theme="secondary" variant="ghost" onClick={onClose}>
<i className="fa-sharp fa-solid fa-xmark"></i>
</IconButton>
</div>
);
interface ModalFooterProps {
onCancel?: () => void;
onOk?: () => void;
cancelLabel?: string;
okLabel?: string;
}
const ModalFooter: React.FC<ModalFooterProps> = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok" }) => (
<div className="wave-modal-footer">
<Button theme="secondary" onClick={onCancel}>
{cancelLabel}
</Button>
<Button onClick={onOk}>{okLabel}</Button>
</div>
);
interface ModalProps {
className?: string;
children?: React.ReactNode;
onClickBackdrop?: () => void;
}
class Modal extends React.Component<ModalProps> {
static Header = ModalHeader;
static Footer = ModalFooter;
renderBackdrop(onClick: (() => void) | undefined) {
return <div className="wave-modal-backdrop" onClick={onClick}></div>;
}
renderModal() {
const { className, children } = this.props;
return (
<div className="wave-modal-container">
{this.renderBackdrop(this.props.onClickBackdrop)}
<div className={`wave-modal ${className}`}>
<div className="wave-modal-content">{children}</div>
</div>
</div>
);
}
render() {
return ReactDOM.createPortal(this.renderModal(), document.getElementById("app") as HTMLElement);
}
}
export {
CmdStrCode,
Toggle,
@ -1117,4 +1178,5 @@ export {
IconButton,
LinkButton,
Status,
Modal,
};

View File

@ -59,22 +59,6 @@
}
}
.modal.alert-modal {
z-index: 205;
footer {
justify-content: center;
.button {
margin-left: 20px;
}
.button:first-child {
margin-left: 0;
}
}
}
.modal.settings-modal {
footer {
justify-content: center;
@ -181,59 +165,6 @@
}
}
.modal.wave-modal {
.wave-modal-content {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
border-radius: 10px;
background: var(--olive-dark-1, #151715);
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45),
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
.wave-modal-content-inner {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
width: 100%;
.wave-modal-header {
width: 100%;
display: flex;
align-items: center;
padding: 12px 20px;
justify-content: space-between;
line-height: 20px;
border-bottom: 1px solid rgba(250, 250, 250, 0.1);
.wave-modal-title {
color: @term-bright-white;
font-style: normal;
line-height: 20px;
font-size: 13px;
}
.wave-modal-close {
display: flex;
padding: 4px;
align-items: center;
gap: 8px;
}
}
.wave-modal-body {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 24px;
width: 87%;
}
}
}
}
.modal.tos-modal {
.modal-content.wave-modal-content {
padding: 32px 48px;
@ -323,14 +254,26 @@
}
}
.modal.about-modal {
.about-wave-modal-content {
width: 401px;
.about-modal {
width: 382px;
.about-wave-modal-body {
.wave-modal-content {
gap: 24px;
.wave-modal-body {
margin-bottom: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 24px;
.about-section {
display: flex;
align-items: center;
gap: 16px;
align-self: stretch;
width: 100%;
.wave-modal-section {
.logo-wrapper {
width: 72px;
height: 72px;
@ -403,7 +346,7 @@
}
}
.wave-modal-section:nth-child(3) {
.about-section:nth-child(3) {
display: flex;
align-items: flex-start;
gap: 10px;
@ -418,7 +361,7 @@
}
}
.wave-modal-section:last-child {
.about-section:last-child {
margin-bottom: 24px;
color: @term-white;
}
@ -426,21 +369,14 @@
}
}
.wave-modal.crconn-modal {
.wave-modal-content.crconn-wave-modal-content {
.crconn-modal {
width: 452px;
min-height: 411px;
overflow: visible;
.wave-modal-content-inner.crconn-wave-modal-content-inner {
display: flex;
padding-bottom: 0px;
flex-direction: column;
align-items: center;
gap: 20px;
flex-shrink: 0;
.wave-modal-content {
gap: 24px;
.crconn-wave-modal-body {
.wave-modal-body {
display: flex;
padding: 0px 20px;
flex-direction: column;
@ -450,31 +386,71 @@
width: 100%;
}
}
}
.crconn-wave-modal-footer {
.erconn-modal {
width: 502px;
min-height: 411px;
.wave-modal-content {
gap: 20px;
.wave-modal-body {
display: flex;
padding: 0px 20px;
flex-direction: column;
align-items: flex-start;
gap: 12px;
align-self: stretch;
width: 100%;
> div {
width: 100%;
}
.name-actions-section {
margin-bottom: 10px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
.name {
color: @term-bright-white;
font-size: 15px;
font-weight: 500;
line-height: 20px;
}
.header-actions {
display: flex;
justify-content: flex-end;
width: 100%;
padding: 0 20px 20px;
align-items: flex-start;
.action-buttons {
display: flex;
button:first-child {
.wave-button {
padding: 4px 15px;
font-size: 11px;
margin-right: 8px;
}
}
}
}
}
}
.wave-modal.rconndetail-modal {
.wave-modal-content.rconndetail-wave-modal-content {
.alert-modal {
.wave-modal-content {
.wave-modal-body {
padding: 40px 20px;
}
}
}
.rconndetail-modal {
width: 631px;
min-height: 565px;
overflow: visible;
.wave-modal-content-inner.rconndetail-wave-modal-content-inner {
.wave-modal-content {
display: flex;
padding-bottom: 0px;
flex-direction: column;
@ -482,7 +458,7 @@
gap: 20px;
flex-shrink: 0;
.rconndetail-wave-modal-body {
.wave-modal-body {
display: flex;
padding: 0px 20px;
align-items: flex-start;
@ -583,95 +559,6 @@
}
}
}
.rconndetail-wave-modal-footer {
display: flex;
justify-content: flex-end;
width: 100%;
padding: 0 20px 20px;
.action-buttons {
display: flex;
button:first-child {
margin-right: 8px;
}
}
}
}
}
.wave-modal.erconn-modal {
.wave-modal-content.erconn-wave-modal-content {
width: 502px;
min-height: 411px;
overflow: visible;
.wave-modal-content-inner.erconn-wave-modal-content-inner {
display: flex;
padding-bottom: 0px;
flex-direction: column;
align-items: center;
gap: 20px;
flex-shrink: 0;
.erconn-wave-modal-body {
display: flex;
padding: 0px 20px;
flex-direction: column;
align-items: flex-start;
gap: 12px;
align-self: stretch;
width: 100%;
> div {
width: 100%;
}
.name-actions-section {
margin-bottom: 10px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
.name {
color: @term-bright-white;
font-size: 15px;
font-weight: 500;
line-height: 20px;
}
.header-actions {
display: flex;
justify-content: flex-end;
align-items: flex-start;
.wave-button {
padding: 4px 15px;
font-size: 11px;
margin-right: 8px;
}
}
}
}
}
.erconn-wave-modal-footer {
display: flex;
justify-content: flex-end;
width: 100%;
padding: 0 20px 20px;
.action-buttons {
display: flex;
button:first-child {
margin-right: 8px;
}
}
}
}
}
.wave-button.color-standard {

View File

@ -11,21 +11,18 @@ import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../model/model";
import * as T from "../../../types/types";
import { Markdown, InfoMessage } from "../common";
import { Markdown } from "../common";
import * as util from "../../../util/util";
import * as textmeasure from "../../../util/textmeasure";
import { Toggle, Checkbox } from "../common";
import { Toggle, Modal } from "../common";
import { ClientDataType } from "../../../types/types";
import { TextField, NumberField, InputDecoration, Dropdown, PasswordField, Tooltip, Button, Status } from "../common";
import close from "../../assets/icons/close.svg";
import { ReactComponent as WarningIcon } from "../../assets/icons/line/triangle-exclamation.svg";
import { ReactComponent as XmarkIcon } from "../../assets/icons/line/xmark.svg";
import shield from "../../assets/icons/shield_check.svg";
import help from "../../assets/icons/help_filled.svg";
import github from "../../assets/icons/github.svg";
import logo from "../../assets/waveterm-logo-with-bg.svg";
import { ReactComponent as AngleDownIcon } from "../../assets/icons/history/angle-down.svg";
dayjs.extend(localizedFormat);
@ -40,6 +37,19 @@ const RemotePtyRows = 9;
const RemotePtyCols = 80;
const PasswordUnchangedSentinel = "--unchanged--";
@mobxReact.observer
class ModalsProvider extends React.Component {
renderModals() {
const modals = GlobalModel.modalsModel.activeModals;
return modals.map((ModalComponent, index) => <ModalComponent key={index} />);
}
render() {
return <>{this.renderModals()}</>;
}
}
@mobxReact.observer
class DisconnectedModal extends React.Component<{}, {}> {
logRef: any = React.createRef();
@ -188,49 +198,30 @@ class AlertModal extends React.Component<{}, {}> {
render() {
let message = GlobalModel.alertMessage.get();
if (message == null) {
return null;
}
let title = message.title ?? (message.confirm ? "Confirm" : "Alert");
let isConfirm = message.confirm;
let title = message?.title ?? (message?.confirm ? "Confirm" : "Alert");
let isConfirm = message?.confirm ?? false;
return (
<div className="modal prompt-modal is-active alert-modal">
<div className="modal-background" />
<div className="modal-content">
<header>
<p className="modal-title">
<WarningIcon className="icon" />
{title}
</p>
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
<XmarkIcon />
</div>
</header>
<If condition={message.markdown}>
<Markdown text={message.message} extraClassName="inner-content" />
<Modal className="alert-modal">
<Modal.Header onClose={this.closeModal} title={title} />
<div className="wave-modal-body">
<If condition={message?.markdown}>
<Markdown text={message?.message ?? ""} />
</If>
<If condition={!message.markdown}>
<div className="inner-content content">
<p>{message.message}</p>
<If condition={!message?.markdown}>{message?.message}</If>
</div>
</If>
<footer>
<div className="wave-modal-footer">
<If condition={isConfirm}>
<div onClick={this.closeModal} className="button is-prompt-cancel is-outlined is-small">
<Button theme="secondary" onClick={this.closeModal}>
Cancel
</div>
<div onClick={this.handleOK} className="button is-wave-green is-outlined is-small">
OK
</div>
</Button>
<Button onClick={this.handleOK}>Ok</Button>
</If>
<If condition={!isConfirm}>
<div onClick={this.handleOK} className="button is-wave-green is-small">
OK
</div>
<Button onClick={this.handleOK}>Ok</Button>
</If>
</footer>
</div>
</div>
</Modal>
);
}
}
@ -342,7 +333,7 @@ class AboutModal extends React.Component<{}, {}> {
@boundMethod
closeModal(): void {
mobx.action(() => {
GlobalModel.aboutModalOpen.set(false);
GlobalModel.modalsModel.popModal();
})();
}
@ -400,18 +391,10 @@ class AboutModal extends React.Component<{}, {}> {
render() {
return (
<div className={cn("modal about-modal wave-modal is-active")}>
<div className="modal-background wave-modal-background" />
<div className="modal-content wave-modal-content about-wave-modal-content">
<div className="modal-content-inner wave-modal-content-inner about-wave-modal-content-inner">
<header className="wave-modal-header about-wave-modal-header">
<div className="wave-modal-title about-wave-modal-title">About</div>
<div className="wave-modal-close about-wave-modal-close" onClick={this.closeModal}>
<img src={close} alt="Close (Escape)" />
</div>
</header>
<div className="wave-modal-body about-wave-modal-body">
<section className="wave-modal-section about-section">
<Modal className="about-modal">
<Modal.Header onClose={this.closeModal} title="About" />
<div className="wave-modal-body">
<div className="about-section">
<div className="logo-wrapper">
<img src={logo} alt="logo" />
</div>
@ -423,11 +406,9 @@ class AboutModal extends React.Component<{}, {}> {
Seamless Workflow
</div>
</div>
</section>
<section className="wave-modal-section about-section text-standard">
{this.getStatus(this.isUpToDate())}
</section>
<section className="wave-modal-section about-section">
</div>
<div className="about-section text-standard">{this.getStatus(this.isUpToDate())}</div>
<div className="about-section">
<a
className="wave-button wave-button-link color-standard"
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
@ -446,28 +427,22 @@ class AboutModal extends React.Component<{}, {}> {
</a>
<a
className="wave-button wave-button-link color-standard"
href={util.makeExternLink(
"https://github.com/wavetermdev/waveterm/blob/main/LICENSE",
)}
href={util.makeExternLink("https://github.com/wavetermdev/waveterm/blob/main/LICENSE")}
target="_blank"
>
<i className="fa-sharp fa-light fa-book-blank"></i>
License
</a>
</section>
<section className="wave-modal-section about-section text-standard">
&copy; 2023 Command Line Inc.
</section>
</div>
</div>
</div>
<div className="about-section text-standard">&copy; 2023 Command Line Inc.</div>
</div>
</Modal>
);
}
}
@mobxReact.observer
class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remoteEdit: T.RemoteEditType }, {}> {
class CreateRemoteConnModal extends React.Component<{}, {}> {
tempAlias: OV<string>;
tempHostName: OV<string>;
tempPort: OV<string>;
@ -476,10 +451,13 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
tempPassword: OV<string>;
tempKeyFile: OV<string>;
errorStr: OV<string>;
remoteEdit: T.RemoteEditType;
model: RemotesModel;
constructor(props: any) {
constructor(props: { remotesModel?: RemotesModel }) {
super(props);
let { remoteEdit } = this.props;
this.model = GlobalModel.remotesModel;
this.remoteEdit = this.model.remoteEdit.get();
this.tempAlias = mobx.observable.box("", { name: "CreateRemote-alias" });
this.tempHostName = mobx.observable.box("", { name: "CreateRemote-hostName" });
this.tempPort = mobx.observable.box("", { name: "CreateRemote-port" });
@ -487,7 +465,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
this.tempConnectMode = mobx.observable.box("auto", { name: "CreateRemote-connectMode" });
this.tempKeyFile = mobx.observable.box("", { name: "CreateRemote-keystr" });
this.tempPassword = mobx.observable.box("", { name: "CreateRemote-password" });
this.errorStr = mobx.observable.box(remoteEdit.errorstr, { name: "CreateRemote-errorStr" });
this.errorStr = mobx.observable.box(this.remoteEdit?.errorstr ?? null, { name: "CreateRemote-errorStr" });
}
remoteCName(): string {
@ -505,7 +483,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
if (this.errorStr.get() != null) {
return this.errorStr.get();
}
return this.props.remoteEdit.errorstr;
return this.remoteEdit?.errorstr ?? null;
}
@boundMethod
@ -545,7 +523,7 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
kwargs["connectmode"] = this.tempConnectMode.get();
kwargs["visual"] = "1";
kwargs["submit"] = "1";
let model = this.props.model;
let model = this.model;
let prtn = GlobalCommandRunner.createRemote(cname, kwargs, false);
prtn.then((crtn) => {
if (crtn.success) {
@ -555,13 +533,13 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
return;
}
mobx.action(() => {
this.errorStr.set(crcrtn.error);
this.errorStr.set(crcrtn.error ?? null);
})();
});
return;
}
mobx.action(() => {
this.errorStr.set(crtn.error);
this.errorStr.set(crtn.error ?? null);
})();
});
model.seRecentConnAdded(true);
@ -617,21 +595,16 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
}
render() {
let { model } = this.props;
let authMode = this.tempAuthMode.get();
if (this.remoteEdit == null) {
return null;
}
return (
<div className={cn("modal wave-modal crconn-modal is-active")}>
<div className="modal-background wave-modal-background" />
<div className="modal-content wave-modal-content crconn-wave-modal-content">
<div className="wave-modal-content-inner crconn-wave-modal-content-inner">
<header className="wave-modal-header crconn-wave-modal-header">
<div className="wave-modal-title crconn-wave-modal-title">Add Connection</div>
<div className="wave-modal-close crconn-wave-modal-close" onClick={model.closeModal}>
<img src={close} alt="Close (Escape)" />
</div>
</header>
<div className="wave-modal-body crconn-wave-modal-body">
<Modal className="crconn-modal">
<Modal.Header title="Add Connection" onClose={this.model.closeModal} />
<div className="wave-modal-body">
<div className="user-section">
<TextField
label="user@host"
@ -705,7 +678,9 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
{ value: "key+password", label: "key+password" },
]}
value={this.tempAuthMode.get()}
onChange={this.handleChangeAuthMode}
onChange={(val: string) => {
this.tempAuthMode.set(val);
}}
decoration={{
endDecoration: (
<InputDecoration>
@ -713,8 +688,8 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
message={
<ul>
<li>
<b>none</b> - no authentication, or authentication is
already configured in your ssh config.
<b>none</b> - no authentication, or authentication is already
configured in your ssh config.
</li>
<li>
<b>key</b> - use a private key.
@ -777,31 +752,36 @@ class CreateRemoteConnModal extends React.Component<{ model: RemotesModel; remot
{ value: "manual", label: "manual" },
]}
value={this.tempConnectMode.get()}
onChange={this.handleChangeConnectMode}
onChange={(val: string) => {
this.tempConnectMode.set(val);
}}
/>
</div>
<If condition={!util.isBlank(this.getErrorStr())}>
<If condition={!util.isBlank(this.getErrorStr() as string)}>
<div className="settings-field settings-error">Error: {this.getErrorStr()}</div>
</If>
</div>
<footer className="wave-modal-footer crconn-wave-modal-footer">
<div className="action-buttons">
<Button theme="secondary" onClick={model.closeModal}>
Cancel
</Button>
<Button onClick={this.submitRemote}>Connect</Button>
</div>
</footer>
</div>
</div>
</div>
<Modal.Footer onCancel={this.model.closeModal} onOk={this.submitRemote} okLabel="Connect" />
</Modal>
);
}
}
@mobxReact.observer
class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; remote: T.RemoteType }, {}> {
class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
termRef: React.RefObject<any> = React.createRef();
model: RemotesModel;
constructor(props: { remotesModel?: RemotesModel }) {
super(props);
this.model = GlobalModel.remotesModel;
}
@mobx.computed
get selectedRemote(): T.RemoteType {
const selectedRemoteId = this.model.selectedRemoteId.get();
return GlobalModel.getRemote(selectedRemoteId);
}
componentDidMount() {
let elem = this.termRef.current;
@ -809,24 +789,23 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
console.log("ERROR null term-remote element");
return;
}
this.props.model.createTermWrap(elem);
this.model.createTermWrap(elem);
}
componentDidUpdate() {
let { remote } = this.props;
if (remote == null || remote.archived) {
this.props.model.deSelectRemote();
if (this.selectedRemote == null || this.selectedRemote.archived) {
this.model.deSelectRemote();
}
}
componentWillUnmount() {
this.props.model.disposeTerm();
this.model.disposeTerm();
}
@boundMethod
clickTermBlock(): void {
if (this.props.model.remoteTermWrap != null) {
this.props.model.remoteTermWrap.giveFocus();
if (this.model.remoteTermWrap != null) {
this.model.remoteTermWrap.giveFocus();
}
}
@ -861,7 +840,7 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
@boundMethod
openEditModal(): void {
this.props.model.openEditModal();
GlobalModel.remotesModel.openEditModal();
}
@boundMethod
@ -878,9 +857,8 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
@boundMethod
clickArchive(): void {
let { remote } = this.props;
if (remote.status == "connected") {
GlobalModel.showAlert({ message: "Cannot delete a connected connection. Disconnect and try again." });
if (this.selectedRemote && this.selectedRemote.status == "connected") {
GlobalModel.showAlert({ message: "Cannot delete when connected. Disconnect and try again." });
return;
}
let prtn = GlobalModel.showAlert({
@ -891,15 +869,16 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
if (!confirm) {
return;
}
GlobalCommandRunner.archiveRemote(remote.remoteid);
if (this.selectedRemote) {
GlobalCommandRunner.archiveRemote(this.selectedRemote.remoteid);
}
});
}
@boundMethod
handleClose(): void {
let { model } = this.props;
model.closeModal();
model.seRecentConnAdded(false);
this.model.closeModal();
this.model.seRecentConnAdded(false);
}
renderInstallStatus(remote: T.RemoteType): any {
@ -1023,24 +1002,22 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
}
render() {
let { model, remote } = this.props;
let isTermFocused = model.remoteTermWrapFocus.get();
let remote = this.selectedRemote;
if (remote == null) {
return null;
}
let model = this.model;
let isTermFocused = this.model.remoteTermWrapFocus.get();
let termFontSize = GlobalModel.termFontSize.get();
let termWidth = textmeasure.termWidthFromCols(RemotePtyCols, termFontSize);
let remoteAliasText = util.isBlank(remote.remotealias) ? "(none)" : remote.remotealias;
return (
<div className={cn("modal wave-modal rconndetail-modal is-active")}>
<div className="modal-background wave-modal-background" />
<div className="modal-content wave-modal-content rconndetail-wave-modal-content">
<div className="wave-modal-content-inner rconndetail-wave-modal-content-inner">
<header className="wave-modal-header rconndetail-wave-modal-header">
<div className="wave-modal-title rconndetail-wave-modal-title">Connection</div>
<div className="wave-modal-close rconndetail-wave-modal-close" onClick={model.closeModal}>
<img src={close} alt="Close (Escape)" />
</div>
</header>
<div className="wave-modal-body rconndetail-wave-modal-body">
<Modal className="rconndetail-modal">
<Modal.Header title="Connection" onClose={this.model.closeModal} />
<div className="wave-modal-body">
<div className="name-header-actions-wrapper">
<div className="name text-primary">{getName(remote)}</div>
<div className="header-actions">{this.renderHeaderBtns(remote)}</div>
@ -1058,11 +1035,7 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
<div className="settings-label">Canonical Name</div>
<div className="settings-input">
{remote.remotecanonicalname}
<If
condition={
!util.isBlank(remote.remotevars.port) && remote.remotevars.port != "22"
}
>
<If condition={!util.isBlank(remote.remotevars.port) && remote.remotevars.port != "22"}>
<span style={{ marginLeft: 5 }}>(port {remote.remotevars.port})</span>
</If>
</div>
@ -1092,7 +1065,7 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
className={cn(
"terminal-wrapper",
{ focus: isTermFocused },
remote != null ? "status-" + remote.status : null,
remote != null ? "status-" + remote.status : null
)}
>
<If condition={!isTermFocused}>
@ -1116,121 +1089,141 @@ class ViewRemoteConnDetailModal extends React.Component<{ model: RemotesModel; r
</div>
</div>
</div>
<footer className="wave-modal-footer rconndetail-wave-modal-footer">
<div className="action-buttons">
<Button theme="secondary" onClick={model.closeModal}>
Cancel
</Button>
<Button onClick={model.closeModal}>Done</Button>
</div>
</footer>
</div>
</div>
</div>
<Modal.Footer onOk={this.model.closeModal} onCancel={this.model.closeModal} okLabel="Done" />
</Modal>
);
}
}
@mobxReact.observer
class EditRemoteConnModal extends React.Component<
{ model: RemotesModel; remote: T.RemoteType; remoteEdit: T.RemoteEditType },
{}
> {
tempAlias: OV<string>;
tempAuthMode: OV<string>;
tempConnectMode: OV<string>;
tempPassword: OV<string>;
tempKeyFile: OV<string>;
submitted: OV<boolean>;
class EditRemoteConnModal extends React.Component<{}, {}> {
internalTempAlias: OV<string>;
internalTempKeyFile: OV<string>;
internalTempPassword: OV<string>;
model: RemotesModel;
constructor(props: any) {
constructor(props: { remotesModel?: RemotesModel }) {
super(props);
const { remote, remoteEdit } = this.props;
// console.log("remoteEdit", remoteEdit);
this.tempAlias = mobx.observable.box(remote.remotealias ?? "", { name: "EditRemoteSettings-alias" });
this.tempAuthMode = mobx.observable.box(remote.authtype, { name: "EditRemoteSettings-authMode" });
this.tempConnectMode = mobx.observable.box(remote.connectmode, { name: "EditRemoteSettings-connectMode" });
this.tempKeyFile = mobx.observable.box(remoteEdit.keystr ?? "", { name: "EditRemoteSettings-keystr" });
this.tempPassword = mobx.observable.box(remoteEdit.haspassword ? PasswordUnchangedSentinel : "", {
name: "EditRemoteSettings-password",
this.model = GlobalModel.remotesModel;
this.internalTempAlias = mobx.observable.box(null, { name: "EditRemoteSettings-internalTempAlias" });
this.internalTempKeyFile = mobx.observable.box(null, { name: "EditRemoteSettings-internalTempKeyFile" });
this.internalTempPassword = mobx.observable.box(null, { name: "EditRemoteSettings-internalTempPassword" });
}
@mobx.computed
get selectedRemoteId() {
return this.model.selectedRemoteId.get();
}
@mobx.computed
get selectedRemote(): T.RemoteType {
return GlobalModel.getRemote(this.selectedRemoteId);
}
@mobx.computed
get remoteEdit(): T.RemoteEditType {
return this.model.remoteEdit.get();
}
@mobx.computed
get isAuthEditMode(): boolean {
return this.model.isAuthEditMode();
}
@mobx.computed
get tempAuthMode(): mobx.IObservableValue<string> {
return mobx.observable.box(this.selectedRemote?.authtype, {
name: "EditRemoteConnModal-authMode",
});
}
@mobx.computed
get tempConnectMode(): mobx.IObservableValue<string> {
return mobx.observable.box(this.selectedRemote?.connectmode, {
name: "EditRemoteConnModal-connectMode",
});
}
@mobx.computed
get tempAlias(): mobx.IObservableValue<string> {
return mobx.observable.box(this.internalTempAlias.get() || this.selectedRemote.remotealias, {
name: "EditRemoteConnModal-alias",
});
}
@mobx.computed
get tempKeyFile(): mobx.IObservableValue<string> {
return mobx.observable.box(this.internalTempKeyFile.get() || this.remoteEdit?.keystr, {
name: "EditRemoteConnModal-keystr",
});
}
@mobx.computed
get tempPassword(): mobx.IObservableValue<string> {
const oldPassword = this.remoteEdit?.haspassword ? PasswordUnchangedSentinel : "";
const newPassword = this.internalTempPassword.get() || oldPassword;
return mobx.observable.box(newPassword, {
name: "EditRemoteConnModal-password",
});
this.submitted = mobx.observable.box(false, { name: "EditRemoteSettings-submitted" });
}
componentDidUpdate() {
let { remote } = this.props;
if (remote == null || remote.archived) {
this.props.model.deSelectRemote();
if (this.selectedRemote == null || this.selectedRemote.archived) {
this.model.deSelectRemote();
}
}
@boundMethod
clickArchive(): void {
let { remote } = this.props;
if (remote.status == "connected") {
GlobalModel.showAlert({ message: "Cannot delete a connected connection. Disconnect and try again." });
if (this.selectedRemote?.status == "connected") {
GlobalModel.showAlert({ message: "Cannot delete while connected. Disconnect and try again." });
return;
}
let prtn = GlobalModel.showAlert({
message: "Are you sure you want to delete this connection?",
confirm: true,
});
prtn.then((confirm) => {
if (!confirm) {
return;
}
GlobalCommandRunner.archiveRemote(remote.remoteid);
GlobalCommandRunner.archiveRemote(this.selectedRemote?.remoteid);
});
}
@boundMethod
clickForceInstall(): void {
let { remote } = this.props;
GlobalCommandRunner.installRemote(remote.remoteid);
GlobalCommandRunner.installRemote(this.selectedRemote?.remoteid);
}
@boundMethod
handleChangeKeyFile(value: string): void {
mobx.action(() => {
this.tempKeyFile.set(value);
this.internalTempKeyFile.set(value);
})();
}
@boundMethod
handleChangePassword(value: string): void {
mobx.action(() => {
this.tempPassword.set(value);
this.internalTempPassword.set(value);
})();
}
@boundMethod
handleChangeAlias(value: string): void {
mobx.action(() => {
this.tempAlias.set(value);
})();
}
@boundMethod
handleChangeConnectMode(value: string): void {
mobx.action(() => {
this.tempConnectMode.set(value);
})();
}
@boundMethod
handleChangeAuthMode(value: string): void {
mobx.action(() => {
this.tempAuthMode.set(value);
this.internalTempAlias.set(value);
})();
}
@boundMethod
canResetPw(): boolean {
let { remoteEdit } = this.props;
if (remoteEdit == null) {
if (this.remoteEdit == null) {
return false;
}
return remoteEdit.haspassword && this.tempPassword.get() != PasswordUnchangedSentinel;
return Boolean(this.remoteEdit.haspassword) && this.tempPassword.get() != PasswordUnchangedSentinel;
}
@boundMethod
@ -1249,10 +1242,9 @@ class EditRemoteConnModal extends React.Component<
@boundMethod
submitRemote(): void {
let { remote, remoteEdit, model } = this.props;
let authMode = this.tempAuthMode.get();
let kwargs: Record<string, string> = {};
if (!util.isStrEq(this.tempKeyFile.get(), remoteEdit.keystr)) {
if (!util.isStrEq(this.tempKeyFile.get(), this.remoteEdit?.keystr)) {
if (authMode == "key" || authMode == "key+password") {
kwargs["key"] = this.tempKeyFile.get();
} else {
@ -1264,29 +1256,20 @@ class EditRemoteConnModal extends React.Component<
kwargs["password"] = this.tempPassword.get();
}
} else {
if (remoteEdit.haspassword) {
if (this.remoteEdit?.haspassword) {
kwargs["password"] = "";
}
}
if (!util.isStrEq(this.tempAlias.get(), remote.remotealias)) {
if (!util.isStrEq(this.tempAlias.get(), this.selectedRemote?.remotealias)) {
kwargs["alias"] = this.tempAlias.get();
}
if (!util.isStrEq(this.tempConnectMode.get(), remote.connectmode)) {
if (!util.isStrEq(this.tempConnectMode.get(), this.selectedRemote?.connectmode)) {
kwargs["connectmode"] = this.tempConnectMode.get();
}
if (Object.keys(kwargs).length == 0) {
mobx.action(() => {
this.submitted.set(true);
})();
return;
}
kwargs["visual"] = "1";
kwargs["submit"] = "1";
GlobalCommandRunner.editRemote(remote.remoteid, kwargs);
mobx.action(() => {
this.submitted.set(true);
})();
model.seRecentConnAdded(false);
GlobalCommandRunner.editRemote(this.selectedRemote?.remoteid, kwargs);
this.model.closeModal();
}
renderAuthModeMessage(): any {
@ -1313,27 +1296,18 @@ class EditRemoteConnModal extends React.Component<
}
render() {
let { model, remote, remoteEdit } = this.props;
let authMode = this.tempAuthMode.get();
if (util.isBlank(remoteEdit.errorstr) && this.submitted.get()) {
if (this.remoteEdit === null || !this.isAuthEditMode) {
return null;
}
return (
<div className={cn("modal wave-modal erconn-modal is-active")}>
<div className="modal-background wave-modal-background" />
<div className="modal-content wave-modal-content erconn-wave-modal-content">
<div className="wave-modal-content-inner erconn-wave-modal-content-inner">
<header className="wave-modal-header erconn-wave-modal-header">
<div className="wave-modal-title erconn-wave-modal-title">Edit Connection</div>
<div className="wave-modal-close erconn-wave-modal-close" onClick={model.closeModal}>
<img src={close} alt="Close (Escape)" />
</div>
</header>
<div className="wave-modal-body erconn-wave-modal-body">
<Modal className="erconn-modal">
<Modal.Header title="Edit Connection" onClose={this.model.closeModal} />
<div className="wave-modal-body">
<div className="name-actions-section">
<div className="name text-primary">{getName(remote)}</div>
<div className="name text-primary">{getName(this.selectedRemote)}</div>
<div className="header-actions">
<Button theme="secondary" onClick={this.clickArchive}>
Delete
@ -1373,7 +1347,9 @@ class EditRemoteConnModal extends React.Component<
{ value: "key+password", label: "key+password" },
]}
value={this.tempAuthMode.get()}
onChange={this.handleChangeAuthMode}
onChange={(val: string) => {
this.tempAuthMode.set(val);
}}
decoration={{
endDecoration: (
<InputDecoration>
@ -1381,8 +1357,8 @@ class EditRemoteConnModal extends React.Component<
message={
<ul>
<li>
<b>none</b> - no authentication, or authentication is
already configured in your ssh config.
<b>none</b> - no authentication, or authentication is already
configured in your ssh config.
</li>
<li>
<b>key</b> - use a private key.
@ -1445,29 +1421,25 @@ class EditRemoteConnModal extends React.Component<
{ value: "manual", label: "manual" },
]}
value={this.tempConnectMode.get()}
onChange={this.handleChangeConnectMode}
onChange={(val: string) => {
this.tempConnectMode.set(val);
}}
/>
</div>
<If condition={!util.isBlank(remoteEdit.errorstr)}>
<div className="settings-field settings-error">Error: {remoteEdit.errorstr}</div>
<If condition={!util.isBlank(this.remoteEdit?.errorstr)}>
<div className="settings-field settings-error">Error: {this.remoteEdit?.errorstr}</div>
</If>
</div>
<footer className="wave-modal-footer erconn-wave-modal-footer">
<div className="action-buttons">
<Button theme="secondary" onClick={() => model.openReadModal(remote.remoteid)}>
Cancel
</Button>
<Button onClick={this.submitRemote}>Save</Button>
</div>
</footer>
</div>
</div>
</div>
<Modal.Footer onOk={this.submitRemote} onCancel={this.model.closeModal} okLabel="Save" />
</Modal>
);
}
}
const getName = (remote: T.RemoteType) => {
const getName = (remote: T.RemoteType): string => {
if (remote == null) {
return "";
}
const { remotealias, remotecanonicalname } = remote;
return remotealias ? `${remotealias} [${remotecanonicalname}]` : remotecanonicalname;
};
@ -1482,4 +1454,5 @@ export {
CreateRemoteConnModal,
ViewRemoteConnDetailModal,
EditRemoteConnModal,
ModalsProvider,
};

View File

@ -0,0 +1,22 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import {
AboutModal,
CreateRemoteConnModal,
ViewRemoteConnDetailModal,
EditRemoteConnModal,
AlertModal,
} from "./modals";
import * as constants from "../../appconst";
const modalsRegistry: { [key: string]: () => React.ReactElement } = {
[constants.ABOUT]: () => <AboutModal />,
[constants.CREATE_REMOTE]: () => <CreateRemoteConnModal />,
[constants.VIEW_REMOTE]: () => <ViewRemoteConnDetailModal />,
[constants.EDIT_REMOTE]: () => <EditRemoteConnModal />,
[constants.ALERT]: () => <AlertModal />,
};
export { modalsRegistry };

View File

@ -20,7 +20,7 @@ type OV<V> = mobx.IObservableValue<V>;
class ConnectionsView extends React.Component<{ model: RemotesModel }, { hoveredItemId: string }> {
tableRef: React.RefObject<any> = React.createRef();
tableWidth: OV<number> = mobx.observable.box(0, { name: "tableWidth" });
tableRszObs: ResizeObserver;
tableRszObs: ResizeObserver = null;
constructor(props) {
super(props);
@ -105,7 +105,6 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered
}
let items = util.sortAndFilterRemotes(GlobalModel.remotes.slice());
let remote = this.props.model.selectedRemoteId.get();
let item: T.RemoteType = null;
return (

View File

@ -1,6 +1,7 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import type React from "react";
import * as mobx from "mobx";
import { sprintf } from "sprintf-js";
import { boundMethod } from "autobind-decorator";
@ -65,7 +66,6 @@ import type {
import * as T from "../types/types";
import { WSControl } from "./ws";
import {
measureText,
getMonoFontSize,
windowWidthToCols,
windowHeightToRows,
@ -76,8 +76,9 @@ 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";
import { MagicLayout } from "../app/magiclayout";
import { modalsRegistry } from "../app/common/modals/modalsRegistry";
import * as constants from "../app/appconst";
dayjs.extend(customParseFormat);
dayjs.extend(localizedFormat);
@ -2709,13 +2710,10 @@ class RemotesModalModel {
}
class RemotesModel {
modalMode: OV<null | "read" | "add" | "edit"> = mobx.observable.box(null, {
name: "RemotesModel-modalMode",
});
selectedRemoteId: OV<string> = mobx.observable.box(null, {
name: "RemotesModel-selectedRemoteId",
});
remoteTermWrap: TermWrap;
remoteTermWrap: TermWrap = null;
remoteTermWrapFocus: OV<boolean> = mobx.observable.box(false, {
name: "RemotesModel-remoteTermWrapFocus",
});
@ -2730,10 +2728,6 @@ class RemotesModel {
name: "RemotesModel-recentlyAdded",
});
isOpen(): boolean {
return this.modalMode.get() != null;
}
get recentConnAdded(): boolean {
return this.recentConnAddedState.get();
}
@ -2753,26 +2747,26 @@ class RemotesModel {
mobx.action(() => {
this.selectedRemoteId.set(remoteId);
this.remoteEdit.set(null);
this.modalMode.set("read");
GlobalModel.modalsModel.pushModal(constants.VIEW_REMOTE);
})();
}
openAddModal(redit: RemoteEditType): void {
mobx.action(() => {
this.remoteEdit.set(redit);
this.modalMode.set("add");
GlobalModel.modalsModel.pushModal(constants.CREATE_REMOTE);
})();
}
openEditModal(redit?: RemoteEditType): void {
if (redit === undefined) {
if (redit == null) {
this.startEditAuth();
}
if (redit != null) {
GlobalModel.modalsModel.pushModal(constants.EDIT_REMOTE);
} else {
mobx.action(() => {
this.selectedRemoteId.set(redit.remoteid);
this.selectedRemoteId.set(redit?.remoteid);
this.remoteEdit.set(redit);
this.modalMode.set("edit");
GlobalModel.modalsModel.pushModal(constants.EDIT_REMOTE);
})();
}
}
@ -2795,10 +2789,6 @@ class RemotesModel {
}
}
getModalMode(): string {
return this.modalMode.get();
}
isAuthEditMode(): boolean {
return this.remoteEdit.get() != null;
}
@ -2806,8 +2796,7 @@ class RemotesModel {
@boundMethod
closeModal(): void {
mobx.action(() => {
this.modalMode.set(null);
this.selectedRemoteId.set(null);
GlobalModel.modalsModel.popModal();
})();
setTimeout(() => GlobalModel.refocus(), 10);
}
@ -2904,6 +2893,32 @@ class RemotesModel {
}
}
class ModalsModel {
store: Array<{ id: string; component: React.ComponentType }> = [];
constructor() {
mobx.makeAutoObservable(this);
}
pushModal(modalId: string) {
const modalFactory = modalsRegistry[modalId];
if (modalFactory && !this.store.some((modal) => modal.id === modalId)) {
this.store.push({ id: modalId, component: modalFactory });
}
}
popModal() {
this.store.pop();
}
get activeModals() {
return this.store.slice().map((modal) => {
return modal.component;
});
}
}
class Model {
clientId: string;
activeSessionId: OV<string> = mobx.observable.box(null, {
@ -2965,6 +2980,7 @@ class Model {
bookmarksModel: BookmarksModel;
historyViewModel: HistoryViewModel;
connectionViewModel: ConnectionsViewModel;
modalsModel: ModalsModel;
clientData: OV<ClientDataType> = mobx.observable.box(null, {
name: "clientData",
});
@ -2987,6 +3003,7 @@ class Model {
this.connectionViewModel = new ConnectionsViewModel();
this.remotesModalModel = new RemotesModalModel();
this.remotesModel = new RemotesModel();
this.modalsModel = new ModalsModel();
let isWaveSrvRunning = getApi().getWaveSrvStatus();
this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, {
name: "model-wavesrv-running",
@ -3075,6 +3092,7 @@ class Model {
showAlert(alertMessage: AlertMessageType): Promise<boolean> {
mobx.action(() => {
this.alertMessage.set(alertMessage);
GlobalModel.modalsModel.pushModal(constants.ALERT);
})();
let prtn = new Promise<boolean>((resolve, reject) => {
this.alertPromiseResolver = resolve;
@ -3085,6 +3103,7 @@ class Model {
cancelAlert(): void {
mobx.action(() => {
this.alertMessage.set(null);
GlobalModel.modalsModel.popModal();
})();
if (this.alertPromiseResolver != null) {
this.alertPromiseResolver(false);
@ -3095,6 +3114,7 @@ class Model {
confirmAlert(): void {
mobx.action(() => {
this.alertMessage.set(null);
GlobalModel.modalsModel.popModal();
})();
if (this.alertPromiseResolver != null) {
this.alertPromiseResolver(true);
@ -3212,10 +3232,6 @@ class Model {
GlobalModel.screenSettingsModal.set(null);
didSomething = true;
}
if (GlobalModel.remotesModel.isOpen()) {
GlobalModel.remotesModel.closeModal();
didSomething = true;
}
if (GlobalModel.clientSettingsModal.get()) {
GlobalModel.clientSettingsModal.set(false);
didSomething = true;
@ -3355,7 +3371,7 @@ class Model {
onMenuItemAbout(): void {
mobx.action(() => {
this.aboutModalOpen.set(true);
this.modalsModel.pushModal(constants.ABOUT);
})();
}
@ -3486,8 +3502,9 @@ class Model {
this.remotes.clear();
}
this.updateRemotes(update.remotes);
if (update.remotes?.length && this.remotesModel.recentConnAddedState.get()) {
this.remotesModel.openReadModal(update.remotes[0].remoteid);
if (update.remotes && update.remotes.length && this.remotesModel.recentConnAddedState.get()) {
GlobalModel.remotesModel.closeModal();
GlobalModel.remotesModel.openReadModal(update.remotes![0].remoteid);
}
}
if ("mainview" in update) {
@ -3737,7 +3754,7 @@ class Model {
submitCommand(
metaCmd: string,
metaSubCmd: string,
args: string[] | null,
args: string[],
kwargs: Record<string, string>,
interactive: boolean
): Promise<CommandRtnType> {
@ -3816,13 +3833,11 @@ class Model {
}
getRemote(remoteId: string): RemoteType {
for (let i = 0; i < this.remotes.length; i++) {
if (this.remotes[i].remoteid == remoteId) {
return this.remotes[i];
}
}
if (remoteId == null) {
return null;
}
return this.remotes.find((remote) => remote.remoteid === remoteId);
}
getRemoteNames(): Record<string, string> {
let rtn: Record<string, string> = {};

View File

@ -168,7 +168,7 @@ type FeCmdPacketType = {
type: string;
metacmd: string;
metasubcmd?: string;
args: string[] | null;
args: string[];
kwargs: Record<string, string>;
rawstr?: string;
uicontext: UIContextType;