working on new connections modal

This commit is contained in:
sawka
2023-04-03 01:39:27 -07:00
parent 7134a21f6a
commit 443c8155e7
7 changed files with 908 additions and 65 deletions
+108 -1
View File
@@ -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<V> = mobx.IObservableValue<V>;
function renderCmdText(text : string) : any {
return <span>&#x2318;{text}</span>;
@@ -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 (
<i className={cn("remote-status", icon, "status-" + status)}/>
);
}
}
@mobxReact.observer
class InlineSettingsTextEdit extends React.Component<{text : string, value : string, onChange : (val : string) => void, maxLength : number, placeholder : string}, {}> {
isEditing : OV<boolean> = mobx.observable.box(false, {name: "inlineedit-isEditing"});
tempText : OV<string>;
@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 (
<div className={cn("settings-input inline-edit", "edit-active")}>
<div className="field has-addons">
<div className="control">
<input className="input" type="text" onKeyDown={this.handleKeyDown} placeholder={this.props.placeholder} onChange={this.handleChangeText} value={this.tempText.get()} maxLength={this.props.maxLength}/>
</div>
<div className="control">
<div onClick={this.cancelChange} title="Cancel (Esc)" className="button is-prompt-danger is-outlined is-small"><span className="icon is-small"><i className="fa-sharp fa-solid fa-xmark"/></span></div>
</div>
<div className="control">
<div onClick={this.confirmChange} title="Confirm (Enter)" className="button is-prompt-green is-outlined is-small"><span className="icon is-small"><i className="fa-sharp fa-solid fa-check"/></span></div>
</div>
</div>
</div>
);
}
else {
return (
<div onClick={this.clickEdit} className={cn("settings-input inline-edit", "edit-not-active")}>
{this.props.text}
<i style={{marginLeft: 5}} className="fa-sharp fa-solid fa-pen"/>
</div>
);
}
}
}
export {CmdStrCode, Toggle, renderCmdText, RemoteStatusLight, InlineSettingsTextEdit};
+17 -51
View File
@@ -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 (
<i className={cn("remote-status", icon, "status-" + status)}/>
);
}
}
@mobxReact.observer
class MainSideBar extends React.Component<{}, {}> {
collapsed : mobx.IObservableValue<boolean> = 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<{}, {}> {
<li className="menu-bookmarks"><a onClick={this.handleWelcomeClick} className={cn({"is-active": GlobalModel.welcomeModalOpen.get()})}><i className="fa-sharp fa-solid fa-door-open"/> WELCOME</a></li>
</ul>
<ul className="menu-list">
<li className="menu-bookmarks"><a onClick={this.handleSettingsClick}><i className="fa-sharp fa-solid fa-cog"/> SETTINGS</a></li>
<li className="menu-settings"><a onClick={this.handleSettingsClick}><i className="fa-sharp fa-solid fa-cog"/> SETTINGS</a></li>
</ul>
<ul className="menu-list">
<li className="menu-connections"><a onClick={this.handleConnectionsClick}><i className="fa-sharp fa-solid fa-computer-classic"/> CONNECTIONS</a></li>
</ul>
<p className="menu-label">
<a onClick={() => this.clickLinks()}>LINKS <i className={cn("fa-sharp fa-solid", (GlobalModel.showLinks.get() ? "fa-angle-down" : "fa-angle-right"))}/></a>
@@ -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 (
<div className="modal prompt-modal is-active alert-modal">
@@ -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<{}, {}> {
<If condition={clientSettingsModal}>
<ClientSettingsModal/>
</If>
<If condition={remotesModal != null}>
<RemotesModal/>
</If>
</div>
);
}
+45 -1
View File
@@ -2307,6 +2307,8 @@ class Model {
sessionSettingsModal : OV<string> = mobx.observable.box(null, {name: "sessionSettingsModal"});
clientSettingsModal : OV<boolean> = mobx.observable.box(false, {name: "clientSettingsModal"});
lineSettingsModal : OV<LineType> = mobx.observable.box(null, {name: "lineSettingsModal"});
remotesModal : OV<string> = 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.remotes.length; i++) {
if (this.remotes[i].local) {
return this.remotes[i];
}
}
return null;
}
getCurRemoteInstance() : RemoteInstanceType {
let screen = this.getActiveScreen();
if (screen == null) {
return null;
}
return screen.getCurRemoteInstance();
}
onLocalServerStatusChange(status : boolean) : void {
mobx.action(() => {
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;
}
}
+502 -4
View File
File diff suppressed because it is too large Load Diff
+203 -7
View File
@@ -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 {
}
}
}
+2
View File
@@ -96,6 +96,8 @@ type RemoteType = {
uname : string,
mshellversion : string,
needsmshellupgrade : boolean,
noinitpk : boolean,
authtype : string,
waitingforpassword : boolean,
remoteopts? : RemoteOptsType,
local : boolean,
+31 -1
View File
@@ -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};