connections screen and modals (#69)

* 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

* 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)
This commit is contained in:
Red J Adaya
2023-11-27 16:22:15 -08:00
committed by GitHub
parent 64203e7823
commit e95934e2df
14 changed files with 3427 additions and 1742 deletions
+38 -15
View File
@@ -49,6 +49,14 @@ textarea {
} }
} }
.text-primary {
font-size: 15px;
font-weight: 500;
line-height: 20px;
font-family: @text-s1-font;
color: @text-primary;
}
.text-standard { .text-standard {
font-size: 12.5px; font-size: 12.5px;
font-weight: 300; font-weight: 300;
@@ -219,7 +227,8 @@ a.a-block {
.history-view, .history-view,
.bookmarks-view, .bookmarks-view,
.plugins-view { .plugins-view,
.connections-view {
flex-grow: 1; flex-grow: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -465,85 +474,99 @@ a.a-block {
} }
.icon.color-red { .icon.color-red {
path, circle { path,
circle {
fill: @tab-red; fill: @tab-red;
} }
} }
.icon.color-green { .icon.color-green {
path, circle { path,
circle {
fill: @tab-green; fill: @tab-green;
} }
} }
.icon.color-orange { .icon.color-orange {
path, circle { path,
circle {
fill: @tab-orange; fill: @tab-orange;
} }
} }
.icon.color-blue { .icon.color-blue {
path, circle { path,
circle {
fill: @tab-blue; fill: @tab-blue;
} }
} }
.icon.color-yellow { .icon.color-yellow {
path, circle { path,
circle {
fill: @tab-yellow; fill: @tab-yellow;
} }
} }
.icon.color-pink { .icon.color-pink {
path, circle { path,
circle {
fill: @tab-pink; fill: @tab-pink;
} }
} }
.icon.color-mint { .icon.color-mint {
path, circle { path,
circle {
fill: @tab-mint; fill: @tab-mint;
} }
} }
.icon.color-cyan { .icon.color-cyan {
path, circle { path,
circle {
fill: @tab-cyan; fill: @tab-cyan;
} }
} }
.icon.color-violet { .icon.color-violet {
path, circle { path,
circle {
fill: @tab-violet; fill: @tab-violet;
} }
} }
.icon.color-white { .icon.color-white {
path, circle { path,
circle {
fill: @tab-white; fill: @tab-white;
} }
} }
.status-icon.status-connected { .status-icon.status-connected {
path, circle { path,
circle {
fill: @status-connected; fill: @status-connected;
} }
} }
.status-icon.status-connecting { .status-icon.status-connecting {
path, circle { path,
circle {
fill: @status-connecting; fill: @status-connecting;
} }
} }
.status-icon.status-disconnected { .status-icon.status-disconnected {
path, circle { path,
circle {
fill: @status-disconnected; fill: @status-disconnected;
} }
} }
.status-icon.status-error { .status-icon.status-error {
path, circle { path,
circle {
fill: @status-error; fill: @status-error;
} }
} }
+28 -8
View File
@@ -15,13 +15,14 @@ import { WorkspaceView } from "./workspace/workspaceview";
import { PluginsView } from "./pluginsview/pluginsview"; import { PluginsView } from "./pluginsview/pluginsview";
import { BookmarksView } from "./bookmarks/bookmarks"; import { BookmarksView } from "./bookmarks/bookmarks";
import { HistoryView } from "./history/history"; import { HistoryView } from "./history/history";
import { ConnectionsView } from "./connections/connections";
import { import {
ScreenSettingsModal, ScreenSettingsModal,
SessionSettingsModal, SessionSettingsModal,
LineSettingsModal, LineSettingsModal,
ClientSettingsModal, ClientSettingsModal,
} from "./common/modals/settings"; } from "./common/modals/settings";
import { RemotesModal } from "./connections/connections"; import { RemotesModal } from "./connections_deprecated/connections";
import { TosModal } from "./common/modals/modals"; import { TosModal } from "./common/modals/modals";
import { MainSideBar } from "./sidebar/sidebar"; import { MainSideBar } from "./sidebar/sidebar";
import { import {
@@ -30,6 +31,8 @@ import {
AlertModal, AlertModal,
AboutModal, AboutModal,
CreateRemoteConnModal, CreateRemoteConnModal,
ViewRemoteConnDetailModal,
EditRemoteConnModal,
} from "./common/modals/modals"; } from "./common/modals/modals";
import { ErrorBoundary } from "./common/error/errorboundary"; import { ErrorBoundary } from "./common/error/errorboundary";
import "./app.less"; import "./app.less";
@@ -85,14 +88,17 @@ class App extends React.Component<{}, {}> {
let sessionSettingsModal = GlobalModel.sessionSettingsModal.get(); let sessionSettingsModal = GlobalModel.sessionSettingsModal.get();
let lineSettingsModal = GlobalModel.lineSettingsModal.get(); let lineSettingsModal = GlobalModel.lineSettingsModal.get();
let clientSettingsModal = GlobalModel.clientSettingsModal.get(); let clientSettingsModal = GlobalModel.clientSettingsModal.get();
let remotesModel = GlobalModel.remotesModalModel; let remotesModel = GlobalModel.remotesModel;
let remotesModal = remotesModel.isOpen(); let remotesModalMode = remotesModel.modalMode.get();
let selectedRemoteId = remotesModel.selectedRemoteId.get(); let selectedRemoteId = remotesModel.selectedRemoteId.get();
let selectedRemote = GlobalModel.getRemote(selectedRemoteId);
let isAuthEditMode = remotesModel.isAuthEditMode();
let remoteEdit = remotesModel.remoteEdit.get(); let remoteEdit = remotesModel.remoteEdit.get();
let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get(); let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get();
let hasClientStop = GlobalModel.getHasClientStop(); let hasClientStop = GlobalModel.getHasClientStop();
let dcWait = this.dcWait.get(); let dcWait = this.dcWait.get();
let platform = GlobalModel.getPlatform(); let platform = GlobalModel.getPlatform();
if (disconnected || hasClientStop) { if (disconnected || hasClientStop) {
if (!dcWait) { if (!dcWait) {
setTimeout(() => this.updateDcWait(true), 1500); setTimeout(() => this.updateDcWait(true), 1500);
@@ -117,7 +123,6 @@ class App extends React.Component<{}, {}> {
if (dcWait) { if (dcWait) {
setTimeout(() => this.updateDcWait(false), 0); setTimeout(() => this.updateDcWait(false), 0);
} }
//console.log(`GlobalModel.activeMainView.get() = ${GlobalModel.activeMainView.get()}`); // @mike - if I remove this, I cant see plugins
return ( return (
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}> <div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
<div className="main-content"> <div className="main-content">
@@ -127,6 +132,7 @@ class App extends React.Component<{}, {}> {
<WorkspaceView /> <WorkspaceView />
<HistoryView /> <HistoryView />
<BookmarksView /> <BookmarksView />
<ConnectionsView model={remotesModel} />
</ErrorBoundary> </ErrorBoundary>
</div> </div>
<AlertModal /> <AlertModal />
@@ -136,9 +142,26 @@ class App extends React.Component<{}, {}> {
<If condition={GlobalModel.aboutModalOpen.get()}> <If condition={GlobalModel.aboutModalOpen.get()}>
<AboutModal /> <AboutModal />
</If> </If>
<If condition={remoteEdit !== null && !remoteEdit.old}> <If condition={remoteEdit !== null && remotesModalMode === "add"}>
<CreateRemoteConnModal model={remotesModel} remoteEdit={remoteEdit} /> <CreateRemoteConnModal model={remotesModel} remoteEdit={remoteEdit} />
</If> </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>
<If condition={screenSettingsModal != null}> <If condition={screenSettingsModal != null}>
<ScreenSettingsModal <ScreenSettingsModal
key={screenSettingsModal.sessionId + ":" + screenSettingsModal.screenId} key={screenSettingsModal.sessionId + ":" + screenSettingsModal.screenId}
@@ -155,9 +178,6 @@ class App extends React.Component<{}, {}> {
<If condition={clientSettingsModal}> <If condition={clientSettingsModal}>
<ClientSettingsModal /> <ClientSettingsModal />
</If> </If>
<If condition={remotesModal}>
<RemotesModal model={GlobalModel.remotesModalModel} />
</If>
</div> </div>
); );
} }
+142 -28
View File
@@ -257,33 +257,6 @@
} }
} }
.wave-button {
display: flex;
padding: 6px 16px !important;
color: @term-white !important;
align-items: center;
gap: 4px;
border-radius: 6px !important;
height: auto !important;
&:hover {
color: @term-white !important;
}
}
.wave-button.is-wave-green {
color: @term-bright-white !important;
background: @term-green !important;
&:hover {
background-color: @term-green;
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
0px 0px 0.5px 0px rgba(255, 255, 255, 0.8) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.6) inset;
color: @term-bright-white !important;
box-shadow: none;
}
}
.button.is-plain, .button.is-plain,
.button.is-prompt-cancel { .button.is-prompt-cancel {
background-color: #222; background-color: #222;
@@ -637,7 +610,8 @@
position: relative; position: relative;
background-color: transparent; background-color: transparent;
height: 44px; height: 44px;
width: 412px; min-width: 412px;
width: 100%;
border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15)); border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15));
border-radius: 6px; border-radius: 6px;
background: var(--element-hover-2, rgba(255, 255, 255, 0.06)); background: var(--element-hover-2, rgba(255, 255, 255, 0.06));
@@ -801,10 +775,18 @@
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5), box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset; 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
&:hover {
cursor: text;
}
&.focused { &.focused {
border-color: @term-green; border-color: @term-green;
} }
&.disabled {
opacity: 0.75;
}
&.error { &.error {
border-color: @term-red; border-color: @term-red;
} }
@@ -931,3 +913,135 @@
} }
} }
} }
.wave-button {
background: none;
color: inherit;
border: none;
padding: 0;
font: inherit;
cursor: pointer;
outline: inherit;
display: flex;
padding: 6px 16px;
align-items: center;
gap: 4px;
border-radius: 6px;
height: auto;
&:hover {
color: @term-white;
}
i {
fill: rgba(255, 255, 255, 0.12);
}
&.primary {
color: @term-green;
background: none;
i {
fill: @term-green;
}
&.solid {
color: @term-bright-white;
background: @term-green;
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
0px 0px 0.5px 0px rgba(255, 255, 255, 0.8) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.6) inset;
i {
fill: @term-white;
}
}
&.outlined {
border: 1px solid @term-green;
}
&.ghost {
// Styles for .ghost are already defined above
}
&:hover {
color: @term-bright-white;
}
}
&.secondary {
color: @term-white;
background: none;
&.solid {
background: rgba(255, 255, 255, 0.09);
box-shadow: none;
}
&.outlined {
border: 1px solid rgba(255, 255, 255, 0.09);
}
&.ghost {
padding: 6px 10px;
i {
fill: @term-green;
}
}
}
&.color-red {
&.solid {
border-color: @term-red;
background-color: mix(@term-red, @term-white, 50%);
box-shadow: none;
}
&.outlined {
color: @term-red;
border-color: @term-red;
}
&.ghost {
}
}
&.disabled {
opacity: 0.5;
}
&.link-button {
cursor: pointer;
}
}
.wave-status-container {
display: flex;
align-items: center;
.dot {
height: 6px;
width: 6px;
border-radius: 50%;
display: inline-block;
margin-right: 8px;
}
.dot.green {
background-color: @status-connected;
}
.dot.red {
background-color: @status-error;
}
.dot.gray {
background-color: @status-disconnected;
}
.dot.yellow {
background-color: @status-connecting;
}
}
+139 -2
View File
@@ -217,6 +217,112 @@ class Tooltip extends React.Component<TooltipProps, TooltipState> {
} }
} }
type ButtonVariantType = "outlined" | "solid" | "ghost";
type ButtonThemeType = "primary" | "secondary";
interface ButtonProps {
theme?: ButtonThemeType;
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
variant?: ButtonVariantType;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
color?: string;
}
class Button extends React.Component<ButtonProps> {
static defaultProps = {
theme: "primary",
variant: "solid",
color: "",
};
@boundMethod
handleClick() {
if (this.props.onClick && !this.props.disabled) {
this.props.onClick();
}
}
render() {
const { leftIcon, rightIcon, theme, children, disabled, variant, color } = this.props;
return (
<button
className={cn("wave-button", theme, variant, color, { disabled: disabled })}
onClick={this.handleClick}
disabled={disabled}
>
{leftIcon && <span className="icon-left">{leftIcon}</span>}
{children}
{rightIcon && <span className="icon-right">{rightIcon}</span>}
</button>
);
}
}
class IconButton extends Button {
render() {
const { children, theme, variant = "solid", ...rest } = this.props;
const className = `wave-button icon-button ${theme} ${variant}`;
return (
<button {...rest} className={className}>
{children}
</button>
);
}
}
export default IconButton;
interface LinkButtonProps extends ButtonProps {
href: string;
target?: string;
}
class LinkButton extends IconButton {
render() {
// @ts-ignore
const { href, target, leftIcon, rightIcon, children, theme, variant }: LinkButtonProps = this.props;
return (
<a href={href} target={target} className={`wave-button link-button`}>
<button {...this.props} className={`icon-button ${theme} ${variant}`}>
{leftIcon && <span className="icon-left">{leftIcon}</span>}
{children}
{rightIcon && <span className="icon-right">{rightIcon}</span>}
</button>
</a>
);
}
}
interface StatusProps {
status: "green" | "red" | "gray" | "yellow";
text: string;
}
class Status extends React.Component<StatusProps> {
@boundMethod
renderDot() {
const { status } = this.props;
return <div className={`dot ${status}`} />;
}
render() {
const { text } = this.props;
return (
<div className="wave-status-container">
{this.renderDot()}
<span>{text}</span>
</div>
);
}
}
interface TextFieldDecorationProps { interface TextFieldDecorationProps {
startDecoration?: React.ReactNode; startDecoration?: React.ReactNode;
endDecoration?: React.ReactNode; endDecoration?: React.ReactNode;
@@ -232,6 +338,7 @@ interface TextFieldProps {
required?: boolean; required?: boolean;
maxLength?: number; maxLength?: number;
autoFocus?: boolean; autoFocus?: boolean;
disabled?: boolean;
} }
interface TextFieldState { interface TextFieldState {
@@ -267,6 +374,22 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
} }
} }
// Method to handle focus at the component level
@boundMethod
handleComponentFocus() {
if (this.inputRef.current && !this.inputRef.current.contains(document.activeElement)) {
this.inputRef.current.focus();
}
}
// Method to handle blur at the component level
@boundMethod
handleComponentBlur() {
if (this.inputRef.current && this.inputRef.current.contains(document.activeElement)) {
this.inputRef.current.blur();
}
}
@boundMethod @boundMethod
handleFocus() { handleFocus() {
this.setState({ focused: true }); this.setState({ focused: true });
@@ -311,14 +434,23 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
} }
render() { render() {
const { label, value, placeholder, decoration, className, maxLength, autoFocus } = this.props; const { label, value, placeholder, decoration, className, maxLength, autoFocus, disabled } = this.props;
const { focused, internalValue, error } = this.state; const { focused, internalValue, error } = this.state;
// Decide if the input should behave as controlled or uncontrolled // Decide if the input should behave as controlled or uncontrolled
const inputValue = value !== undefined ? value : internalValue; const inputValue = value !== undefined ? value : internalValue;
return ( return (
<div className={cn(`wave-textfield ${className || ""}`, { focused: focused, error: error })}> <div
className={cn(`wave-textfield ${className || ""}`, {
focused: focused,
error: error,
disabled: disabled,
})}
onFocus={this.handleComponentFocus}
onBlur={this.handleComponentBlur}
tabIndex={-1}
>
{decoration?.startDecoration && <>{decoration.startDecoration}</>} {decoration?.startDecoration && <>{decoration.startDecoration}</>}
<div className="wave-textfield-inner"> <div className="wave-textfield-inner">
<label <label
@@ -341,6 +473,7 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
placeholder={placeholder} placeholder={placeholder}
maxLength={maxLength} maxLength={maxLength}
autoFocus={autoFocus} autoFocus={autoFocus}
disabled={disabled}
/> />
</div> </div>
{decoration?.endDecoration && <>{decoration.endDecoration}</>} {decoration?.endDecoration && <>{decoration.endDecoration}</>}
@@ -980,4 +1113,8 @@ export {
NumberField, NumberField,
PasswordField, PasswordField,
Tooltip, Tooltip,
Button,
IconButton,
LinkButton,
Status,
}; };
+202 -16
View File
@@ -460,7 +460,7 @@
.action-buttons { .action-buttons {
display: flex; display: flex;
div.button { button:first-child {
margin-right: 8px; margin-right: 8px;
} }
} }
@@ -468,29 +468,215 @@
} }
} }
.wave-button { .wave-modal.rconndetail-modal {
display: flex; .wave-modal-content.rconndetail-wave-modal-content {
padding: 6px 16px; width: 631px;
align-items: center; min-height: 565px;
gap: var(--sizing-2-xs, 4px); overflow: visible;
border-radius: 6px;
height: auto; .wave-modal-content-inner.rconndetail-wave-modal-content-inner {
display: flex;
padding-bottom: 0px;
flex-direction: column;
align-items: center;
gap: 20px;
flex-shrink: 0;
.rconndetail-wave-modal-body {
display: flex;
padding: 0px 20px;
align-items: flex-start;
width: 100%;
display: flex;
flex-direction: column;
gap: 16px;
align-self: stretch;
.name-header-actions-wrapper {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
.rconndetail-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;
}
}
}
.remote-detail {
.settings-field {
display: flex;
flex-direction: row;
align-items: center;
.settings-label {
font-weight: bold;
width: 12em;
display: flex;
flex-direction: row;
align-items: center;
}
.settings-input {
display: flex;
flex-direction: row;
align-items: center;
color: @term-white;
}
}
.settings-field:not(:first-child) {
margin-top: 4px;
}
.status {
display: flex;
height: 30px;
padding: 3px 8px;
align-items: center;
gap: 8px;
align-self: stretch;
border-radius: 6px;
background: rgba(241, 246, 243, 0.08);
}
.terminal-wrapper {
width: 100%;
margin-top: 5px;
.terminal-connectelem {
height: 163px !important; // Needed to override plugin height
.xterm-viewport {
display: flex;
padding: 6px 10px;
gap: 8px;
align-items: flex-start;
align-self: stretch;
border-radius: 6px;
border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15));
background: #080a08;
height: 163px !important; // Needed to override plugin height
}
.xterm-screen {
padding: 10px;
width: 541px !important; // Needed to override plugin width
}
}
}
}
}
}
.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-button.color-green { .wave-modal.erconn-modal {
color: @term-bright-white; .wave-modal-content.erconn-wave-modal-content {
background: @term-green !important; // !important is needed to override the default button color width: 502px;
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5), min-height: 411px;
0px 0px 0.5px 0px rgba(255, 255, 255, 0.8) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.6) inset; overflow: visible;
&:hover { .wave-modal-content-inner.erconn-wave-modal-content-inner {
color: @term-bright-white; 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 { .wave-button.color-standard {
color: @term-white; color: @term-white;
background: var(--overlays-white-6, rgba(255, 255, 255, 0.12)); background: rgba(255, 255, 255, 0.12);
&:hover { &:hover {
color: @term-white; color: @term-white;
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -10,7 +10,7 @@ import cn from "classnames";
import { GlobalModel, GlobalCommandRunner, TabColors, MinFontSize, MaxFontSize } from "../../../model/model"; import { GlobalModel, GlobalCommandRunner, TabColors, MinFontSize, MaxFontSize } from "../../../model/model";
import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common"; import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common";
import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types"; import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types";
import { ConnectionDropdown } from "../../connections/connections"; import { ConnectionDropdown } from "../../connections_deprecated/connections";
import { PluginModel } from "../../../plugins/plugins"; import { PluginModel } from "../../../plugins/plugins";
import * as util from "../../../util/util"; import * as util from "../../../util/util";
import { commandRtnHandler } from "../../../util/util"; import { commandRtnHandler } from "../../../util/util";
+82 -383
View File
@@ -1,408 +1,107 @@
@import "../../app/common/themes/themes.less"; @import "../../app/common/themes/themes.less";
.modal.prompt-modal.remotes-modal { .connections-view {
.modal-content { background-color: @background-session;
min-width: 850px; flex-grow: 1;
} display: flex;
.icon { flex-direction: column;
width: 1em; position: relative;
height: 1em; overflow: auto;
fill: @base-color; margin-bottom: 10px;
margin: 0; margin-right: 10px;
}
.button {
svg {
float: right;
margin-top: 0.3em;
margin-right: 0;
}
}
.dropdown,
.button {
display: inline-flex;
}
.dropdown .button {
border: none !important;
}
.inner-content {
display: flex;
flex-direction: row;
align-items: stretch;
padding: 0;
min-height: 45em;
max-height: 45em;
.remotes-menu {
flex: 0 0 200px;
border-right: 1px solid @disabled-color;
overflow-y: auto;
.remote-menu-item {
border-top: 1px solid @disabled-color;
padding: 0.5em;
display: flex;
flex-direction: row;
cursor: pointer;
&.add-remote {
padding: 10px 5px 10px 5px;
}
&:hover {
background-color: #333;
}
&.is-selected {
background-color: @active-menu-color;
.remote-name .remote-name-secondary {
color: @term-white;
}
}
&:first-child {
border-top: 0;
}
.remote-status-light {
width: 2em;
margin-top: 0.7em;
margin-right: 0.7em;
font-size: 0.8em;
}
.remote-name {
flex-grow: 1;
.remote-name-primary {
font-weight: bold;
max-width: 10em;
margin-right: 1em;
}
.remote-name-secondary {
color: @disabled-color;
max-width: 14em;
}
}
}
}
.remote-detail {
padding: 10px;
flex-grow: 1;
display: flex;
flex-direction: column;
.settings-field {
margin-top: 0.75em;
}
* {
flex-shrink: 0;
}
.detail-subtitle {
margin-bottom: 10px;
margin-top: 10px;
}
.title {
color: @term-white;
padding: 0.75em 0;
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;
border-radius: 0 0 5px 5px;
.xterm-rows {
padding-top: 0.5em;
}
}
.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;
border: 1px solid #777;
border-bottom: none;
.message-row {
display: flex;
flex-direction: row;
align-items: center;
svg {
vertical-align: text-bottom;
}
}
.remote-status {
position: relative;
top: -1px;
}
.button {
height: 22px;
}
}
.settings-field {
.update-auth-button {
visibility: hidden;
}
&:hover {
.update-auth-button {
visibility: visible;
}
.hide-hover {
display: none;
}
}
}
&.auth-editing,
&.create-remote {
.settings-field.align-top {
align-items: flex-start;
.settings-label {
margin-top: 8px;
}
.settings-input {
align-items: flex-start;
}
}
.settings-label {
display: flex;
flex-direction: row;
align-items: center;
width: 12em !important;
}
.settings-field .settings-input .undo-icon {
cursor: pointer;
margin-left: 5px;
}
.editremote-dropdown .dropdown-trigger button {
width: 120px;
justify-content: flex-start;
color: @base-color;
border: none;
&:hover {
box-shadow: none;
}
}
.settings-field .raw-input {
width: 120px;
}
.settings-input input {
background: rgba(255, 255, 255, 0.8);
width: 250px;
outline: none;
}
.dropdown .dropdown-item {
padding: 5px 5px 5px 12px;
}
.dropdown .dropdown-content {
max-width: 10.6em;
}
.settings-input {
.info-message {
margin-left: 22px;
}
}
.settings-label {
.info-message {
margin-right: 15px;
}
}
}
}
}
.terminal-wrapper {
position: relative;
padding: 2px 10px 5px 4px;
margin: 5px 5px 10px 5px;
box-shadow: 0 0 1px 1px rgba(255, 255, 255, 0.3);
&.focus {
box-shadow: 0 0 3px 3px rgba(255, 255, 255, 0.3);
}
.term-tag {
position: absolute;
top: 0;
right: 0;
background-color: @term-red;
color: @term-white;
z-index: 110;
padding: 4px;
}
}
}
.dropdown.conn-dropdown {
padding-left: 0;
border-radius: 8px; border-radius: 8px;
background-color: rgba(241, 246, 243, 0.08); border: 1px solid rgba(241, 246, 243, 0.08);
background: var(--element-window, rgba(13, 13, 13, 0.85));
.conn-dd-trigger { .header {
margin: 24px 18px;
display: flex; display: flex;
flex-direction: row; justify-content: space-between;
width: 413px;
padding: 6px 8px 6px 12px;
align-items: center; align-items: center;
height: 42px;
.lefticon { .connections-title {
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;
}
}
.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 { .no-items {
display: flex; display: flex;
width: 413px; flex-direction: row;
padding: 6px; justify-content: center;
flex-direction: column; padding: 30px 0 30px 0;
align-items: flex-start; border: 1px solid white;
border-radius: 8px; border-radius: 3px;
background-color: @dropdown-menu; margin: 20px 50px 20px 20px;
}
.dropdown-item { .connections-table {
display: flex; margin: 0px 10px 10px 10px;
padding: 5px 12px 5px 8px; table-layout: fixed;
align-items: center; max-width: 970px;
gap: 8px;
align-self: stretch;
border-radius: 6px;
.status-div { colgroup {
display: flex; .first-col {
align-items: center; max-width: 650px;
justify-content: center;
width: 16px;
height: 16px;
padding: 3px;
svg.status-icon {
width: 10px;
height: 10px;
}
} }
.second-col {
.add-div { max-width: 150px;
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
svg.add-icon {
width: 16px;
height: 16px;
path {
fill: @text-primary;
}
}
} }
.third-col {
max-width: 200px;
}
}
.text-standard { thead {
border-radius: var(--sizing-2-xs, 4px);
border-top: 1px solid rgba(250, 250, 250, 0.1);
border-bottom: 1px solid rgba(241, 246, 243, 0.15);
background: var(--opacity-zinc-502, rgba(250, 250, 250, 0.02));
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
th {
height: 32px;
padding: 5px 15px 5px 10px;
color: @text-secondary; color: @text-secondary;
} }
}
.text-caption { tr.connections-item {
color: @text-caption; border-bottom: 1px solid rgba(241, 246, 243, 0.15);
} color: @text-secondary;
cursor: pointer;
.ellipsis {
text-overflow: ellipsis;
}
&:hover { &:hover {
background-color: rgba(241, 246, 243, 0.08); background: rgba(255, 255, 255, 0.06);
td.bookmark i {
display: block;
}
}
td {
height: 40px;
padding: 5px 15px 5px 10px;
vertical-align: middle;
.action-buttons {
display: flex;
visibility: hidden;
}
}
&.hovered {
.action-buttons {
visibility: visible;
}
} }
} }
} }
footer {
margin-left: 10px;
}
.help-entry {
margin: 1em 2em;
}
} }
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,408 @@
@import "../../app/common/themes/themes.less";
.modal.prompt-modal.remotes-modal {
.modal-content {
min-width: 850px;
}
.icon {
width: 1em;
height: 1em;
fill: @base-color;
margin: 0;
}
.button {
svg {
float: right;
margin-top: 0.3em;
margin-right: 0;
}
}
.dropdown,
.button {
display: inline-flex;
}
.dropdown .button {
border: none !important;
}
.inner-content {
display: flex;
flex-direction: row;
align-items: stretch;
padding: 0;
min-height: 45em;
max-height: 45em;
.remotes-menu {
flex: 0 0 200px;
border-right: 1px solid @disabled-color;
overflow-y: auto;
.remote-menu-item {
border-top: 1px solid @disabled-color;
padding: 0.5em;
display: flex;
flex-direction: row;
cursor: pointer;
&.add-remote {
padding: 10px 5px 10px 5px;
}
&:hover {
background-color: #333;
}
&.is-selected {
background-color: @active-menu-color;
.remote-name .remote-name-secondary {
color: @term-white;
}
}
&:first-child {
border-top: 0;
}
.remote-status-light {
width: 2em;
margin-top: 0.7em;
margin-right: 0.7em;
font-size: 0.8em;
}
.remote-name {
flex-grow: 1;
.remote-name-primary {
font-weight: bold;
max-width: 10em;
margin-right: 1em;
}
.remote-name-secondary {
color: @disabled-color;
max-width: 14em;
}
}
}
}
.remote-detail {
padding: 10px;
flex-grow: 1;
display: flex;
flex-direction: column;
.settings-field {
margin-top: 0.75em;
}
* {
flex-shrink: 0;
}
.detail-subtitle {
margin-bottom: 10px;
margin-top: 10px;
}
.title {
color: @term-white;
padding: 0.75em 0;
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;
border-radius: 0 0 5px 5px;
.xterm-rows {
padding-top: 0.5em;
}
}
.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;
border: 1px solid #777;
border-bottom: none;
.message-row {
display: flex;
flex-direction: row;
align-items: center;
svg {
vertical-align: text-bottom;
}
}
.remote-status {
position: relative;
top: -1px;
}
.button {
height: 22px;
}
}
.settings-field {
.update-auth-button {
visibility: hidden;
}
&:hover {
.update-auth-button {
visibility: visible;
}
.hide-hover {
display: none;
}
}
}
&.auth-editing,
&.create-remote {
.settings-field.align-top {
align-items: flex-start;
.settings-label {
margin-top: 8px;
}
.settings-input {
align-items: flex-start;
}
}
.settings-label {
display: flex;
flex-direction: row;
align-items: center;
width: 12em !important;
}
.settings-field .settings-input .undo-icon {
cursor: pointer;
margin-left: 5px;
}
.editremote-dropdown .dropdown-trigger button {
width: 120px;
justify-content: flex-start;
color: @base-color;
border: none;
&:hover {
box-shadow: none;
}
}
.settings-field .raw-input {
width: 120px;
}
.settings-input input {
background: rgba(255, 255, 255, 0.8);
width: 250px;
outline: none;
}
.dropdown .dropdown-item {
padding: 5px 5px 5px 12px;
}
.dropdown .dropdown-content {
max-width: 10.6em;
}
.settings-input {
.info-message {
margin-left: 22px;
}
}
.settings-label {
.info-message {
margin-right: 15px;
}
}
}
}
}
.terminal-wrapper {
position: relative;
padding: 2px 10px 5px 4px;
margin: 5px 5px 10px 5px;
box-shadow: 0 0 1px 1px rgba(255, 255, 255, 0.3);
&.focus {
box-shadow: 0 0 3px 3px rgba(255, 255, 255, 0.3);
}
.term-tag {
position: absolute;
top: 0;
right: 0;
background-color: @term-red;
color: @term-white;
z-index: 110;
padding: 4px;
}
}
}
.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;
}
}
.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;
}
}
.add-div {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
svg.add-icon {
width: 16px;
height: 16px;
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);
}
}
}
}
File diff suppressed because it is too large Load Diff
+12 -8
View File
@@ -110,6 +110,15 @@ class MainSideBar extends React.Component<{}, {}> {
GlobalCommandRunner.bookmarksView(); GlobalCommandRunner.bookmarksView();
} }
@boundMethod
handleConnectionsClick(): void {
if (GlobalModel.activeMainView.get() == "connections") {
GlobalModel.showSessionView();
return;
}
GlobalCommandRunner.connectionsView();
}
@boundMethod @boundMethod
handleWebSharingClick(): void { handleWebSharingClick(): void {
if (GlobalModel.activeMainView.get() == "webshare") { if (GlobalModel.activeMainView.get() == "webshare") {
@@ -126,11 +135,6 @@ class MainSideBar extends React.Component<{}, {}> {
})(); })();
} }
@boundMethod
handleConnectionsClick(): void {
GlobalModel.remotesModalModel.openModal();
}
@boundMethod @boundMethod
openSessionSettings(e: any, session: Session): void { openSessionSettings(e: any, session: Session): void {
e.preventDefault(); e.preventDefault();
@@ -199,14 +203,14 @@ class MainSideBar extends React.Component<{}, {}> {
<div className="logo"> <div className="logo">
<If condition={isCollapsed}> <If condition={isCollapsed}>
<div className="logo-container" onClick={this.toggleCollapsed}> <div className="logo-container" onClick={this.toggleCollapsed}>
<img src="public/logos/wave-logo.png"/> <img src="public/logos/wave-logo.png" />
</div> </div>
</If> </If>
<If condition={!isCollapsed}> <If condition={!isCollapsed}>
<div className="logo-container"> <div className="logo-container">
<img src="public/logos/wave-dark.png"/> <img src="public/logos/wave-dark.png" />
</div> </div>
<div className="spacer"/> <div className="spacer" />
<div className="collapse-button" onClick={this.toggleCollapsed}> <div className="collapse-button" onClick={this.toggleCollapsed}>
<LeftChevronIcon className="icon" /> <LeftChevronIcon className="icon" />
</div> </div>
+3 -3
View File
@@ -19,7 +19,7 @@ import { getRemoteStr } from "../../common/prompt/prompt";
import { GlobalModel, ScreenLines, Screen, Session } from "../../../model/model"; import { GlobalModel, ScreenLines, Screen, Session } from "../../../model/model";
import { Line } from "../../line/linecomps"; import { Line } from "../../line/linecomps";
import { LinesView } from "../../line/linesview"; import { LinesView } from "../../line/linesview";
import { ConnectionDropdown } from "../../connections/connections"; import { ConnectionDropdown } from "../../connections_deprecated/connections";
import * as util from "../../../util/util"; import * as util from "../../../util/util";
import { TextField, InputDecoration } from "../../common/common"; import { TextField, InputDecoration } from "../../common/common";
import { ReactComponent as EllipseIcon } from "../../assets/icons/ellipse.svg"; import { ReactComponent as EllipseIcon } from "../../assets/icons/ellipse.svg";
@@ -101,7 +101,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
@boundMethod @boundMethod
clickNewConnection(): void { clickNewConnection(): void {
GlobalModel.remotesModalModel.openModalForEdit({ remoteedit: true, old: false }, true); GlobalModel.remotesModel.openAddModal({ remoteedit: true });
} }
renderTabIconSelector(): React.ReactNode { renderTabIconSelector(): React.ReactNode {
@@ -117,7 +117,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
<div className="text-s1 unselectable">Select the icon</div> <div className="text-s1 unselectable">Select the icon</div>
<div className="control-iconlist tabicon-list"> <div className="control-iconlist tabicon-list">
<div key="square" className="icondiv" title="square" onClick={() => this.selectTabIcon("square")}> <div key="square" className="icondiv" title="square" onClick={() => this.selectTabIcon("square")}>
<SquareIcon className="icon square-icon"/> <SquareIcon className="icon square-icon" />
</div> </div>
<For each="icon" of={TabIcons}> <For each="icon" of={TabIcons}>
<div <div
+226 -13
View File
@@ -767,7 +767,7 @@ class Screen {
e.preventDefault(); e.preventDefault();
let p = navigator.clipboard.readText(); let p = navigator.clipboard.readText();
p.then((text) => { p.then((text) => {
termWrap.dataHandler?.(text); termWrap.dataHandler?.(text, termWrap);
}); });
return false; return false;
} }
@@ -2203,6 +2203,14 @@ class HistoryViewModel {
} }
} }
class ConnectionsViewModel {
showConnectionsView(): void {
mobx.action(() => {
GlobalModel.activeMainView.set("connections");
})();
}
}
class BookmarksModel { class BookmarksModel {
bookmarks: OArr<BookmarkType> = mobx.observable.array([], { bookmarks: OArr<BookmarkType> = mobx.observable.array([], {
name: "Bookmarks", name: "Bookmarks",
@@ -2700,6 +2708,202 @@ 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;
remoteTermWrapFocus: OV<boolean> = mobx.observable.box(false, {
name: "RemotesModel-remoteTermWrapFocus",
});
showNoInputMsg: OV<boolean> = mobx.observable.box(false, {
name: "RemotesModel-showNoInputMg",
});
showNoInputTimeoutId: any = null;
remoteEdit: OV<RemoteEditType> = mobx.observable.box(null, {
name: "RemotesModel-remoteEdit",
});
recentConnAddedState: OV<boolean> = mobx.observable.box(false, {
name: "RemotesModel-recentlyAdded",
});
isOpen(): boolean {
return this.modalMode.get() != null;
}
get recentConnAdded(): boolean {
return this.recentConnAddedState.get();
}
seRecentConnAdded(value: boolean) {
this.recentConnAddedState.set(value);
}
deSelectRemote(): void {
mobx.action(() => {
this.selectedRemoteId.set(null);
this.remoteEdit.set(null);
})();
}
openReadModal(remoteId: string): void {
mobx.action(() => {
this.selectedRemoteId.set(remoteId);
this.remoteEdit.set(null);
this.modalMode.set("read");
})();
}
openAddModal(redit: RemoteEditType): void {
mobx.action(() => {
this.remoteEdit.set(redit);
this.modalMode.set("add");
})();
}
openEditModal(redit?: RemoteEditType): void {
if (redit === undefined) {
this.startEditAuth();
}
if (redit != null) {
mobx.action(() => {
this.selectedRemoteId.set(redit.remoteid);
this.remoteEdit.set(redit);
this.modalMode.set("edit");
})();
}
}
selectRemote(remoteId: string): void {
if (this.selectedRemoteId.get() == remoteId) {
return;
}
mobx.action(() => {
this.selectedRemoteId.set(remoteId);
this.remoteEdit.set(null);
})();
}
@boundMethod
startEditAuth(): void {
let remoteId = this.selectedRemoteId.get();
if (remoteId != null) {
GlobalCommandRunner.openEditRemote(remoteId);
}
}
getModalMode(): string {
return this.modalMode.get();
}
isAuthEditMode(): boolean {
return this.remoteEdit.get() != null;
}
@boundMethod
closeModal(): void {
mobx.action(() => {
this.modalMode.set(null);
this.selectedRemoteId.set(null);
})();
setTimeout(() => GlobalModel.refocus(), 10);
}
disposeTerm(): void {
if (this.remoteTermWrap == null) {
return;
}
this.remoteTermWrap.dispose();
this.remoteTermWrap = null;
mobx.action(() => {
this.remoteTermWrapFocus.set(false);
})();
}
receiveData(remoteId: string, ptyPos: number, ptyData: Uint8Array, reason?: string) {
if (this.remoteTermWrap == null) {
return;
}
if (this.remoteTermWrap.getContextRemoteId() != remoteId) {
return;
}
this.remoteTermWrap.receiveData(ptyPos, ptyData);
}
@boundMethod
setRemoteTermWrapFocus(focus: boolean): void {
mobx.action(() => {
this.remoteTermWrapFocus.set(focus);
})();
}
@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
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);
}
createTermWrap(elem: HTMLElement): void {
this.disposeTerm();
let remoteId = this.selectedRemoteId.get();
if (remoteId == null) {
return;
}
let termOpts = {
rows: RemotePtyRows,
cols: RemotePtyCols,
flexrows: false,
maxptysize: 64 * 1024,
};
let termWrap = 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,
});
this.remoteTermWrap = termWrap;
}
}
class Model { class Model {
clientId: string; clientId: string;
activeSessionId: OV<string> = mobx.observable.box(null, { activeSessionId: OV<string> = mobx.observable.box(null, {
@@ -2729,9 +2933,10 @@ class Model {
authKey: string; authKey: string;
isDev: boolean; isDev: boolean;
platform: string; platform: string;
activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare"> = mobx.observable.box("session", { activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare" | "connections"> =
name: "activeMainView", mobx.observable.box("session", {
}); name: "activeMainView",
});
termFontSize: CV<number>; termFontSize: CV<number>;
alertMessage: OV<AlertMessageType> = mobx.observable.box(null, { alertMessage: OV<AlertMessageType> = mobx.observable.box(null, {
name: "alertMessage", name: "alertMessage",
@@ -2753,11 +2958,13 @@ class Model {
name: "lineSettingsModal", name: "lineSettingsModal",
}); // linenum }); // linenum
remotesModalModel: RemotesModalModel; remotesModalModel: RemotesModalModel;
remotesModel: RemotesModel;
inputModel: InputModel; inputModel: InputModel;
pluginsModel: PluginsModel; pluginsModel: PluginsModel;
bookmarksModel: BookmarksModel; bookmarksModel: BookmarksModel;
historyViewModel: HistoryViewModel; historyViewModel: HistoryViewModel;
connectionViewModel: ConnectionsViewModel;
clientData: OV<ClientDataType> = mobx.observable.box(null, { clientData: OV<ClientDataType> = mobx.observable.box(null, {
name: "clientData", name: "clientData",
}); });
@@ -2777,7 +2984,9 @@ class Model {
this.pluginsModel = new PluginsModel(); this.pluginsModel = new PluginsModel();
this.bookmarksModel = new BookmarksModel(); this.bookmarksModel = new BookmarksModel();
this.historyViewModel = new HistoryViewModel(); this.historyViewModel = new HistoryViewModel();
this.connectionViewModel = new ConnectionsViewModel();
this.remotesModalModel = new RemotesModalModel(); this.remotesModalModel = new RemotesModalModel();
this.remotesModel = new RemotesModel();
let isWaveSrvRunning = getApi().getWaveSrvStatus(); let isWaveSrvRunning = getApi().getWaveSrvStatus();
this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, { this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, {
name: "model-wavesrv-running", name: "model-wavesrv-running",
@@ -3003,8 +3212,8 @@ class Model {
GlobalModel.screenSettingsModal.set(null); GlobalModel.screenSettingsModal.set(null);
didSomething = true; didSomething = true;
} }
if (GlobalModel.remotesModalModel.isOpen()) { if (GlobalModel.remotesModel.isOpen()) {
GlobalModel.remotesModalModel.closeModal(); GlobalModel.remotesModel.closeModal();
didSomething = true; didSomething = true;
} }
if (GlobalModel.clientSettingsModal.get()) { if (GlobalModel.clientSettingsModal.get()) {
@@ -3213,7 +3422,7 @@ class Model {
} else { } else {
// remote update // remote update
let ptyData = base64ToArray(ptyMsg.ptydata64); let ptyData = base64ToArray(ptyMsg.ptydata64);
this.remotesModalModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData); this.remotesModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData);
} }
return; return;
} }
@@ -3277,6 +3486,9 @@ class Model {
this.remotes.clear(); this.remotes.clear();
} }
this.updateRemotes(update.remotes); this.updateRemotes(update.remotes);
if (update.remotes?.length && this.remotesModel.recentConnAddedState.get()) {
this.remotesModel.openReadModal(update.remotes[0].remoteid);
}
} }
if ("mainview" in update) { if ("mainview" in update) {
if (update.mainview == "plugins") { if (update.mainview == "plugins") {
@@ -3302,12 +3514,8 @@ class Model {
} }
if (interactive && "remoteview" in update) { if (interactive && "remoteview" in update) {
let rview: RemoteViewType = update.remoteview; let rview: RemoteViewType = update.remoteview;
if (rview.remoteshowall) { if (rview.remoteedit != null) {
this.remotesModalModel.openModal(); this.remotesModel.openEditModal({ ...rview.remoteedit });
} else if (rview.remoteedit != null) {
this.remotesModalModel.openModalForEdit({ ...rview.remoteedit, old: true }, false);
} else if (rview.ptyremoteid) {
this.remotesModalModel.openModal(rview.ptyremoteid);
} }
} }
if ("cmdline" in update) { if ("cmdline" in update) {
@@ -4037,6 +4245,10 @@ class CommandRunner {
GlobalModel.submitCommand("bookmarks", "show", null, { nohist: "1" }, true); GlobalModel.submitCommand("bookmarks", "show", null, { nohist: "1" }, true);
} }
connectionsView() {
GlobalModel.connectionViewModel.showConnectionsView();
}
historyView(params: HistorySearchParams) { historyView(params: HistorySearchParams) {
let kwargs = { nohist: "1" }; let kwargs = { nohist: "1" };
kwargs["offset"] = String(params.offset); kwargs["offset"] = String(params.offset);
@@ -4214,6 +4426,7 @@ export {
RemoteColors, RemoteColors,
getTermPtyData, getTermPtyData,
RemotesModalModel, RemotesModalModel,
RemotesModel,
MinFontSize, MinFontSize,
MaxFontSize, MaxFontSize,
}; };