mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
working on new connections modal
This commit is contained in:
+108
-1
@@ -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>⌘{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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+203
-7
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@ type RemoteType = {
|
||||
uname : string,
|
||||
mshellversion : string,
|
||||
needsmshellupgrade : boolean,
|
||||
noinitpk : boolean,
|
||||
authtype : string,
|
||||
waitingforpassword : boolean,
|
||||
remoteopts? : RemoteOptsType,
|
||||
local : boolean,
|
||||
|
||||
+31
-1
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user