Merge pull request #119 from wavetermdev/dev-0.5.1

pull dev-0.5.1 branch to main
This commit is contained in:
Mike Sawka 2023-12-04 09:55:11 -08:00 committed by GitHub
commit 08762fdfc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 4212 additions and 2349 deletions

View File

@ -33,7 +33,7 @@ WAVETERM_DEV=1 PCLOUD_ENDPOINT="https://ot2e112zx5.execute-api.us-west-2.amazona
```bash
# @scripthaus command typecheck
# @scripthaus cd :playbook
node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/index.ts src/types/custom.d.ts
node_modules/.bin/tsc --noEmit
```
```bash

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 {
font-size: 12.5px;
font-weight: 300;
@ -80,6 +88,15 @@ textarea {
height: 16px;
}
body a {
color: @wave-green;
cursor: pointer;
text-decoration: none;
&:hover {
color: @wave-green;
}
}
body code {
font-family: @terminal-font;
}
@ -148,10 +165,10 @@ svg.icon {
border-radius: 5px;
cursor: pointer;
background-color: @button-background !important;
color: @prompt-green;
color: @wave-green;
.hoverEffect;
&:hover {
color: @prompt-green;
color: @wave-green;
}
&.disabled {
color: fade(@disabled-color, 60%);
@ -176,12 +193,6 @@ svg.icon {
left: 0;
}
.content {
a:hover {
color: #485fc7;
}
}
input[type="checkbox"] {
cursor: pointer;
}
@ -219,7 +230,8 @@ a.a-block {
.history-view,
.bookmarks-view,
.plugins-view {
.plugins-view,
.connections-view {
flex-grow: 1;
display: flex;
flex-direction: column;
@ -465,85 +477,99 @@ a.a-block {
}
.icon.color-red {
path, circle {
path,
circle {
fill: @tab-red;
}
}
.icon.color-green {
path, circle {
path,
circle {
fill: @tab-green;
}
}
.icon.color-orange {
path, circle {
path,
circle {
fill: @tab-orange;
}
}
.icon.color-blue {
path, circle {
path,
circle {
fill: @tab-blue;
}
}
.icon.color-yellow {
path, circle {
path,
circle {
fill: @tab-yellow;
}
}
.icon.color-pink {
path, circle {
path,
circle {
fill: @tab-pink;
}
}
.icon.color-mint {
path, circle {
path,
circle {
fill: @tab-mint;
}
}
.icon.color-cyan {
path, circle {
path,
circle {
fill: @tab-cyan;
}
}
.icon.color-violet {
path, circle {
path,
circle {
fill: @tab-violet;
}
}
.icon.color-white {
path, circle {
path,
circle {
fill: @tab-white;
}
}
.status-icon.status-connected {
path, circle {
path,
circle {
fill: @status-connected;
}
}
.status-icon.status-connecting {
path, circle {
path,
circle {
fill: @status-connecting;
}
}
.status-icon.status-disconnected {
path, circle {
path,
circle {
fill: @status-disconnected;
}
}
.status-icon.status-error {
path, circle {
path,
circle {
fill: @status-error;
}
}

View File

@ -15,22 +15,16 @@ import { WorkspaceView } from "./workspace/workspaceview";
import { PluginsView } from "./pluginsview/pluginsview";
import { BookmarksView } from "./bookmarks/bookmarks";
import { HistoryView } from "./history/history";
import { ConnectionsView } from "./connections/connections";
import {
ScreenSettingsModal,
SessionSettingsModal,
LineSettingsModal,
ClientSettingsModal,
} from "./common/modals/settings";
import { RemotesModal } from "./connections/connections";
import { TosModal } from "./common/modals/modals";
import { MainSideBar } from "./sidebar/sidebar";
import {
DisconnectedModal,
ClientStopModal,
AlertModal,
AboutModal,
CreateRemoteConnModal,
} from "./common/modals/modals";
import { DisconnectedModal, ClientStopModal, ModalsProvider } from "./common/modals/modals";
import { ErrorBoundary } from "./common/error/errorboundary";
import "./app.less";
@ -64,7 +58,7 @@ class App extends React.Component<{}, {}> {
opts.showCut = true;
}
let sel = window.getSelection();
if (!isBlank(sel.toString())) {
if (!isBlank(sel?.toString())) {
GlobalModel.contextEditMenu(e, opts);
} else {
if (isInNonTermInput) {
@ -85,14 +79,12 @@ class App extends React.Component<{}, {}> {
let sessionSettingsModal = GlobalModel.sessionSettingsModal.get();
let lineSettingsModal = GlobalModel.lineSettingsModal.get();
let clientSettingsModal = GlobalModel.clientSettingsModal.get();
let remotesModel = GlobalModel.remotesModalModel;
let remotesModal = remotesModel.isOpen();
let selectedRemoteId = remotesModel.selectedRemoteId.get();
let remoteEdit = remotesModel.remoteEdit.get();
let remotesModel = GlobalModel.remotesModel;
let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get();
let hasClientStop = GlobalModel.getHasClientStop();
let dcWait = this.dcWait.get();
let platform = GlobalModel.getPlatform();
if (disconnected || hasClientStop) {
if (!dcWait) {
setTimeout(() => this.updateDcWait(true), 1500);
@ -117,7 +109,6 @@ class App extends React.Component<{}, {}> {
if (dcWait) {
setTimeout(() => this.updateDcWait(false), 0);
}
//console.log(`GlobalModel.activeMainView.get() = ${GlobalModel.activeMainView.get()}`); // @mike - if I remove this, I cant see plugins
return (
<div id="main" className={"platform-" + platform} onContextMenu={this.handleContextMenu}>
<div className="main-content">
@ -127,18 +118,13 @@ class App extends React.Component<{}, {}> {
<WorkspaceView />
<HistoryView />
<BookmarksView />
<ConnectionsView model={remotesModel} />
</ErrorBoundary>
</div>
<AlertModal />
<If condition={GlobalModel.needsTos()}>
<TosModal />
</If>
<If condition={GlobalModel.aboutModalOpen.get()}>
<AboutModal />
</If>
<If condition={remoteEdit !== null && !remoteEdit.old}>
<CreateRemoteConnModal model={remotesModel} remoteEdit={remoteEdit} />
</If>
<ModalsProvider />
<If condition={screenSettingsModal != null}>
<ScreenSettingsModal
key={screenSettingsModal.sessionId + ":" + screenSettingsModal.screenId}
@ -155,9 +141,6 @@ class App extends React.Component<{}, {}> {
<If condition={clientSettingsModal}>
<ClientSettingsModal />
</If>
<If condition={remotesModal}>
<RemotesModal model={GlobalModel.remotesModalModel} />
</If>
</div>
);
}

5
src/app/appconst.ts Normal file
View File

@ -0,0 +1,5 @@
export const ABOUT = "about";
export const CREATE_REMOTE = "createRemote";
export const VIEW_REMOTE = "viewRemote";
export const EDIT_REMOTE = "editRemote";
export const ALERT = "alert";

View File

@ -247,7 +247,7 @@
}
}
.button.is-prompt-green {
.button.is-wave-green {
background-color: #222;
color: @term-white;
@ -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-prompt-cancel {
background-color: #222;
@ -637,7 +610,8 @@
position: relative;
background-color: transparent;
height: 44px;
width: 412px;
min-width: 412px;
width: 100%;
border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15));
border-radius: 6px;
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),
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 {
border-color: @term-green;
}
&.disabled {
opacity: 0.75;
}
&.error {
border-color: @term-red;
}
@ -931,3 +913,228 @@
}
}
}
.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-yellow {
&.solid {
border-color: @warning-yellow;
background-color: mix(@warning-yellow, @term-white, 50%);
box-shadow: none;
}
&.outlined {
color: @warning-yellow;
border-color: @warning-yellow;
&:hover {
color: @term-white;
border-color: @term-white;
}
}
&.ghost {
}
}
&.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;
}
}
.wave-modal-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 500;
.wave-modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(21, 23, 21, 0.7);
z-index: 1;
}
}
.wave-modal {
z-index: 2;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
border-radius: 10px;
background: #151715;
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45),
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
.wave-modal-content {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
.wave-modal-header {
width: 100%;
display: flex;
align-items: center;
padding: 12px 14px 12px 20px;
justify-content: space-between;
line-height: 20px;
border-bottom: 1px solid rgba(250, 250, 250, 0.1);
button {
i {
font-size: 18px;
}
}
}
.wave-modal-body {
width: 100%;
padding: 0px 20px;
}
.wave-modal-footer {
display: flex;
justify-content: flex-end;
width: 100%;
padding: 0 20px 20px;
button:first-child {
margin-right: 8px;
}
}
}
}

View File

@ -99,7 +99,7 @@ class Toggle extends React.Component<{ checked: boolean; onChange: (value: boole
}
class Checkbox extends React.Component<
{ checked: boolean; onChange: (value: boolean) => void; label: string; id: string },
{ checked: boolean; onChange: (value: boolean) => void; label: React.ReactNode; id: string },
{}
> {
render() {
@ -217,6 +217,115 @@ 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;
style?: React.CSSProperties;
}
class Button extends React.Component<ButtonProps> {
static defaultProps = {
theme: "primary",
variant: "solid",
color: "",
style: {},
};
@boundMethod
handleClick() {
if (this.props.onClick && !this.props.disabled) {
this.props.onClick();
}
}
render() {
const { leftIcon, rightIcon, theme, children, disabled, variant, color, style } = this.props;
return (
<button
className={cn("wave-button", theme, variant, color, { disabled: disabled })}
onClick={this.handleClick}
disabled={disabled}
style={style}
>
{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 {
startDecoration?: React.ReactNode;
endDecoration?: React.ReactNode;
@ -232,6 +341,7 @@ interface TextFieldProps {
required?: boolean;
maxLength?: number;
autoFocus?: boolean;
disabled?: boolean;
}
interface TextFieldState {
@ -242,7 +352,6 @@ interface TextFieldState {
hasContent: boolean;
}
@mobxReact.observer
class TextField extends React.Component<TextFieldProps, TextFieldState> {
inputRef: React.RefObject<HTMLInputElement>;
state: TextFieldState;
@ -267,6 +376,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
handleFocus() {
this.setState({ focused: true });
@ -311,14 +436,23 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
}
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;
// Decide if the input should behave as controlled or uncontrolled
const inputValue = value !== undefined ? value : internalValue;
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}</>}
<div className="wave-textfield-inner">
<label
@ -341,6 +475,7 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
placeholder={placeholder}
maxLength={maxLength}
autoFocus={autoFocus}
disabled={disabled}
/>
</div>
{decoration?.endDecoration && <>{decoration.endDecoration}</>}
@ -617,7 +752,7 @@ class InlineSettingsTextEdit extends React.Component<
<div
onClick={this.confirmChange}
title="Confirm (Enter)"
className="button is-prompt-green is-outlined is-small"
className="button is-wave-green is-outlined is-small"
>
<span className="icon is-small">
<i className="fa-sharp fa-solid fa-check" />
@ -964,6 +1099,70 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
}
}
interface ModalHeaderProps {
onClose: () => void;
title: string;
}
const ModalHeader: React.FC<ModalHeaderProps> = ({ onClose, title }) => (
<div className="wave-modal-header">
{<div>{title}</div>}
<IconButton theme="secondary" variant="ghost" onClick={onClose}>
<i className="fa-sharp fa-solid fa-xmark"></i>
</IconButton>
</div>
);
interface ModalFooterProps {
onCancel?: () => void;
onOk?: () => void;
cancelLabel?: string;
okLabel?: string;
}
const ModalFooter: React.FC<ModalFooterProps> = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok" }) => (
<div className="wave-modal-footer">
{onCancel && (
<Button theme="secondary" onClick={onCancel}>
{cancelLabel}
</Button>
)}
{onOk && <Button onClick={onOk}>{okLabel}</Button>}
</div>
);
interface ModalProps {
className?: string;
children?: React.ReactNode;
onClickBackdrop?: () => void;
}
class Modal extends React.Component<ModalProps> {
static Header = ModalHeader;
static Footer = ModalFooter;
renderBackdrop(onClick: (() => void) | undefined) {
return <div className="wave-modal-backdrop" onClick={onClick}></div>;
}
renderModal() {
const { className, children } = this.props;
return (
<div className="wave-modal-container">
{this.renderBackdrop(this.props.onClickBackdrop)}
<div className={`wave-modal ${className}`}>
<div className="wave-modal-content">{children}</div>
</div>
</div>
);
}
render() {
return ReactDOM.createPortal(this.renderModal(), document.getElementById("app") as HTMLElement);
}
}
export {
CmdStrCode,
Toggle,
@ -980,4 +1179,9 @@ export {
NumberField,
PasswordField,
Tooltip,
Button,
IconButton,
LinkButton,
Status,
Modal,
};

View File

@ -59,22 +59,6 @@
}
}
.modal.alert-modal {
z-index: 205;
footer {
justify-content: center;
.button {
margin-left: 20px;
}
.button:first-child {
margin-left: 0;
}
}
}
.modal.settings-modal {
footer {
justify-content: center;
@ -181,59 +165,6 @@
}
}
.modal.wave-modal {
.wave-modal-content {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
border-radius: 10px;
background: var(--olive-dark-1, #151715);
box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.35), 0px 10px 24px 0px rgba(0, 0, 0, 0.45),
0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
.wave-modal-content-inner {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
width: 100%;
.wave-modal-header {
width: 100%;
display: flex;
align-items: center;
padding: 12px 20px;
justify-content: space-between;
line-height: 20px;
border-bottom: 1px solid rgba(250, 250, 250, 0.1);
.wave-modal-title {
color: @term-bright-white;
font-style: normal;
line-height: 20px;
font-size: 13px;
}
.wave-modal-close {
display: flex;
padding: 4px;
align-items: center;
gap: 8px;
}
}
.wave-modal-body {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 24px;
width: 87%;
}
}
}
}
.modal.tos-modal {
.modal-content.wave-modal-content {
padding: 32px 48px;
@ -323,14 +254,26 @@
}
}
.modal.about-modal {
.about-wave-modal-content {
width: 401px;
.about-modal {
width: 382px;
.about-wave-modal-body {
.wave-modal-content {
gap: 24px;
.wave-modal-body {
margin-bottom: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 24px;
.about-section {
display: flex;
align-items: center;
gap: 16px;
align-self: stretch;
width: 100%;
.wave-modal-section {
.logo-wrapper {
width: 72px;
height: 72px;
@ -403,7 +346,7 @@
}
}
.wave-modal-section:nth-child(3) {
.about-section:nth-child(3) {
display: flex;
align-items: flex-start;
gap: 10px;
@ -418,7 +361,7 @@
}
}
.wave-modal-section:last-child {
.about-section:last-child {
margin-bottom: 24px;
color: @term-white;
}
@ -426,71 +369,220 @@
}
}
.wave-modal.crconn-modal {
.wave-modal-content.crconn-wave-modal-content {
width: 452px;
min-height: 411px;
overflow: visible;
.crconn-modal {
width: 452px;
min-height: 411px;
.wave-modal-content-inner.crconn-wave-modal-content-inner {
.wave-modal-content {
gap: 24px;
.wave-modal-body {
display: flex;
padding-bottom: 0px;
padding: 0px 20px;
flex-direction: column;
align-items: center;
gap: 20px;
flex-shrink: 0;
align-items: flex-start;
gap: 12px;
align-self: stretch;
width: 100%;
}
}
}
.crconn-wave-modal-body {
.screen-settings-modal {
width: 640px;
min-height: 329px;
.wave-modal-content {
gap: 24px;
.wave-modal-body {
display: flex;
padding: 0px 20px;
flex-direction: column;
align-items: flex-start;
gap: 4px;
align-self: stretch;
width: 100%;
}
}
}
.erconn-modal {
width: 502px;
min-height: 411px;
.wave-modal-content {
gap: 20px;
.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;
padding: 0px 20px;
flex-direction: column;
align-items: flex-start;
gap: 12px;
align-self: stretch;
width: 100%;
}
}
.crconn-wave-modal-footer {
display: flex;
justify-content: flex-end;
width: 100%;
padding: 0 20px 20px;
.name {
color: @term-bright-white;
font-size: 15px;
font-weight: 500;
line-height: 20px;
}
.action-buttons {
display: flex;
.header-actions {
display: flex;
justify-content: flex-end;
align-items: flex-start;
div.button {
margin-right: 8px;
.wave-button {
padding: 4px 15px;
font-size: 11px;
margin-right: 8px;
}
}
}
}
}
}
.wave-button {
display: flex;
padding: 6px 16px;
align-items: center;
gap: var(--sizing-2-xs, 4px);
border-radius: 6px;
height: auto;
.alert-modal {
.wave-modal-content {
.wave-modal-body {
padding: 40px 20px;
}
}
}
.wave-button.color-green {
color: @term-bright-white;
background: @term-green !important; // !important is needed to override the default button color
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;
.rconndetail-modal {
width: 631px;
min-height: 565px;
&:hover {
color: @term-bright-white;
.wave-modal-content {
display: flex;
padding-bottom: 0px;
flex-direction: column;
align-items: center;
gap: 20px;
flex-shrink: 0;
.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
}
}
}
}
}
}
}
.wave-button.color-standard {
color: @term-white;
background: var(--overlays-white-6, rgba(255, 255, 255, 0.12));
background: rgba(255, 255, 255, 0.12);
&:hover {
color: @term-white;
@ -659,31 +751,32 @@
fill: @tab-pink;
}
.tab-colors {
.tab-colors,
.tab-icons {
display: flex;
flex-direction: row;
align-items: center;
.tab-color-sep {
.tab-color-sep,
.tab-icon-sep {
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
}
.tab-color-cur {
width: 100px;
}
.tab-color-icon {
.tab-color-icon,
.tab-icon-icon {
width: 1.1em;
vertical-align: middle;
}
.tab-color-name {
.tab-color-name,
.tab-icon-name {
margin-left: 1em;
}
.tab-color-select {
.tab-color-select,
.tab-icon-select {
cursor: pointer;
margin: 5px;
&:hover {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import {
AboutModal,
CreateRemoteConnModal,
ViewRemoteConnDetailModal,
EditRemoteConnModal,
AlertModal,
} from "./modals";
import * as constants from "../../appconst";
const modalsRegistry: { [key: string]: () => React.ReactElement } = {
[constants.ABOUT]: () => <AboutModal />,
[constants.CREATE_REMOTE]: () => <CreateRemoteConnModal />,
[constants.VIEW_REMOTE]: () => <ViewRemoteConnDetailModal />,
[constants.EDIT_REMOTE]: () => <EditRemoteConnModal />,
[constants.ALERT]: () => <AlertModal />,
};
export { modalsRegistry };

View File

@ -7,10 +7,18 @@ import * as mobx from "mobx";
import { boundMethod } from "autobind-decorator";
import { If, For } from "tsx-control-statements/components";
import cn from "classnames";
import { GlobalModel, GlobalCommandRunner, TabColors } from "../../../model/model";
import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../common";
import {
GlobalModel,
GlobalCommandRunner,
TabColors,
MinFontSize,
MaxFontSize,
TabIcons,
Screen,
} from "../../../model/model";
import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage, Modal } from "../common";
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 * as util from "../../../util/util";
import { commandRtnHandler } from "../../../util/util";
@ -50,15 +58,16 @@ Are you sure you want to stop web-sharing this tab?
`.trim();
@mobxReact.observer
class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string; }, {}> {
class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string }, {}> {
shareCopied: OV<boolean> = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" });
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
screen: Screen;
constructor(props: any) {
super(props);
let { sessionId, screenId } = props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
this.screen = GlobalModel.getScreenById(sessionId, screenId);
if (this.screen == null) {
return;
}
}
@ -84,6 +93,15 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
selectTabIcon(icon: string): void {
if (this.screen.getTabIcon() == icon) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.screen.screenId, { tabicon: icon }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeArchived(val: boolean): void {
let { sessionId, screenId } = this.props;
@ -203,105 +221,123 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
render() {
let { sessionId, screenId } = this.props;
let inline = false;
let screen = GlobalModel.getScreenById(sessionId, screenId);
let screen = this.screen;
if (screen == null) {
return null;
}
console.log("screen.getTabIcon()", screen.getTabIcon());
let color: string = null;
let icon: string = null;
let curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
return (
<div className={cn("modal screen-settings-modal settings-modal prompt-modal is-active")}>
<div className="modal-background"/>
<div className="modal-content">
{this.shareCopied.get() && <div className="copied-indicator" />}
<header>
<div className="modal-title">tab settings ({screen.name.get()})</div>
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
<XmarkIcon />
</div>
</header>
<div className="inner-content">
<div className="settings-field">
<div className="settings-label">Tab Id</div>
<div className="settings-input">{screen.screenId}</div>
</div>
<div className="settings-field">
<div className="settings-label">Name</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder="name"
text={screen.name.get() ?? "(none)"}
value={screen.name.get() ?? ""}
onChange={this.inlineUpdateName}
maxLength={50}
showIcon={true}
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">Connection</div>
<div className="settings-input">
<ConnectionDropdown curRemote={curRemote} onSelectRemote={this.selectRemote} allowNewConn={false}/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">Tab Color</div>
<div className="settings-input">
<div className="tab-colors">
<div className="tab-color-cur">
<SquareIcon className={cn("tab-color-icon", "color-" + screen.getTabColor())} />
<span className="tab-color-name">{screen.getTabColor()}</span>
</div>
<div className="tab-color-sep">|</div>
<For each="color" of={TabColors}>
<div
key={color}
className="tab-color-select"
onClick={() => this.selectTabColor(color)}
>
<SquareIcon className={cn("tab-color-icon", "color-" + color)} />
</div>
</For>
</div>
</div>
</div>
<div className="settings-field">
<div className="settings-label">
<div>Archived</div>
<InfoMessage width={400}>
Archive will hide the tab. Commands and output will be retained in
history.
</InfoMessage>
</div>
<div className="settings-input">
<Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived} />
</div>
</div>
<div className="settings-field">
<div className="settings-label">
<div>Actions</div>
<InfoMessage width={400}>
Delete will remove the tab, removing all commands and output from history.
</InfoMessage>
</div>
<div className="settings-input">
<div
onClick={this.handleDeleteScreen}
className="button is-prompt-danger is-outlined is-small"
>
Delete Tab
</div>
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
<Modal className="screen-settings-modal">
<Modal.Header onClose={this.closeModal} title={`tab settings (${screen.name.get()})`} />
<div className="wave-modal-body">
<div className="settings-field">
<div className="settings-label">Tab Id</div>
<div className="settings-input">{screen.screenId}</div>
</div>
<footer>
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
Close
<div className="settings-field">
<div className="settings-label">Name</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder="name"
text={screen.name.get() ?? "(none)"}
value={screen.name.get() ?? ""}
onChange={this.inlineUpdateName}
maxLength={50}
showIcon={true}
/>
</div>
</footer>
</div>
<div className="settings-field">
<div className="settings-label">Connection</div>
<div className="settings-input">
<ConnectionDropdown
curRemote={curRemote}
onSelectRemote={this.selectRemote}
allowNewConn={false}
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">Tab Color</div>
<div className="settings-input">
<div className="tab-colors">
<div className="tab-color-cur">
<SquareIcon className={cn("tab-color-icon", "color-" + screen.getTabColor())} />
<span className="tab-color-name">{screen.getTabColor()}</span>
</div>
<div className="tab-color-sep">|</div>
<For each="color" of={TabColors}>
<div
key={color}
className="tab-color-select"
onClick={() => this.selectTabColor(color)}
>
<SquareIcon className={cn("tab-color-icon", "color-" + color)} />
</div>
</For>
</div>
</div>
</div>
<div className="settings-field">
<div className="settings-label">Tab Icon</div>
<div className="settings-input">
<div className="tab-icons">
<div className="tab-icon-cur">
<If condition={screen.getTabIcon() == "default"}>
<SquareIcon className={cn("tab-color-icon", "color-white")} />
</If>
<If condition={screen.getTabIcon() != "default"}>
<i className={`fa-sharp fa-solid fa-${screen.getTabIcon()}`}></i>
</If>
<span className="tab-icon-name">{screen.getTabIcon()}</span>
</div>
<div className="tab-icon-sep">|</div>
<For each="icon" of={TabIcons}>
<div
key={color}
className="tab-icon-select"
onClick={() => this.selectTabIcon(icon)}
>
<i className={`fa-sharp fa-solid fa-${icon}`}></i>
</div>
</For>
</div>
</div>
</div>
<div className="settings-field">
<div className="settings-label">
<div>Archived</div>
<InfoMessage width={400}>
Archive will hide the tab. Commands and output will be retained in history.
</InfoMessage>
</div>
<div className="settings-input">
<Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived} />
</div>
</div>
<div className="settings-field">
<div className="settings-label">
<div>Actions</div>
<InfoMessage width={400}>
Delete will remove the tab, removing all commands and output from history.
</InfoMessage>
</div>
<div className="settings-input">
<div
onClick={this.handleDeleteScreen}
className="button is-prompt-danger is-outlined is-small"
>
Delete Tab
</div>
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
</div>
</div>
<Modal.Footer cancelLabel="Close" onCancel={this.closeModal} />
</Modal>
);
}
}
@ -436,7 +472,7 @@ class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
<SettingsError errorMessage={this.errorMessage} />
</div>
<footer>
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
<div onClick={this.closeModal} className="button is-wave-green is-outlined is-small">
Close
</div>
</footer>
@ -572,7 +608,7 @@ class LineSettingsModal extends React.Component<{ linenum: number }, {}> {
<div style={{ height: 50 }} />
</div>
<footer>
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
<div onClick={this.closeModal} className="button is-wave-green is-outlined is-small">
Close
</div>
</footer>
@ -630,7 +666,10 @@ class ClientSettingsModal extends React.Component<{}, {}> {
}
renderFontSizeDropdown(): any {
let availableFontSizes = [8, 9, 10, 11, 12, 13, 14, 15];
let availableFontSizes = [];
for (let s = MinFontSize; s <= MaxFontSize; s++) {
availableFontSizes.push(s);
}
let fsize: number = 0;
let curSize = GlobalModel.termFontSize.get();
return (
@ -767,7 +806,7 @@ class ClientSettingsModal extends React.Component<{}, {}> {
<SettingsError errorMessage={this.errorMessage} />
</div>
<footer>
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
<div onClick={this.closeModal} className="button is-wave-green is-outlined is-small">
Close
</div>
</footer>

View File

@ -7,7 +7,7 @@
vertical-align: middle;
width: 1.2em;
height: 1.2em;
fill: @prompt-green;
fill: @wave-green;
}
.term-prompt-branch {

View File

@ -6,7 +6,7 @@
@background-session: rgba(13, 13, 13, 0.85);
@background-session-components: rgba(48, 49, 48, 0.6);
@background-session-components-solid: rgb(33, 34, 33);
@prompt-green: rgb(88, 193, 66);
@wave-green: rgb(88, 193, 66);
@disabled-background: rgba(76, 81, 75, 1);
@disabled-color: #adadad;
@scrollbar-background: rgba(21, 23, 21, 1);

View File

@ -1,408 +1,107 @@
@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;
.connections-view {
background-color: @background-session;
flex-grow: 1;
display: flex;
flex-direction: column;
position: relative;
overflow: auto;
margin-bottom: 10px;
margin-right: 10px;
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;
flex-direction: row;
width: 413px;
padding: 6px 8px 6px 12px;
justify-content: space-between;
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;
}
.connections-title {
}
}
.conn-dd-menu {
.no-items {
display: flex;
width: 413px;
padding: 6px;
flex-direction: column;
align-items: flex-start;
border-radius: 8px;
background-color: @dropdown-menu;
flex-direction: row;
justify-content: center;
padding: 30px 0 30px 0;
border: 1px solid white;
border-radius: 3px;
margin: 20px 50px 20px 20px;
}
.dropdown-item {
display: flex;
padding: 5px 12px 5px 8px;
align-items: center;
gap: 8px;
align-self: stretch;
border-radius: 6px;
.connections-table {
margin: 0px 10px 10px 10px;
table-layout: fixed;
max-width: 970px;
.status-div {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
padding: 3px;
svg.status-icon {
width: 10px;
height: 10px;
}
colgroup {
.first-col {
max-width: 650px;
}
.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;
}
}
.second-col {
max-width: 150px;
}
.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;
}
}
.text-caption {
color: @text-caption;
}
.ellipsis {
text-overflow: ellipsis;
}
tr.connections-item {
border-bottom: 1px solid rgba(241, 246, 243, 0.15);
color: @text-secondary;
cursor: pointer;
&: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

View File

@ -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

View File

@ -188,7 +188,7 @@
}
&.active {
border: 1px solid rgba(@prompt-green, 0.8) !important;
border: 1px solid rgba(@wave-green, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
@ -234,7 +234,7 @@
}
.success {
fill: @prompt-green;
fill: @wave-green;
}
.fail {

View File

@ -68,7 +68,7 @@ class LinesView extends React.Component<
});
this.visibleMap = new Map();
this.collapsedMap = new Map();
this.computeVisibleMap_debounced = debounce(1000, this.computeVisibleMap.bind(this));
this.computeVisibleMap_debounced = debounce(100, this.computeVisibleMap.bind(this));
}
@boundMethod

View File

@ -45,7 +45,7 @@
margin-bottom: 1em;
border: 1px solid transparent;
&.selected {
border-color: @prompt-green;
border-color: @wave-green;
}
.plugin-summary-header {
display: flex;

View File

@ -110,6 +110,15 @@ class MainSideBar extends React.Component<{}, {}> {
GlobalCommandRunner.bookmarksView();
}
@boundMethod
handleConnectionsClick(): void {
if (GlobalModel.activeMainView.get() == "connections") {
GlobalModel.showSessionView();
return;
}
GlobalCommandRunner.connectionsView();
}
@boundMethod
handleWebSharingClick(): void {
if (GlobalModel.activeMainView.get() == "webshare") {
@ -126,11 +135,6 @@ class MainSideBar extends React.Component<{}, {}> {
})();
}
@boundMethod
handleConnectionsClick(): void {
GlobalModel.remotesModalModel.openModal();
}
@boundMethod
openSessionSettings(e: any, session: Session): void {
e.preventDefault();
@ -199,14 +203,14 @@ class MainSideBar extends React.Component<{}, {}> {
<div className="logo">
<If condition={isCollapsed}>
<div className="logo-container" onClick={this.toggleCollapsed}>
<img src="public/logos/wave-logo.png"/>
<img src="public/logos/wave-logo.png" />
</div>
</If>
<If condition={!isCollapsed}>
<div className="logo-container">
<img src="public/logos/wave-dark.png"/>
<img src="public/logos/wave-dark.png" />
</div>
<div className="spacer"/>
<div className="spacer" />
<div className="collapse-button" onClick={this.toggleCollapsed}>
<LeftChevronIcon className="icon" />
</div>

View File

@ -19,7 +19,7 @@
border: 1px solid transparent;
&.active {
border: 1px solid rgba(@prompt-green, 0.8) !important;
border: 1px solid rgba(@wave-green, 0.8) !important;
box-shadow: 0px 0px 0.5px 0px rgba(255, 255, 255, 0.5) inset, 0px 0.5px 0px 0px rgba(255, 255, 255, 0.2) inset;
}
@ -73,6 +73,42 @@
.cmd-input-context {
color: #fff;
white-space: nowrap;
display: flex;
justify-content: space-between;
align-items: center;
}
.cmd-input-filter {
opacity: 0.5;
&:hover {
opacity: 1.0;
}
.avatar {
display: inline-block;
width: 1em;
height: 1em;
margin: 0 0.5em;
vertical-align: text-top;
fill: @base-color;
}
.warning {
fill: @warning-yellow;
}
@keyframes infiniteRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.spin {
animation: infiniteRotate 2s linear infinite;
}
}
.cmd-input-field {
@ -139,7 +175,7 @@
height: 2.5em;
cursor: pointer;
border-radius: 50%;
fill: @prompt-green;
fill: @wave-green;
padding: 0.25em;
}
.icon.disabled {

View File

@ -5,18 +5,19 @@ import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { boundMethod } from "autobind-decorator";
import { If } from "tsx-control-statements/components";
import { If, Choose, When, Otherwise } from "tsx-control-statements/components";
import cn from "classnames";
import dayjs from "dayjs";
import type { RemoteType, RemoteInstanceType, RemotePtrType } from "../../../types/types";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner } from "../../../model/model";
import { renderCmdText } from "../../common/common";
import { GlobalModel, GlobalCommandRunner, Screen, ScreenLines } from "../../../model/model";
import { renderCmdText, Button } from "../../common/common";
import { TextAreaInput } from "./textareainput";
import { InfoMsg } from "./infomsg";
import { HistoryInfo } from "./historyinfo";
import { Prompt } from "../../common/prompt/prompt";
import { ReactComponent as ExecIcon } from "../../assets/icons/exec.svg";
import { ReactComponent as RotateIcon } from "../../assets/icons/line/rotate.svg";
import "./cmdinput.less";
dayjs.extend(localizedFormat);
@ -90,6 +91,13 @@ class CmdInput extends React.Component<{}, {}> {
GlobalCommandRunner.connectRemote(remoteId);
}
@boundMethod
toggleFilter(screen: Screen) {
mobx.action(() => {
screen.filterRunning.set(!screen.filterRunning.get());
})();
}
render() {
let model = GlobalModel;
let inputModel = model.inputModel;
@ -113,11 +121,12 @@ class CmdInput extends React.Component<{}, {}> {
let focusVal = inputModel.physicalInputFocused.get();
let inputMode: string = inputModel.inputMode.get();
let textAreaInputKey = screen == null ? "null" : screen.screenId;
let win = GlobalModel.getScreenLinesById(screen.screenId) ?? GlobalModel.loadScreenLines(screen.screenId);
let numRunningLines = win.getRunningCmdLines().length;
return (
<div
ref={this.cmdInputRef}
className={cn("cmd-input", { "has-info": infoShow }, { active: focusVal })}
onClick={this.cmdInputClick}
>
<If condition={historyShow}>
<div className="cmd-input-grow-spacer"></div>
@ -131,7 +140,7 @@ class CmdInput extends React.Component<{}, {}> {
&nbsp;is {remote.status}
<If condition={remote.status != "connecting"}>
<div
className="button is-prompt-green is-outlined is-small"
className="button is-wave-green is-outlined is-small"
onClick={() => this.clickConnectRemote(remote.remoteid)}
>
connect now
@ -143,6 +152,14 @@ class CmdInput extends React.Component<{}, {}> {
<div className="has-text-white">
<span ref={this.promptRef}><Prompt rptr={rptr} festate={feState} /></span>
</div>
<If condition={numRunningLines > 0}>
<div onClick={() => this.toggleFilter(screen)}className="cmd-input-filter">
{numRunningLines}
<div className="avatar">
<RotateIcon className="warning spin" />
</div>
</div>
</If>
</div>
<div
key="input"
@ -168,7 +185,7 @@ class CmdInput extends React.Component<{}, {}> {
)}
{focusVal && (
<div onMouseDown={this.clickHistoryHint} className="cmd-btn hoverEffect">
{historyShow ? "close (esc)" : "history (crtl-r)"}
{historyShow ? "close (esc)" : "history (ctrl-r)"}
</div>
)}
<ExecIcon

View File

@ -433,6 +433,9 @@ class TextAreaInput extends React.Component<{ onHeightChange: () => void }, {}>
break;
}
}
if (cutSpot == -1) {
cutSpot = 0;
}
let cutValue = value.slice(cutSpot, selStart);
let prevValue = value.slice(0, cutSpot);
let restValue = value.slice(selStart);

View File

@ -98,6 +98,11 @@
}
}
}
.filter-running {
margin: auto 1rem 0 1rem;
align-self: center;
}
}
.screen-settings-inline {

View File

@ -11,24 +11,19 @@ import cn from "classnames";
import { debounce } from "throttle-debounce";
import dayjs from "dayjs";
import { GlobalCommandRunner, TabColors, TabIcons } from "../../../model/model";
import type { LineType, RenderModeType, LineFactoryProps, CommandRtnType } from "../../../types/types";
import type { LineType, RenderModeType, LineFactoryProps } from "../../../types/types";
import * as T from "../../../types/types";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { InlineSettingsTextEdit, RemoteStatusLight } from "../../common/common";
import { Button } from "../../common/common";
import { getRemoteStr } from "../../common/prompt/prompt";
import { GlobalModel, ScreenLines, Screen, Session } from "../../../model/model";
import { Line } from "../../line/linecomps";
import { LinesView } from "../../line/linesview";
import { ConnectionDropdown } from "../../connections/connections";
import { ConnectionDropdown } from "../../connections_deprecated/connections";
import * as util from "../../../util/util";
import { TextField, InputDecoration } from "../../common/common";
import { TextField } from "../../common/common";
import { ReactComponent as EllipseIcon } from "../../assets/icons/ellipse.svg";
import { ReactComponent as Check12Icon } from "../../assets/icons/check12.svg";
import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg";
import { ReactComponent as StatusCircleIcon } from "../../assets/icons/statuscircle.svg";
import { ReactComponent as ArrowsUpDownIcon } from "../../assets/icons/arrowsupdown.svg";
import { ReactComponent as CircleIcon } from "../../assets/icons/circle.svg";
import { ReactComponent as AddIcon } from "../../assets/icons/add.svg";
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
import "./screenview.less";
@ -101,7 +96,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
@boundMethod
clickNewConnection(): void {
GlobalModel.remotesModalModel.openModalForEdit({ remoteedit: true, old: false }, true);
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
}
renderTabIconSelector(): React.ReactNode {
@ -117,7 +112,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
<div className="text-s1 unselectable">Select the icon</div>
<div className="control-iconlist tabicon-list">
<div key="square" className="icondiv" title="square" onClick={() => this.selectTabIcon("square")}>
<SquareIcon className="icon square-icon"/>
<SquareIcon className="icon square-icon" />
</div>
<For each="icon" of={TabIcons}>
<div
@ -334,6 +329,22 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
return <Line key={realLine.lineid} screen={screen} line={realLine} {...restProps} />;
}
determineVisibleLines(win: ScreenLines): LineType[] {
let { screen } = this.props;
if (screen.filterRunning.get()) {
return win.getRunningCmdLines();
}
return win.getNonArchivedLines();
}
@boundMethod
disableFilter() {
let { screen } = this.props;
mobx.action(() => {
screen.filterRunning.set(false);
})();
}
render() {
let { session, screen } = this.props;
let win = this.getScreenLines();
@ -351,7 +362,7 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
return this.renderError("loading client data", true);
}
let isActive = screen.isActive();
let lines = win.getNonArchivedLines();
let lines = this.determineVisibleLines(win);
let renderMode = this.renderMode.get();
return (
<div className="window-view" ref={this.windowViewRef}>
@ -374,7 +385,7 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
<NewTabSettings screen={screen} />
</If>
<If condition={screen.nextLineNum.get() != 1}>
<div className="window-view" ref={this.windowViewRef} data-screenid={screen.screenId}>
<div className="window-empty" ref={this.windowViewRef} data-screenid={screen.screenId}>
<div key="lines" className="lines"></div>
<div key="window-empty" className={cn("window-empty")}>
<div>
@ -395,14 +406,14 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
<i title="archived" className="fa-sharp fa-solid fa-share-nodes" /> web shared
</div>
<div className="share-tag-link">
<div className="button is-prompt-green is-outlined is-small" onClick={this.copyShareLink}>
<div className="button is-wave-green is-outlined is-small" onClick={this.copyShareLink}>
<span>copy link</span>
<span className="icon">
<i className="fa-sharp fa-solid fa-copy" />
</span>
</div>
<div
className="button is-prompt-green is-outlined is-small"
className="button is-wave-green is-outlined is-small"
onClick={this.openScreenSettings}
>
<span>open settings</span>
@ -422,6 +433,19 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
lineFactory={this.buildLineComponent}
/>
</If>
<If condition={screen.filterRunning.get()}>
<div className="filter-running">
<Button
variant="outlined"
color="color-yellow"
style={{ borderRadius: "999px" }}
onClick={this.disableFilter}
>
Showing Running Commands &nbsp;
<i className="fa-sharp fa-solid fa-xmark" />
</Button>
</div>
</If>
</div>
);
}

View File

@ -11,7 +11,7 @@ import cn from "classnames";
import { debounce } from "throttle-debounce";
import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner, Session, ScreenLines, Screen } from "../../../model/model";
import { GlobalModel, GlobalCommandRunner, Session, Screen, TabIcons } from "../../../model/model";
import { renderCmdText } from "../../common/common";
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
import { ReactComponent as ActionsIcon } from "../../assets/icons/tab/actions.svg";

View File

@ -1,6 +1,7 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import type React from "react";
import * as mobx from "mobx";
import { sprintf } from "sprintf-js";
import { boundMethod } from "autobind-decorator";
@ -65,7 +66,6 @@ import type {
import * as T from "../types/types";
import { WSControl } from "./ws";
import {
measureText,
getMonoFontSize,
windowWidthToCols,
windowHeightToRows,
@ -76,8 +76,9 @@ import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import customParseFormat from "dayjs/plugin/customParseFormat";
import { getRendererContext, cmdStatusIsRunning } from "../app/line/lineutil";
import { sortAndFilterRemotes } from "../util/util";
import { MagicLayout } from "../app/magiclayout";
import { modalsRegistry } from "../app/common/modals/modalsRegistry";
import * as constants from "../app/appconst";
dayjs.extend(customParseFormat);
dayjs.extend(localizedFormat);
@ -91,7 +92,7 @@ const DevServerEndpoint = "http://127.0.0.1:8090";
const DevServerWsEndpoint = "ws://127.0.0.1:8091";
const DefaultTermFontSize = 12;
const MinFontSize = 8;
const MaxFontSize = 15;
const MaxFontSize = 24;
const InputChunkSize = 500;
const RemoteColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"];
const TabColors = ["red", "orange", "yellow", "green", "mint", "cyan", "blue", "violet", "pink", "white"];
@ -304,7 +305,6 @@ class Cmd {
}
handleData(data: string, termWrap: TermWrap): void {
// console.log("handle data", {data: data});
if (!this.isRunning()) {
return;
}
@ -357,6 +357,7 @@ class Screen {
renderers: Record<string, RendererModel> = {}; // lineid => RendererModel
shareMode: OV<string>;
webShareOpts: OV<WebShareOpts>;
filterRunning: OV<boolean>;
constructor(sdata: ScreenDataType) {
this.sessionId = sdata.sessionid;
@ -393,6 +394,9 @@ class Screen {
this.webShareOpts = mobx.observable.box(sdata.webshareopts, {
name: "screen-webShareOpts",
});
this.filterRunning = mobx.observable.box(false, {
name: "screen-filter-running",
})
}
dispose() {}
@ -756,6 +760,22 @@ class Screen {
}
termCustomKeyHandler(e: any, termWrap: TermWrap): boolean {
if (e.type == "keypress" && e.code == "KeyC" && e.shiftKey && e.ctrlKey) {
e.stopPropagation();
e.preventDefault();
let sel = termWrap.terminal.getSelection();
navigator.clipboard.writeText(sel);
return false;
}
if (e.type == "keypress" && e.code == "KeyV" && e.shiftKey && e.ctrlKey) {
e.stopPropagation();
e.preventDefault();
let p = navigator.clipboard.readText();
p.then((text) => {
termWrap.dataHandler?.(text, termWrap);
});
return false;
}
if (termWrap.isRunning) {
return true;
}
@ -2188,6 +2208,14 @@ class HistoryViewModel {
}
}
class ConnectionsViewModel {
showConnectionsView(): void {
mobx.action(() => {
GlobalModel.activeMainView.set("connections");
})();
}
}
class BookmarksModel {
bookmarks: OArr<BookmarkType> = mobx.observable.array([], {
name: "Bookmarks",
@ -2685,6 +2713,216 @@ class RemotesModalModel {
}
}
class RemotesModel {
selectedRemoteId: OV<string> = mobx.observable.box(null, {
name: "RemotesModel-selectedRemoteId",
});
remoteTermWrap: TermWrap = null;
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",
});
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);
GlobalModel.modalsModel.pushModal(constants.VIEW_REMOTE);
})();
}
openAddModal(redit: RemoteEditType): void {
mobx.action(() => {
this.remoteEdit.set(redit);
GlobalModel.modalsModel.pushModal(constants.CREATE_REMOTE);
})();
}
openEditModal(redit?: RemoteEditType): void {
if (redit == null) {
this.startEditAuth();
GlobalModel.modalsModel.pushModal(constants.EDIT_REMOTE);
} else {
mobx.action(() => {
this.selectedRemoteId.set(redit?.remoteid);
this.remoteEdit.set(redit);
GlobalModel.modalsModel.pushModal(constants.EDIT_REMOTE);
})();
}
}
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);
}
}
isAuthEditMode(): boolean {
return this.remoteEdit.get() != null;
}
@boundMethod
closeModal(): void {
mobx.action(() => {
GlobalModel.modalsModel.popModal();
})();
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 ModalsModel {
store: Array<{ id: string; component: React.ComponentType }> = [];
constructor() {
mobx.makeAutoObservable(this);
}
pushModal(modalId: string) {
const modalFactory = modalsRegistry[modalId];
if (modalFactory && !this.store.some((modal) => modal.id === modalId)) {
this.store.push({ id: modalId, component: modalFactory });
}
}
popModal() {
this.store.pop();
}
get activeModals() {
return this.store.slice().map((modal) => {
return modal.component;
});
}
}
class Model {
clientId: string;
activeSessionId: OV<string> = mobx.observable.box(null, {
@ -2714,9 +2952,10 @@ class Model {
authKey: string;
isDev: boolean;
platform: string;
activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare"> = mobx.observable.box("session", {
name: "activeMainView",
});
activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare" | "connections"> =
mobx.observable.box("session", {
name: "activeMainView",
});
termFontSize: CV<number>;
alertMessage: OV<AlertMessageType> = mobx.observable.box(null, {
name: "alertMessage",
@ -2738,11 +2977,14 @@ class Model {
name: "lineSettingsModal",
}); // linenum
remotesModalModel: RemotesModalModel;
remotesModel: RemotesModel;
inputModel: InputModel;
pluginsModel: PluginsModel;
bookmarksModel: BookmarksModel;
historyViewModel: HistoryViewModel;
connectionViewModel: ConnectionsViewModel;
modalsModel: ModalsModel;
clientData: OV<ClientDataType> = mobx.observable.box(null, {
name: "clientData",
});
@ -2762,7 +3004,10 @@ class Model {
this.pluginsModel = new PluginsModel();
this.bookmarksModel = new BookmarksModel();
this.historyViewModel = new HistoryViewModel();
this.connectionViewModel = new ConnectionsViewModel();
this.remotesModalModel = new RemotesModalModel();
this.remotesModel = new RemotesModel();
this.modalsModel = new ModalsModel();
let isWaveSrvRunning = getApi().getWaveSrvStatus();
this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, {
name: "model-wavesrv-running",
@ -2851,6 +3096,7 @@ class Model {
showAlert(alertMessage: AlertMessageType): Promise<boolean> {
mobx.action(() => {
this.alertMessage.set(alertMessage);
GlobalModel.modalsModel.pushModal(constants.ALERT);
})();
let prtn = new Promise<boolean>((resolve, reject) => {
this.alertPromiseResolver = resolve;
@ -2861,6 +3107,7 @@ class Model {
cancelAlert(): void {
mobx.action(() => {
this.alertMessage.set(null);
GlobalModel.modalsModel.popModal();
})();
if (this.alertPromiseResolver != null) {
this.alertPromiseResolver(false);
@ -2871,6 +3118,7 @@ class Model {
confirmAlert(): void {
mobx.action(() => {
this.alertMessage.set(null);
GlobalModel.modalsModel.popModal();
})();
if (this.alertPromiseResolver != null) {
this.alertPromiseResolver(true);
@ -2988,10 +3236,6 @@ class Model {
GlobalModel.screenSettingsModal.set(null);
didSomething = true;
}
if (GlobalModel.remotesModalModel.isOpen()) {
GlobalModel.remotesModalModel.closeModal();
didSomething = true;
}
if (GlobalModel.clientSettingsModal.get()) {
GlobalModel.clientSettingsModal.set(false);
didSomething = true;
@ -3131,7 +3375,7 @@ class Model {
onMenuItemAbout(): void {
mobx.action(() => {
this.aboutModalOpen.set(true);
this.modalsModel.pushModal(constants.ABOUT);
})();
}
@ -3198,7 +3442,7 @@ class Model {
} else {
// remote update
let ptyData = base64ToArray(ptyMsg.ptydata64);
this.remotesModalModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData);
this.remotesModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData);
}
return;
}
@ -3262,6 +3506,10 @@ class Model {
this.remotes.clear();
}
this.updateRemotes(update.remotes);
if (update.remotes && update.remotes.length && this.remotesModel.recentConnAddedState.get()) {
GlobalModel.remotesModel.closeModal();
GlobalModel.remotesModel.openReadModal(update.remotes![0].remoteid);
}
}
if ("mainview" in update) {
if (update.mainview == "plugins") {
@ -3287,12 +3535,8 @@ class Model {
}
if (interactive && "remoteview" in update) {
let rview: RemoteViewType = update.remoteview;
if (rview.remoteshowall) {
this.remotesModalModel.openModal();
} else if (rview.remoteedit != null) {
this.remotesModalModel.openModalForEdit({ ...rview.remoteedit, old: true }, false);
} else if (rview.ptyremoteid) {
this.remotesModalModel.openModal(rview.ptyremoteid);
if (rview.remoteedit != null) {
this.remotesModel.openEditModal({ ...rview.remoteedit });
}
}
if ("cmdline" in update) {
@ -3514,7 +3758,7 @@ class Model {
submitCommand(
metaCmd: string,
metaSubCmd: string,
args: string[] | null,
args: string[],
kwargs: Record<string, string>,
interactive: boolean
): Promise<CommandRtnType> {
@ -3593,12 +3837,10 @@ class Model {
}
getRemote(remoteId: string): RemoteType {
for (let i = 0; i < this.remotes.length; i++) {
if (this.remotes[i].remoteid == remoteId) {
return this.remotes[i];
}
if (remoteId == null) {
return null;
}
return null;
return this.remotes.find((remote) => remote.remoteid === remoteId);
}
getRemoteNames(): Record<string, string> {
@ -3624,11 +3866,15 @@ class Model {
}
getCmd(line: LineType): Cmd {
let slines = this.getScreenLinesById(line.screenid);
return this.getCmdByScreenLine(line.screenid, line.lineid);
}
getCmdByScreenLine(screenId: string, lineId: string): Cmd {
let slines = this.getScreenLinesById(screenId);
if (slines == null) {
return null;
}
return slines.getCmd(line.lineid);
return slines.getCmd(lineId);
}
getActiveLine(screenId: string, lineid: string): SWLinePtr {
@ -4018,6 +4264,10 @@ class CommandRunner {
GlobalModel.submitCommand("bookmarks", "show", null, { nohist: "1" }, true);
}
connectionsView() {
GlobalModel.connectionViewModel.showConnectionsView();
}
historyView(params: HistorySearchParams) {
let kwargs = { nohist: "1" };
kwargs["offset"] = String(params.offset);
@ -4195,5 +4445,8 @@ export {
RemoteColors,
getTermPtyData,
RemotesModalModel,
RemotesModel,
MinFontSize,
MaxFontSize,
};
export type { LineContainerModel };

View File

@ -97,7 +97,7 @@
.gutter {
flex-shrink: 0;
flex-grow: 0;
background: fade(@prompt-green, 40%);
background: fade(@wave-green, 40%);
max-width: 3px;
}
.gutter-horizontal {
@ -107,10 +107,10 @@
cursor: row-resize;
}
.gutter:hover {
background: @prompt-green;
background: @wave-green;
}
.gutter-dragging:hover {
background: @prompt-green;
background: @wave-green;
}
.pane {

View File

@ -61,6 +61,7 @@ class TermWrap {
onUpdateContentHeight: (termContext: RendererContext, height: number) => void;
ptyDataSource: (termContext: TermContextUnion) => Promise<PtyDataType>;
initializing: boolean;
dataHandler?: (data: string, termWrap: TermWrap) => void;
constructor(elem: Element, opts: TermWrapOpts) {
opts = opts ?? ({} as any);
@ -104,6 +105,7 @@ class TermWrap {
this.terminal.onKey((e) => opts.keyHandler(e, this));
}
if (opts.dataHandler != null) {
this.dataHandler = opts.dataHandler;
this.terminal.onData((e) => opts.dataHandler(e, this));
}
this.terminal.textarea.addEventListener("focus", () => {

View File

@ -168,7 +168,7 @@ type FeCmdPacketType = {
type: string;
metacmd: string;
metasubcmd?: string;
args: string[] | null;
args: string[];
kwargs: Record<string, string>;
rawstr?: string;
uicontext: UIContextType;

View File

@ -1,10 +1,10 @@
{
"include": ["src/**/*", "types/**/*"],
"exclude": ["src/electron/emain.ts"],
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"jsx": "react",
"strict": true,
"jsx": "preserve",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
@ -12,6 +12,7 @@
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"isolatedModules": true,
"experimentalDecorators": true
"experimentalDecorators": true,
"downlevelIteration": true
}
}

View File

@ -1,2 +1,2 @@
const VERSION = "v0.5.0";
const VERSION = "v0.5.1";
module.exports = VERSION;

View File

@ -61,6 +61,9 @@ const MaxEvalDepth = 5
const MaxOpenAIAPITokenLen = 100
const MaxOpenAIModelLen = 100
const TermFontSizeMin = 8
const TermFontSizeMax = 24
const TsFormatStr = "2006-01-02 15:04:05"
const (
@ -439,10 +442,10 @@ func SyncCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.
runPacket.ReqId = uuid.New().String()
runPacket.CK = base.MakeCommandKey(ids.ScreenId, scbase.GenWaveUUID())
runPacket.UsePty = true
ptermVal := defaultStr(pk.Kwargs["pterm"], DefaultPTERM)
ptermVal := defaultStr(pk.Kwargs["wterm"], DefaultPTERM)
runPacket.TermOpts, err = GetUITermOpts(pk.UIContext.WinSize, ptermVal)
if err != nil {
return nil, fmt.Errorf("/sync error, invalid 'pterm' value %q: %v", ptermVal, err)
return nil, fmt.Errorf("/sync error, invalid 'wterm' value %q: %v", ptermVal, err)
}
runPacket.Command = ":"
runPacket.ReturnState = true
@ -535,7 +538,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U
runPacket.ReqId = uuid.New().String()
runPacket.CK = base.MakeCommandKey(ids.ScreenId, scbase.GenWaveUUID())
runPacket.UsePty = true
ptermVal := defaultStr(pk.Kwargs["pterm"], DefaultPTERM)
ptermVal := defaultStr(pk.Kwargs["wterm"], DefaultPTERM)
runPacket.TermOpts, err = GetUITermOpts(pk.UIContext.WinSize, ptermVal)
if err != nil {
return nil, fmt.Errorf("/run error, invalid 'pterm' value %q: %v", ptermVal, err)
@ -761,10 +764,6 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
}
if pk.Kwargs["tabicon"] != "" {
icon := pk.Kwargs["tabicon"]
err = validateIcon(icon, "screen tabicon")
if err != nil {
return nil, err
}
updateMap[sstore.ScreenField_TabIcon] = icon
varsUpdated = append(varsUpdated, "tabicon")
setNonAnchor = true
@ -1532,7 +1531,7 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
if promptStr == "" {
return nil, fmt.Errorf("openai error, prompt string is blank")
}
ptermVal := defaultStr(pk.Kwargs["pterm"], DefaultPTERM)
ptermVal := defaultStr(pk.Kwargs["wterm"], DefaultPTERM)
pkTermOpts, err := GetUITermOpts(pk.UIContext.WinSize, ptermVal)
if err != nil {
return nil, fmt.Errorf("openai error, invalid 'pterm' value %q: %v", ptermVal, err)
@ -1990,15 +1989,6 @@ func validateColor(color string, typeStr string) error {
return fmt.Errorf("invalid %s, valid colors are: %s", typeStr, formatStrs(ColorNames, "or", false))
}
func validateIcon(icon string, typeStr string) error {
for _, c := range TabIcons {
if icon == c {
return nil
}
}
return fmt.Errorf("invalid %s, valid icons are: %s", typeStr, formatStrs(TabIcons, "or", false))
}
func validateRemoteColor(color string, typeStr string) error {
for _, c := range RemoteColorNames {
if color == c {
@ -3554,8 +3544,8 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
if err != nil {
return nil, fmt.Errorf("invalid termfontsize, must be a number between 8-15: %v", err)
}
if newFontSize < 8 || newFontSize > 15 {
return nil, fmt.Errorf("invalid termfontsize, must be a number between 8-15")
if newFontSize < TermFontSizeMin || newFontSize > TermFontSizeMax {
return nil, fmt.Errorf("invalid termfontsize, must be a number between %d-%d", TermFontSizeMin, TermFontSizeMax)
}
feOpts := clientData.FeOpts
feOpts.TermFontSize = newFontSize

View File

@ -179,7 +179,19 @@ func setBracketArgs(argMap map[string]string, bracketStr string) error {
return nil
}
var literalRtnStateCommands = []string{".", "source", "unset", "cd", "alias", "unalias", "deactivate", "eval"}
var literalRtnStateCommands = []string{
".",
"source",
"unset",
"cd",
"alias",
"unalias",
"deactivate",
"eval",
"asdf",
"nvm",
"virtualenv",
}
func getCallExprLitArg(callExpr *syntax.CallExpr, argNum int) string {
if len(callExpr.Args) <= argNum {

View File

@ -36,7 +36,7 @@ const WaveLockFile = "waveterm.lock"
const WaveDirName = ".waveterm" // must match emain.ts
const WaveDevDirName = ".waveterm-dev" // must match emain.ts
const WaveAppPathVarName = "WAVETERM_APP_PATH"
const WaveVersion = "v0.5.0"
const WaveVersion = "v0.5.1"
const WaveAuthKeyFileName = "waveterm.authkey"
const MShellVersion = "v0.3.0"
const DefaultMacOSShell = "/bin/bash"

View File

@ -1391,17 +1391,9 @@ func ArchiveScreenLines(ctx context.Context, screenId string) (*ModelUpdate, err
if !tx.Exists(query, screenId) {
return fmt.Errorf("screen does not exist")
}
fmt.Printf("** archive-screen-lines: %s\n", screenId)
if isWebShare(tx, screenId) {
query = `INSERT INTO screenupdate (screenid, lineid, updatetype, updatets)
SELECT screenid, lineid, ?, ? FROM line WHERE screenid = ? AND archived = 0`
tx.Exec(query, UpdateType_LineDel, time.Now().UnixMilli(), screenId)
NotifyUpdateWriter()
query = `SELECT count(*) FROM line WHERE screenid = ? AND archived = 0`
count := tx.GetInt(query, screenId)
fmt.Printf("** archive-screen-lines: wrote into screenupdate: %d\n", count)
}
query = `UPDATE line SET archived = 1 WHERE screenid = ? AND archived = 0`
query = `UPDATE line SET archived = 1
WHERE line.archived = 0 AND line.screenid = ? AND NOT EXISTS (SELECT * FROM cmd c
WHERE line.screenid = c.screenid AND line.lineid = c.lineid AND c.status IN ('running', 'detached'))`
tx.Exec(query, screenId)
return nil
})
@ -1709,7 +1701,7 @@ const (
ScreenField_SelectedLine = "selectedline" // int
ScreenField_Focus = "focustype" // string
ScreenField_TabColor = "tabcolor" // string
ScreenField_TabIcon = "tabicon" // string
ScreenField_TabIcon = "tabicon" // string
ScreenField_PTerm = "pterm" // string
ScreenField_Name = "name" // string
ScreenField_ShareName = "sharename" // string