From d528f3db27be254f30adc69ae603acd4bc265dd6 Mon Sep 17 00:00:00 2001 From: sawka Date: Thu, 2 Nov 2023 00:08:04 -0700 Subject: [PATCH] abstract connection selector dropdown properly. use in settings. remove old selector. --- src/app/common/modals/settings.tsx | 124 ++++++++------- src/app/connections/connections.less | 182 +++++++++++++++------- src/app/connections/connections.tsx | 188 +++++++++++++---------- src/app/workspace/screen/screenview.less | 137 ----------------- src/app/workspace/screen/screenview.tsx | 80 +--------- src/model/model.ts | 2 +- 6 files changed, 298 insertions(+), 415 deletions(-) diff --git a/src/app/common/modals/settings.tsx b/src/app/common/modals/settings.tsx index 6e5c174e7..c58dc8bd1 100644 --- a/src/app/common/modals/settings.tsx +++ b/src/app/common/modals/settings.tsx @@ -10,7 +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 { ConnectionDropdown } from "../../connections/connections"; import { PluginModel } from "../../../plugins/plugins"; import * as util from "../../../util/util"; import { commandRtnHandler } from "../../../util/util"; @@ -50,7 +50,7 @@ Are you sure you want to stop web-sharing this screen? `.trim(); @mobxReact.observer -class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string; inline?: boolean }, {}> { +class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string; }, {}> { shareCopied: OV = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" }); errorMessage: OV = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" }); @@ -194,39 +194,37 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: }); } + @boundMethod + selectRemote(cname: string): void { + let prtn = GlobalCommandRunner.screenSetRemote(cname, true, false); + util.commandRtnHandler(prtn, this.errorMessage); + } + render() { - let { sessionId, screenId, inline } = this.props; + let { sessionId, screenId } = this.props; + let inline = false; let screen = GlobalModel.getScreenById(sessionId, screenId); if (screen == null) { return null; } let color: string = null; + let curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid); return ( -
- {!inline &&
} -
+
+
+
{this.shareCopied.get() &&
} - {!inline && ( -
-
screen settings ({screen.name.get()})
-
- -
-
- )} +
+
screen settings ({screen.name.get()})
+
+ +
+
- {!inline && ( -
-
Screen Id
-
{screen.screenId}
-
- )} +
+
Screen Id
+
{screen.screenId}
+
Name
@@ -240,6 +238,12 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: />
+
+
Connection
+
+ +
+
Tab Color
@@ -261,47 +265,41 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
- {!inline && ( -
-
-
Archived
- - Archive will hide the screen tab. Commands and output will be retained in - history. - -
-
- +
+
+
Archived
+ + Archive will hide the screen tab. Commands and output will be retained in + history. + +
+
+ +
+
+
+
+
Actions
+ + Delete will remove the screen, removing all commands and output from history. + +
+
+
+ Delete Screen
- )} - {!inline && ( -
-
-
Actions
- - Delete will remove the screen, removing all commands and output from history. - -
-
-
- Delete Screen -
-
-
- )} +
- {!inline && ( -
-
- Close -
-
- )} +
+
+ Close +
+
); diff --git a/src/app/connections/connections.less b/src/app/connections/connections.less index 7a039405b..369681b30 100644 --- a/src/app/connections/connections.less +++ b/src/app/connections/connections.less @@ -270,65 +270,139 @@ } } -.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; +.dropdown.conn-dropdown { + padding-left: 0; + border-radius: 8px; + background-color: rgba(241, 246, 243, 0.08); + + .conn-dd-trigger { + display: flex; + flex-direction: row; + width: 413px; + padding: 6px 8px 6px 12px; + align-items: center; + height: 42px; + + .lefticon { + margin-right: 8px; + margin-top: 4px; + position: relative; + + .status-icon { + width: 10px; + height: 10px; + stroke-width: 2px; + stroke: @status-outline; + position: absolute; + bottom: 3px; + right: -2px; } - .remote-name { - vertical-align: bottom; - .remote-status { - top: -2px; - left: 4px; - vertical-align: middle; - font-size: 0.85em; + } + + .dd-control { + display: flex; + padding: 4px; + align-items: center; + + .icon { + height: 16px; + width: 16px; + } + } + + .globe-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + } + + .conntext { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + flex: 1 0 0; + + .conntext-solo { + color: @text-primary; + text-overflow: ellipsis; + } + + .conntext-1 { + color: @text-primary; + text-overflow: ellipsis; + } + + .conntext-2 { + color: @text-secondary; + text-overflow: ellipsis; + } + } + } + + .conn-dd-menu { + display: flex; + width: 413px; + padding: 6px; + flex-direction: column; + align-items: flex-start; + border-radius: 8px; + background-color: @dropdown-menu; + + .dropdown-item { + display: flex; + padding: 5px 12px 5px 8px; + align-items: center; + gap: 8px; + align-self: stretch; + border-radius: 6px; + + .status-div { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + padding: 3px; + + svg.status-icon { + width: 10px; + height: 10px; } } - } - .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; - } + .add-div { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + + svg.add-icon { + width: 16px; + height: 16px; - .remote-name-secondary { - color: @disabled-color; - max-width: inherit; - display: inline; + path { + fill: @text-primary; + } + } + } + + .text-standard { + color: @text-secondary; + } + + .text-caption { + color: @text-caption; + } + + .ellipsis { + text-overflow: ellipsis; + } + + &:hover { + background-color: rgba(241, 246, 243, 0.08); + } } } } diff --git a/src/app/connections/connections.tsx b/src/app/connections/connections.tsx index 34d7d61e1..d1c69f148 100644 --- a/src/app/connections/connections.tsx +++ b/src/app/connections/connections.tsx @@ -9,7 +9,7 @@ import { If, For } from "tsx-control-statements/components"; import cn from "classnames"; import { GlobalModel, GlobalCommandRunner, RemotesModalModel } from "../../model/model"; import { Toggle, RemoteStatusLight, InfoMessage } from "../common/common"; -import { RemoteType, RemoteEditType } from "../../types/types"; +import * as T from "../../types/types"; import * as util from "../../util/util"; import * as textmeasure from "../../util/textmeasure"; @@ -17,6 +17,10 @@ import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg"; import { ReactComponent as AngleDownIcon } from "../assets/icons/history/angle-down.svg"; import { ReactComponent as RotateLeftIcon } from "../assets/icons/rotate_left.svg"; import { ReactComponent as AddIcon } from "../assets/icons/add.svg"; +import { ReactComponent as GlobeIcon } from "../assets/icons/globe.svg"; +import { ReactComponent as StatusCircleIcon } from "../assets/icons/statuscircle.svg"; +import { ReactComponent as ArrowsUpDownIcon } from "../assets/icons/arrowsupdown.svg"; +import { ReactComponent as CircleIcon } from "../assets/icons/circle.svg"; import "./connections.less"; @@ -28,14 +32,14 @@ const RemotePtyRows = 8; const RemotePtyCols = 80; const PasswordUnchangedSentinel = "--unchanged--"; -function getRemoteCNWithPort(remote: RemoteType) { +function getRemoteCNWithPort(remote: T.RemoteType) { if (util.isBlank(remote.remotevars.port) || remote.remotevars.port == "22") { return remote.remotecanonicalname; } return remote.remotecanonicalname + ":" + remote.remotevars.port; } -function getRemoteTitle(remote: RemoteType) { +function getRemoteTitle(remote: T.RemoteType) { if (!util.isBlank(remote.remotealias)) { return remote.remotealias + " (" + remote.remotecanonicalname + ")"; } @@ -144,7 +148,7 @@ class ConnectModeDropdown extends React.Component<{ tempVal: OV }, {}> { } @mobxReact.observer -class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdit: RemoteEditType }, {}> { +class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdit: T.RemoteEditType }, {}> { tempAlias: OV; tempHostName: OV; tempPort: OV; @@ -452,7 +456,7 @@ class CreateRemote extends React.Component<{ model: RemotesModalModel; remoteEdi @mobxReact.observer class EditRemoteSettings extends React.Component< - { model: RemotesModalModel; remote: RemoteType; remoteEdit: RemoteEditType }, + { model: RemotesModalModel; remote: T.RemoteType; remoteEdit: T.RemoteEditType }, {} > { tempAlias: OV; @@ -763,7 +767,7 @@ class EditRemoteSettings extends React.Component< } @mobxReact.observer -class RemoteDetailView extends React.Component<{ model: RemotesModalModel; remote: RemoteType }, {}> { +class RemoteDetailView extends React.Component<{ model: RemotesModalModel; remote: T.RemoteType }, {}> { termRef: React.RefObject = React.createRef(); componentDidMount() { @@ -793,7 +797,7 @@ class RemoteDetailView extends React.Component<{ model: RemotesModalModel; remot } } - getRemoteTypeStr(remote: RemoteType): string { + getRemoteTypeStr(remote: T.RemoteType): string { if (!util.isBlank(remote.uname)) { let unameStr = remote.uname; unameStr = unameStr.replace("|", ", "); @@ -827,7 +831,7 @@ class RemoteDetailView extends React.Component<{ model: RemotesModalModel; remot this.props.model.startEditAuth(); } - renderInstallStatus(remote: RemoteType): any { + renderInstallStatus(remote: T.RemoteType): any { let statusStr: string = null; if (remote.installstatus == "disconnected") { if (remote.needsmshellupgrade) { @@ -851,7 +855,7 @@ class RemoteDetailView extends React.Component<{ model: RemotesModalModel; remot ); } - renderRemoteMessage(remote: RemoteType): any { + renderRemoteMessage(remote: T.RemoteType): any { let message: string = ""; let buttons: any[] = []; // connect, disconnect, editauth, tryreconnect, install @@ -1073,7 +1077,7 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> { GlobalCommandRunner.openCreateRemote(); } - renderRemoteMenuItem(remote: RemoteType, selectedId: string): any { + renderRemoteMenuItem(remote: T.RemoteType, selectedId: string): any { return (
{ let model = this.props.model; let selectedRemoteId = model.selectedRemoteId.get(); let allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice()); - let remote: RemoteType = null; + let remote: T.RemoteType = null; let isAuthEditMode = model.isAuthEditMode(); let selectedRemote = GlobalModel.getRemote(selectedRemoteId); let remoteEdit = model.remoteEdit.get(); @@ -1175,89 +1179,107 @@ class RemotesModal extends React.Component<{ model: RemotesModalModel }, {}> { } @mobxReact.observer -class RemotesSelector extends React.Component<{ model: RemotesModalModel; isChangeRemoteOnSelect?: boolean }, { isOpen: boolean }> { - constructor(props: any) { - super(props); - this.state = { - isOpen: false, - }; +class ConnectionDropdown extends React.Component<{ curRemote: T.RemoteType, onSelectRemote?: (cname: string) => void, allowNewConn: boolean, onNewConn?: () => void }, {}> { + connDropdownActive: OV = mobx.observable.box(false, { name: "connDropdownActive" }); + + @boundMethod + toggleConnDropdown(): void { + mobx.action(() => { + this.connDropdownActive.set(!this.connDropdownActive.get()); + })(); } @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 + selectRemote(cname: string): void { + mobx.action(() => { + this.connDropdownActive.set(false); + })(); + if (this.props.onSelectRemote) { + this.props.onSelectRemote(cname); } - this.setState({ isOpen: false }); } @boundMethod - clickAddRemote(): void { - GlobalModel.remotesModalModel.openModalForEdit({remoteedit: true}, true); - this.setState({ isOpen: false }); + clickNewConnection(): void { + mobx.action(() => { + this.connDropdownActive.set(false); + })(); + if (this.props.onNewConn) { + this.props.onNewConn(); + } } - - 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}
-
- ); + let { curRemote } = this.props; + let remote: T.RemoteType = null; + let allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice()); return ( -
-
-
-
- -
-