From 443c8155e792a9fcb1a0c117e605a6bfcba57e91 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Apr 2023 01:39:27 -0700 Subject: [PATCH] working on new connections modal --- src/elements.tsx | 109 +++++++++- src/main.tsx | 68 ++----- src/model.ts | 46 ++++- src/settings.tsx | 506 ++++++++++++++++++++++++++++++++++++++++++++++- src/sh2.less | 210 +++++++++++++++++++- src/types.ts | 2 + src/util.ts | 32 ++- 7 files changed, 908 insertions(+), 65 deletions(-) diff --git a/src/elements.tsx b/src/elements.tsx index a111a19d..ef13db01 100644 --- a/src/elements.tsx +++ b/src/elements.tsx @@ -5,6 +5,9 @@ import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; import cn from "classnames"; import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; +import type {RemoteType} from "./types"; + +type OV = mobx.IObservableValue; function renderCmdText(text : string) : any { return ⌘{text}; @@ -69,4 +72,108 @@ class Toggle extends React.Component<{checked : boolean, onChange : (value : boo } } -export {CmdStrCode, Toggle, renderCmdText}; +@mobxReact.observer +class RemoteStatusLight extends React.Component<{remote : RemoteType}, {}> { + render() { + let remote = this.props.remote; + let status = "error"; + let wfp = false; + if (remote != null) { + status = remote.status; + wfp = remote.waitingforpassword; + } + let icon = "fa-sharp fa-solid fa-circle" + if (status == "connecting") { + icon = (wfp ? "fa-sharp fa-solid fa-key" : "fa-sharp fa-solid fa-rotate"); + } + return ( + + ); + } +} + +@mobxReact.observer +class InlineSettingsTextEdit extends React.Component<{text : string, value : string, onChange : (val : string) => void, maxLength : number, placeholder : string}, {}> { + isEditing : OV = mobx.observable.box(false, {name: "inlineedit-isEditing"}); + tempText : OV; + + @boundMethod + handleChangeText(e : any) : void { + mobx.action(() => { + this.tempText.set(e.target.value); + })(); + } + + @boundMethod + confirmChange() : void { + mobx.action(() => { + let newText = this.tempText.get(); + this.isEditing.set(false); + this.tempText = null; + this.props.onChange(newText); + })(); + } + + @boundMethod + cancelChange() : void { + mobx.action(() => { + this.isEditing.set(false); + this.tempText = null; + })(); + } + + @boundMethod + handleKeyDown(e : any) : void { + if (e.code == "Enter") { + e.preventDefault(); + e.stopPropagation(); + this.confirmChange(); + return; + } + if (e.code == "Escape") { + e.preventDefault(); + e.stopPropagation(); + this.cancelChange(); + return; + } + return; + } + + @boundMethod + clickEdit() : void { + mobx.action(() => { + this.isEditing.set(true); + this.tempText = mobx.observable.box(this.props.value, {name: "inlineedit-tempText"}); + })(); + } + + render() { + if (this.isEditing.get()) { + return ( +
+
+
+ +
+
+
+
+
+
+
+
+
+ ); + } + else { + return ( +
+ {this.props.text} + +
+ ); + } + } +} + +export {CmdStrCode, Toggle, renderCmdText, RemoteStatusLight, InlineSettingsTextEdit}; diff --git a/src/main.tsx b/src/main.tsx index 94675d8b..2dfd5c25 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -13,14 +13,14 @@ import type * as T from "./types"; import localizedFormat from 'dayjs/plugin/localizedFormat'; import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, riToRPtr, TabColors, RemoteColors} from "./model"; import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./textmeasure"; -import {isModKeyPress, boundInt} from "./util"; +import {isModKeyPress, boundInt, sortAndFilterRemotes} from "./util"; import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import {BookmarksView} from "./bookmarks"; import {HistoryView} from "./history"; import {Line, Prompt} from "./linecomps"; -import {ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal} from "./settings"; -import {renderCmdText} from "./elements"; +import {ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal, RemotesModal} from "./settings"; +import {renderCmdText, RemoteStatusLight} from "./elements"; import {LinesView} from "./linesview"; dayjs.extend(localizedFormat) @@ -2038,39 +2038,6 @@ class SessionView extends React.Component<{}, {}> { } } -function getConnVal(r : RemoteType) : number { - if (r.status == "connected") { - return 1; - } - if (r.status == "disconnected") { - return 2; - } - if (r.status == "error") { - return 3; - } - return 4; -} - -@mobxReact.observer -class RemoteStatusLight extends React.Component<{remote : RemoteType}, {}> { - render() { - let remote = this.props.remote; - let status = "error"; - let wfp = false; - if (remote != null) { - status = remote.status; - wfp = remote.waitingforpassword; - } - let icon = "fa-sharp fa-solid fa-circle" - if (status == "connecting") { - icon = (wfp ? "fa-sharp fa-solid fa-key" : "fa-sharp fa-solid fa-rotate"); - } - return ( - - ); - } -} - @mobxReact.observer class MainSideBar extends React.Component<{}, {}> { collapsed : mobx.IObservableValue = mobx.observable.box(false); @@ -2167,6 +2134,11 @@ class MainSideBar extends React.Component<{}, {}> { })(); } + @boundMethod + handleConnectionsClick() : void { + GlobalModel.openRemotesModal(); + } + @boundMethod openSessionSettings(e : any, session : Session) : void { e.preventDefault(); @@ -2275,7 +2247,10 @@ class MainSideBar extends React.Component<{}, {}> {
  • WELCOME
  • +

    this.clickLinks()}>LINKS @@ -2312,19 +2287,6 @@ class MainSideBar extends React.Component<{}, {}> { } } -function sortAndFilterRemotes(origRemotes : RemoteType[]) : RemoteType[] { - let remotes = origRemotes.filter((r) => !r.archived); - remotes.sort((a, b) => { - let connValA = getConnVal(a); - let connValB = getConnVal(b); - if (connValA != connValB) { - return connValA - connValB; - } - return a.remoteidx - b.remoteidx; - }); - return remotes; -} - @mobxReact.observer class DisconnectedModal extends React.Component<{}, {}> { logRef : any = React.createRef(); @@ -2484,7 +2446,7 @@ class AlertModal extends React.Component<{}, {}> { if (message == null) { return null; } - let title = message.title ?? "Alert"; + let title = message.title ?? (message.confirm ? "Confirm" : "Alert"); let isConfirm = message.confirm; return (

    @@ -2658,6 +2620,7 @@ class Main extends React.Component<{}, {}> { let sessionSettingsModal = GlobalModel.sessionSettingsModal.get(); let lineSettingsModal = GlobalModel.lineSettingsModal.get(); let clientSettingsModal = GlobalModel.clientSettingsModal.get(); + let remotesModal = GlobalModel.remotesModal.get(); let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.localServerRunning.get(); let hasClientStop = GlobalModel.getHasClientStop(); let dcWait = this.dcWait.get(); @@ -2709,6 +2672,9 @@ class Main extends React.Component<{}, {}> { + + +
    ); } diff --git a/src/model.ts b/src/model.ts index 82aae3ef..51245513 100644 --- a/src/model.ts +++ b/src/model.ts @@ -2307,6 +2307,8 @@ class Model { sessionSettingsModal : OV = mobx.observable.box(null, {name: "sessionSettingsModal"}); clientSettingsModal : OV = mobx.observable.box(false, {name: "clientSettingsModal"}); lineSettingsModal : OV = mobx.observable.box(null, {name: "lineSettingsModal"}); + remotesModal : OV = mobx.observable.box(null, {name: "remotesModal"}); // set with remoteid + remoteTermWrap : TermWrap = null; inputModel : InputModel; bookmarksModel : BookmarksModel; @@ -2487,6 +2489,40 @@ class Model { getApi().restartLocalServer(); } + openRemotesModal() : void { + let ri = this.getCurRemoteInstance(); + let remoteId : string = null; + if (ri != null) { + remoteId = ri.remoteid; + } + else { + let localRemote = this.getLocalRemote(); + if (localRemote != null) { + remoteId = localRemote.remoteid; + } + } + mobx.action(() => { + this.remotesModal.set(remoteId); + })(); + } + + getLocalRemote() : RemoteType { + for (let i=0; i { this.localServerRunning.set(status); @@ -2660,12 +2696,20 @@ class Model { } else { // remote update + let ptyData = base64ToArray(ptyMsg.ptydata64); + + // new remote term + if (this.remoteTermWrap != null && this.remoteTermWrap.getContextRemoteId() == ptyMsg.remoteid) { + this.remoteTermWrap.receiveData(ptyMsg.ptypos, ptyData); + } + + // old remote term let activeRemoteId = this.inputModel.getPtyRemoteId(); if (activeRemoteId != ptyMsg.remoteid || this.inputModel.remoteTermWrap == null) { return; } - let ptyData = base64ToArray(ptyMsg.ptydata64); this.inputModel.remoteTermWrap.receiveData(ptyMsg.ptypos, ptyData); + return; } } diff --git a/src/settings.tsx b/src/settings.tsx index dcf1765d..09563fe5 100644 --- a/src/settings.tsx +++ b/src/settings.tsx @@ -5,16 +5,22 @@ import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import cn from "classnames"; -import {GlobalModel, GlobalCommandRunner, TabColors} from "./model"; -import {Toggle} from "./elements"; -import {LineType, RendererPluginType, ClientDataType} from "./types"; +import {GlobalModel, GlobalCommandRunner, TabColors, getTermPtyData} from "./model"; +import {Toggle, RemoteStatusLight, InlineSettingsTextEdit} from "./elements"; +import {LineType, RendererPluginType, ClientDataType, RemoteType, RemoteInputPacketType} from "./types"; import {PluginModel} from "./plugins"; +import * as util from "./util"; +import * as textmeasure from "./textmeasure"; +import {TermWrap} from "./term"; type OV = mobx.IObservableValue; type OArr = mobx.IObservableArray; type OMap = mobx.ObservableMap; type CV = mobx.IComputedValue; +const RemotePtyRows = 8; +const RemotePtyCols = 80; + // @ts-ignore const VERSION = __PROMPT_VERSION__; // @ts-ignore @@ -599,4 +605,496 @@ class ClientSettingsModal extends React.Component<{}, {}> { } } -export {ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal}; +@mobxReact.observer +class RemotesModal extends React.Component<{}, {}> { + termRef : React.RefObject = React.createRef(); + remoteTermWrap : TermWrap; + remoteTermWrapFocus : OV = mobx.observable.box(false, {name: "RemotesModal-remoteTermWrapFocus"}); + showNoInputMsg : OV = mobx.observable.box(false, {name: "RemotesModel-showNoInputMg"}); + showNoInputTimeoutId : any = null; + authEditMode : OV = mobx.observable.box(false, {name: "RemotesModal-authEditMode"}); + + componentDidMount() { + this.syncTermWrap() + } + + componentDidUpdate() { + this.syncTermWrap(); + } + + componentWillUnmount() { + this.disposeTerm(); + } + + disposeTerm() : void { + if (this.remoteTermWrap != null) { + this.remoteTermWrap.dispose(); + this.remoteTermWrap = null; + GlobalModel.remoteTermWrap = null; + } + } + + syncTermWrap() : void { + if (this.authEditMode.get()) { + this.disposeTerm(); + return; + } + let remoteId = GlobalModel.remotesModal.get(); + let curTermRemoteId = (this.remoteTermWrap == null ? null : this.remoteTermWrap.getContextRemoteId()); + if (remoteId == curTermRemoteId) { + return; + } + if (this.remoteTermWrap != null) { + this.disposeTerm(); + } + if (remoteId == null) { + return; + } + let elem = this.termRef.current; + if (elem == null) { + console.log("ERROR null term-remote element"); + return; + } + let termOpts = {rows: RemotePtyRows, cols: RemotePtyCols, flexrows: false, maxptysize: 64*1024}; + this.remoteTermWrap = new TermWrap(elem, { + termContext: {remoteId: remoteId}, + usedRows: RemotePtyRows, + termOpts: termOpts, + winSize: null, + keyHandler: (e, termWrap) => { this.termKeyHandler(remoteId, e, termWrap)}, + focusHandler: this.setRemoteTermWrapFocus.bind(this), + isRunning: true, + fontSize: GlobalModel.termFontSize.get(), + ptyDataSource: getTermPtyData, + onUpdateContentHeight: null, + }); + GlobalModel.remoteTermWrap = this.remoteTermWrap; + } + + @boundMethod + setShowNoInputMsg(val : boolean) { + mobx.action(() => { + if (this.showNoInputTimeoutId != null) { + clearTimeout(this.showNoInputTimeoutId); + this.showNoInputTimeoutId = null; + } + if (val) { + this.showNoInputMsg.set(true); + this.showNoInputTimeoutId = setTimeout(() => this.setShowNoInputMsg(false), 2000); + } + else { + this.showNoInputMsg.set(false); + } + })(); + } + + @boundMethod + setRemoteTermWrapFocus(focus : boolean) : void { + mobx.action(() => { + this.remoteTermWrapFocus.set(focus); + })(); + } + + @boundMethod + clickTermBlock() : void { + if (this.remoteTermWrap != null) { + this.remoteTermWrap.giveFocus(); + } + } + + getRemoteTypeStr(remote : RemoteType) : string { + if (!util.isBlank(remote.uname)) { + let unameStr = remote.uname; + unameStr = unameStr.replace("|", ", "); + return remote.remotetype + " (" + unameStr + ")"; + } + return remote.remotetype; + } + + @boundMethod + termKeyHandler(remoteId : string, event : any, termWrap : TermWrap) : void { + let remote = GlobalModel.getRemote(remoteId); + if (remote == null) { + return; + } + if (remote.status != "connecting" && remote.installstatus != "connecting") { + this.setShowNoInputMsg(true); + return; + } + let inputPacket : RemoteInputPacketType = { + type: "remoteinput", + remoteid: remoteId, + inputdata64: btoa(event.key), + }; + GlobalModel.sendInputPacket(inputPacket); + } + + @boundMethod + closeModal() : void { + mobx.action(() => { + GlobalModel.remotesModal.set(null); + })(); + } + + @boundMethod + selectRemote(remoteId : string) : void { + if (GlobalModel.remotesModal.get() == remoteId) { + return; + } + mobx.action(() => { + GlobalModel.remotesModal.set(remoteId); + this.authEditMode.set(false); + })(); + } + + @boundMethod + connectRemote(remoteId : string) { + GlobalCommandRunner.connectRemote(remoteId); + } + + @boundMethod + disconnectRemote(remoteId : string) { + GlobalCommandRunner.disconnectRemote(remoteId); + } + + @boundMethod + installRemote(remoteId : string) { + GlobalCommandRunner.installRemote(remoteId); + } + + @boundMethod + cancelInstall(remoteId : string) { + GlobalCommandRunner.installCancelRemote(remoteId); + } + + @boundMethod + editAuthSettings() : void { + mobx.action(() => { + this.authEditMode.set(true); + })(); + } + + @boundMethod + cancelEditAuth() : void { + mobx.action(() => { + this.authEditMode.set(false); + })(); + } + + @boundMethod + clickAddRemote() : void { + } + + @boundMethod + clickArchive(remoteId : string) : void { + let prtn = GlobalModel.showAlert({message: "Are you sure you want to archive this connection?", confirm: true}); + prtn.then((confirm) => { + if (!confirm) { + return; + } + console.log("archive remote", remoteId); + }); + } + + @boundMethod + editAlias(remoteId : string, alias : string) : void { + } + + renderRemoteMenuItem(remote : RemoteType, selectedId : string) : any { + return ( +
    this.selectRemote(remote.remoteid) } className={cn("remote-menu-item", {"is-selected" : remote.remoteid == selectedId})}> +
    + +
    +
    {remote.remotecanonicalname}
    +
    +
    + +
    +
    {remote.remotealias}
    +
    {remote.remotecanonicalname}
    +
    +
    +
    + ); + } + + renderAddRemoteMenuItem() : any { + return ( +
    +
    + Add Connection +
    +
    + ); + } + + renderInstallStatus(remote : RemoteType) : any { + let statusStr : string = null; + if (remote.installstatus == "disconnected") { + if (remote.needsmshellupgrade) { + statusStr = "mshell " + remote.mshellversion + " (needs upgrade)"; + } + else if (util.isBlank(remote.mshellversion)) { + statusStr = "mshell unknown"; + } + else { + statusStr = "mshell " + remote.mshellversion + " (current)"; + } + } + else { + statusStr = remote.installstatus; + } + if (statusStr == null) { + return null; + } + return ( +
    +
    Install Status
    +
    + {statusStr} +
    +
    + ); + } + + renderRemoteMessage(remote : RemoteType) : any { + if (remote.status == "connected") { + return ( +
    +
    +
    Connected and ready to run commands.
    +
    +
    this.disconnectRemote(remote.remoteid)} className="button is-prompt-danger is-outlined is-small">Disconnect Now
    +
    +
    + ); + } + if (remote.status == "connecting") { + let message = (remote.waitingforpassword ? "Connecting, waiting for user-input..." : "Connecting..."); + return ( +
    +
    +
    {message}
    +
    +
    this.disconnectRemote(remote.remoteid)} className="button is-prompt-danger is-outlined is-small">Disconnect Now
    +
    +
    + ); + } + if (remote.status == "disconnected") { + return ( +
    +
    +
    Disconnected
    +
    +
    this.connectRemote(remote.remoteid)} className="button is-prompt-green is-outlined is-small">Connect Now
    +
    +
    + ); + } + if (remote.status == "error") { + if (remote.noinitpk) { + return ( +
    +
    +
    Error, could not connect.
    +
    +
    this.connectRemote(remote.remoteid)} className="button is-prompt-green is-outlined is-small">Try Reconnect
    +
    this.editAuthSettings()} className="button is-plain is-outlined is-small">Update Auth Settings
    +
    +
    + ); + } + if (remote.needsmshellupgrade) { + if (remote.installstatus == "connecting") { + return ( +
    +
    +
    Installing...
    +
    +
    this.cancelInstall(remote.remoteid)} className="button is-prompt-danger is-outlined is-small">Cancel Install
    +
    +
    + ); + } + return ( +
    +
    +
    Error, needs install.
    +
    +
    this.installRemote(remote.remoteid)} className="button is-prompt-green is-outlined is-small">Install Now
    +
    this.editAuthSettings()} className="button is-plain is-outlined is-small">Update Auth Settings
    +
    +
    + ); + } + return ( +
    +
    +
    Error
    +
    this.connectRemote(remote.remoteid)} className="button is-prompt-green is-outlined is-small">Try Reconnect
    +
    this.editAuthSettings()} className="button is-plain is-outlined is-small">Update Auth Settings
    +
    +
    + ); + } + return null; + } + + renderRemote(remoteId : string) : any { + let remote = GlobalModel.getRemote(remoteId); + if (remote == null) { + return ( +
    +
    + No Remote Selected +
    +
    + ); + } + let isTermFocused = this.remoteTermWrapFocus.get(); + let termFontSize = GlobalModel.termFontSize.get(); + let remoteMessage = this.renderRemoteMessage(remote); + let termWidth = textmeasure.termWidthFromCols(RemotePtyCols, termFontSize); + let remoteAliasText = (util.isBlank(remote.remotealias) ? "(none)" : remote.remotealias); + return ( +
    +
    {getRemoteTitle(remote)}
    +
    +
    Conn Id
    +
    {remote.remoteid}
    +
    +
    +
    Type
    +
    {this.getRemoteTypeStr(remote)}
    +
    +
    +
    Canonical Name
    +
    + {remote.remotecanonicalname} + + (port {remote.remotevars.port}) + +
    +
    +
    +
    Alias
    + this.editAlias(remote.remoteid, val)} text={remoteAliasText ?? ""} value={remote.remotealias} placeholder="" maxLength={50}/> +
    +
    +
    Auth Type
    +
    this.editAuthSettings()}> + {remote.authtype} + +
    +
    +
    +
    Connect Mode
    +
    + {remote.connectmode} +
    +
    + {this.renderInstallStatus(remote)} +
    +
    Archive
    +
    +
    this.clickArchive(remote.remoteid)} className="button is-prompt-danger is-outlined is-small is-inline-height"> + Archive This Connection +
    +
    +
    +
    +
    + {remoteMessage} +
    +
    + +
    +
    + +
    input is only allowed while status is 'connecting'
    +
    +
    +
    +
    + ); + } + + renderEditAuthSettings(remoteId : string) : any { + let remote = GlobalModel.getRemote(remoteId); + if (remote == null) { + return ( +
    +
    + No Remote Selected +
    +
    + ); + } + return ( +
    +
    {getRemoteTitle(remote)}
    +
    + Editing Authentication Settings +
    +
    +
    Cancel
    +
    Submit
    +
    +
    + ); + } + + render() { + let selectedRemoteId = GlobalModel.remotesModal.get(); + let allRemotes = util.sortAndFilterRemotes(GlobalModel.remotes.slice()); + let remote : RemoteType = null; + return ( +
    +
    +
    +
    +
    Connections
    +
    + +
    +
    +
    +
    + {this.renderAddRemoteMenuItem()} + + {this.renderRemoteMenuItem(remote, selectedRemoteId)} + +
    + + {this.renderRemote(selectedRemoteId)} + + + {this.renderEditAuthSettings(selectedRemoteId)} + +
    +
    +
    Close
    +
    +
    +
    + ); + } +} + +function getRemoteCNWithPort(remote : RemoteType) { + if (util.isBlank(remote.remotevars.port) || remote.remotevars.port == "22") { + return remote.remotecanonicalname; + } + return remote.remotecanonicalname + ":" + remote.remotevars.port; +} + +function getRemoteTitle(remote : RemoteType) { + if (!util.isBlank(remote.remotealias)) { + return remote.remotealias + " (" + remote.remotecanonicalname + ")"; + } + return remote.remotecanonicalname; +} + +export {ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal, RemotesModal}; diff --git a/src/sh2.less b/src/sh2.less index fa0f9952..f56e1320 100644 --- a/src/sh2.less +++ b/src/sh2.less @@ -32,6 +32,8 @@ @soft-blue: #729fcf; +@active-menu-color: #485fc7; + :root { --fa-style-family: "Font Awesome 6 Sharp"; } @@ -1383,7 +1385,7 @@ body::-webkit-scrollbar { } } -.cmd-input-info { +.cmd-input-info, .remotes-modal { .terminal-wrapper { position: relative; background-color: #000; @@ -2542,6 +2544,19 @@ input[type=checkbox] { flex-grow: 1; } +.flex-centered-row { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.flex-centered-col { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} .loading-spinner { display: inline-block; @@ -2660,6 +2675,8 @@ input[type=checkbox] { } .modal.alert-modal { + z-index: 205; + footer { justify-content: center; @@ -2760,6 +2777,148 @@ input[type=checkbox] { } } +.modal.prompt-modal.remotes-modal { + .modal-content { + min-width: 850px; + } + + .inner-content { + display: flex; + flex-direction: row; + align-items: stretch; + padding: 0; + max-height: 80vh; + + .remotes-menu { + flex: 0 0 200px; + min-height: 450px; + border-right: 1px solid #666; + overflow-y: auto; + height: 100px; + + .remote-menu-item { + border-top: 1px solid #666; + padding: 5px; + display: flex; + flex-direction: row; + cursor: pointer; + + &.add-remote { + font-size: 13px; + padding: 10px 5px 10px 5px; + } + + &:hover { + background-color: #333; + } + + &.is-selected { + background-color: @active-menu-color; + + .remote-name .remote-name-secondary { + color: white; + } + } + + &:first-child { + border-top: 0; + } + + .remote-status-light { + width: 15px; + margin-top: -2px; + } + + .remote-name { + flex-grow: 1; + + .remote-name-primary { + font-size: 12px; + font-weight: bold; + } + + .remote-name-secondary { + font-size: 11px; + color: #777; + } + } + } + } + + .remote-detail { + padding: 10px; + flex-grow: 1; + font-size: 12px; + display: flex; + flex-direction: column; + + .settings-field { + margin-top: 5px; + } + + * { + flex-shrink: 0; + } + + .title { + color: white; + padding-bottom: 8px; + margin-bottom: 0; + border-bottom: 1px solid #777; + } + + .terminal-wrapper { + margin-left: 0; + margin-bottom: 0; + + &.has-message { + margin-top: 0; + } + + box-shadow: none; + border: 1px solid #777; + } + + .action-buttons { + display: flex; + flex-direction: row; + gap: 10px; + margin-top: 2px; + } + + .remote-message { + margin-top: 5px; + padding: 8px; + border-radius: 5px 5px 0 0; + background-color: #333; + + i.fa-check { + color: @term-green; + } + + &.is-ok { + + } + + .message-row { + display: flex; + flex-direction: row; + align-items: center; + } + + .remote-status { + position: relative; + top: -1px; + } + + .button { + height: 22px; + } + } + } + } +} + .modal.welcome-modal { footer { .prev-button { @@ -2883,6 +3042,26 @@ input[type=checkbox] { display: flex; flex-direction: row; align-items: center; + + &.inline-edit.edit-not-active { + cursor: pointer; + } + + &.settings-clickable { + cursor: pointer; + } + + &.inline-edit.edit-active { + input.input { + padding: 0; + height: 20px; + font-size: 12px; + } + + .button { + height: 20px; + } + } input { padding: 4px; @@ -3010,21 +3189,37 @@ input[type=checkbox] { &:hover { background-color: @term-green; - font-weight: bold; + color: @term-bright-white; } } -.button.is-prompt-cancel { +.button.is-plain, .button.is-prompt-cancel { background-color: #222; - color: #777; - border-color: #777; + color: @term-white; &:hover { - background-color: #777; - color: #fff; + background-color: #666; + color: @term-bright-white; } } +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} .simple-image-renderer { padding: 10px; @@ -3297,3 +3492,4 @@ body.prompt-webshare #main { } } } + diff --git a/src/types.ts b/src/types.ts index 9a035ae7..baedf21e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -96,6 +96,8 @@ type RemoteType = { uname : string, mshellversion : string, needsmshellupgrade : boolean, + noinitpk : boolean, + authtype : string, waitingforpassword : boolean, remoteopts? : RemoteOptsType, local : boolean, diff --git a/src/util.ts b/src/util.ts index b1435e45..9b972065 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,6 +2,7 @@ import * as mobx from "mobx"; import {sprintf} from "sprintf-js"; import dayjs from "dayjs"; import localizedFormat from 'dayjs/plugin/localizedFormat'; +import type {RemoteType} from "./types"; dayjs.extend(localizedFormat) @@ -299,4 +300,33 @@ function getDateStr(d : Date) : string { return dowStr + " " + yearStr + "-" + monthStr + "-" + dayStr; } -export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, parseEnv0, boundInt, isModKeyPress, incObs, isBlank, loadFonts, getTodayStr, getYesterdayStr, getDateStr}; +function getRemoteConnVal(r : RemoteType) : number { + if (r.status == "connected") { + return 1; + } + if (r.status == "connecting") { + return 2; + } + if (r.status == "disconnected") { + return 3; + } + if (r.status == "error") { + return 4; + } + return 5; +} + +function sortAndFilterRemotes(origRemotes : RemoteType[]) : RemoteType[] { + let remotes = origRemotes.filter((r) => !r.archived); + remotes.sort((a, b) => { + let connValA = getRemoteConnVal(a); + let connValB = getRemoteConnVal(b); + if (connValA != connValB) { + return connValA - connValB; + } + return a.remoteidx - b.remoteidx; + }); + return remotes; +} + +export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, parseEnv0, boundInt, isModKeyPress, incObs, isBlank, loadFonts, getTodayStr, getYesterdayStr, getDateStr, sortAndFilterRemotes};