migrate modals to new modals system (#124)

* migrate screen settings modal to new modals system

* use screen member var in the methods

* migrate session settings modal to new modal system

* use Modal component in session settings modal

* migrate line settings modal to new modals system

* use Modal component in line settings modal

* migrate client settings modal to new modals framework

* set alert modal width to 500px

* remove screen settings modal after deletion

* use Dropdown component for connnections dropdown

* use Dropdown component for connections dropdown in new tab flow

* replace InfoMessage with Tooltip

* use Dropdown for fontsize dropdown

* use Dropdown for renderer dropdown

* fix dropdown width issue on new tab container

* fix class names concatenation

* fix dropdown width issue in screen settings modal
This commit is contained in:
Red J Adaya 2023-12-08 14:51:46 +08:00 committed by GitHub
parent 87bf3f7a65
commit 8a938744f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 531 additions and 368 deletions

View File

@ -74,9 +74,6 @@ class App extends React.Component<{}, {}> {
}
render() {
let screenSettingsModal = GlobalModel.screenSettingsModal.get();
let sessionSettingsModal = GlobalModel.sessionSettingsModal.get();
let lineSettingsModal = GlobalModel.lineSettingsModal.get();
let clientSettingsModal = GlobalModel.clientSettingsModal.get();
let remotesModel = GlobalModel.remotesModel;
let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get();
@ -121,22 +118,6 @@ class App extends React.Component<{}, {}> {
</ErrorBoundary>
</div>
<ModalsProvider />
<If condition={screenSettingsModal != null}>
<ScreenSettingsModal
key={screenSettingsModal.sessionId + ":" + screenSettingsModal.screenId}
sessionId={screenSettingsModal.sessionId}
screenId={screenSettingsModal.screenId}
/>
</If>
<If condition={sessionSettingsModal != null}>
<SessionSettingsModal key={sessionSettingsModal} sessionId={sessionSettingsModal} />
</If>
<If condition={lineSettingsModal != null}>
<LineSettingsModal key={String(lineSettingsModal)} linenum={lineSettingsModal} />
</If>
<If condition={clientSettingsModal}>
<ClientSettingsModal />
</If>
</div>
);
}

View File

@ -3,3 +3,7 @@ export const CREATE_REMOTE = "createRemote";
export const VIEW_REMOTE = "viewRemote";
export const EDIT_REMOTE = "editRemote";
export const ALERT = "alert";
export const SCREEN_SETTINGS = "screenSettings";
export const SESSION_SETTINGS = "sessionSettings";
export const LINE_SETTINGS = "lineSettings";
export const CLIENT_SETTINGS = "clientSettings";

View File

@ -610,7 +610,7 @@
position: relative;
background-color: transparent;
height: 44px;
min-width: 412px;
min-width: 150px;
width: 100%;
border: 1px solid var(--element-separator, rgba(241, 246, 243, 0.15));
border-radius: 6px;
@ -618,6 +618,10 @@
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;
&.no-label {
height: 34px;
}
&-label {
position: absolute;
left: 16px;
@ -841,7 +845,7 @@
justify-content: center;
i {
font-size: 16px;
font-size: 13px;
}
}
@ -865,11 +869,11 @@
background-color: #444;
border-radius: 5px;
overflow: hidden;
max-width: 300px;
width: 300px;
i {
display: inline;
font-size: 16px;
font-size: 13px;
fill: @base-color;
padding-top: 0.2em;
}
@ -1114,6 +1118,11 @@
line-height: 20px;
border-bottom: 1px solid rgba(250, 250, 250, 0.1);
.wave-modal-title {
color: #eceeec;
font-size: 15px;
}
button {
i {
font-size: 18px;
@ -1132,8 +1141,8 @@
width: 100%;
padding: 0 20px 20px;
button:first-child {
margin-right: 8px;
button:last-child {
margin-left: 8px;
}
}
}

View File

@ -150,6 +150,7 @@ interface TooltipProps {
message: React.ReactNode;
icon?: React.ReactNode; // Optional icon property
children: React.ReactNode;
className?: string;
}
interface TooltipState {
@ -185,8 +186,8 @@ class Tooltip extends React.Component<TooltipProps, TooltipState> {
if (iconElement) {
const rect = iconElement.getBoundingClientRect();
return {
top: `${rect.bottom + window.scrollY - 29.5}px`,
left: `${rect.left + window.scrollX + rect.width / 2 - 19}px`,
top: `${rect.bottom + window.scrollY - 29}px`,
left: `${rect.left + window.scrollX + rect.width / 2 - 17.5}px`,
};
}
return {};
@ -199,7 +200,7 @@ class Tooltip extends React.Component<TooltipProps, TooltipState> {
const style = this.calculatePosition();
return ReactDOM.createPortal(
<div className="wave-tooltip" style={style}>
<div className={cn("wave-tooltip", this.props.className)} style={style}>
{this.props.icon && <div className="wave-tooltip-icon">{this.props.icon}</div>}
<div className="wave-tooltip-message">{this.props.message}</div>
</div>,
@ -864,7 +865,7 @@ interface DropdownDecorationProps {
}
interface DropdownProps {
label: string;
label?: string;
options: { value: string; label: string }[];
value?: string;
className?: string;
@ -1051,7 +1052,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
{options.map((option, index) => (
<div
key={option.value}
className={cn("wave-dropdown-item", {
className={cn("wave-dropdown-item unselectable", {
"wave-dropdown-item-highlighted": index === highlightedIndex,
})}
onClick={(e) => this.handleSelect(option.value, e)}
@ -1068,8 +1069,9 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
return (
<div
className={cn(`wave-dropdown ${className || ""}`, {
className={cn("wave-dropdown", className, {
"wave-dropdown-error": isError,
"no-label": !label,
})}
ref={this.wrapperRef}
tabIndex={0}
@ -1078,15 +1080,19 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
onFocus={this.handleFocus}
>
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
<If condition={label}>
<div
className={cn("wave-dropdown-label unselectable", {
float: shouldLabelFloat,
"offset-left": decoration?.startDecoration,
})}
>
{label}
</div>
</If>
<div
className={cn("wave-dropdown-label", {
float: shouldLabelFloat,
"offset-left": decoration?.startDecoration,
})}
className={cn("wave-dropdown-display unselectable", { "offset-left": decoration?.startDecoration })}
>
{label}
</div>
<div className={cn("wave-dropdown-display", { "offset-left": decoration?.startDecoration })}>
{selectedOptionLabel}
</div>
<div className={cn("wave-dropdown-arrow", { "wave-dropdown-arrow-rotate": isOpen })}>
@ -1106,7 +1112,7 @@ interface ModalHeaderProps {
const ModalHeader: React.FC<ModalHeaderProps> = ({ onClose, title }) => (
<div className="wave-modal-header">
{<div>{title}</div>}
{<div className="wave-modal-title">{title}</div>}
<IconButton theme="secondary" variant="ghost" onClick={onClose}>
<i className="fa-sharp fa-solid fa-xmark"></i>
</IconButton>

View File

@ -396,13 +396,109 @@
gap: 12px;
align-self: stretch;
width: 100%;
> div {
width: 100%;
}
}
}
}
.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%;
.screen-settings-dropdown {
width: 412px;
.lefticon {
position: absolute;
top: 50%;
left: 16px;
transform: translateY(-50%);
.globe-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.status-icon {
position: absolute;
left: 7px;
top: 8px;
}
}
}
.archived-label,
.actions-label {
div:first-child {
margin-right: 5px;
}
div:last-child i {
font-size: 13px;
}
}
}
}
}
.screen-settings-tooltip .wave-tooltip-icon {
i {
font-size: 13px;
}
}
.session-settings-modal {
width: 640px;
.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%;
}
}
}
.line-settings-modal {
width: 640px;
.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%;
}
}
}
.client-settings-modal {
width: 640px;
.wave-modal-content {
gap: 24px;
@ -470,6 +566,8 @@
}
.alert-modal {
width: 500px;
.wave-modal-content {
.wave-modal-body {
padding: 40px 20px;

View File

@ -9,6 +9,7 @@ import {
EditRemoteConnModal,
AlertModal,
} from "./modals";
import { ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal } from "./settings";
import * as constants from "../../appconst";
const modalsRegistry: { [key: string]: () => React.ReactElement } = {
@ -17,6 +18,10 @@ const modalsRegistry: { [key: string]: () => React.ReactElement } = {
[constants.VIEW_REMOTE]: () => <ViewRemoteConnDetailModal />,
[constants.EDIT_REMOTE]: () => <EditRemoteConnModal />,
[constants.ALERT]: () => <AlertModal />,
[constants.SCREEN_SETTINGS]: () => <ScreenSettingsModal />,
[constants.SESSION_SETTINGS]: () => <SessionSettingsModal />,
[constants.LINE_SETTINGS]: () => <LineSettingsModal />,
[constants.CLIENT_SETTINGS]: () => <ClientSettingsModal />,
};
export { modalsRegistry };

View File

@ -15,16 +15,17 @@ import {
MaxFontSize,
TabIcons,
Screen,
Session,
} from "../../../model/model";
import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage, Modal } from "../common";
import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../../types/types";
import { ConnectionDropdown } from "../../connections_deprecated/connections";
import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage, Modal, Dropdown, Tooltip } from "../common";
import { LineType, RendererPluginType, ClientDataType, CommandRtnType, RemoteType } from "../../../types/types";
import { PluginModel } from "../../../plugins/plugins";
import * as util from "../../../util/util";
import { commandRtnHandler } from "../../../util/util";
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
import { ReactComponent as XmarkIcon } from "../../assets/icons/line/xmark.svg";
import { ReactComponent as AngleDownIcon } from "../../assets/icons/history/angle-down.svg";
import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg";
import { ReactComponent as StatusCircleIcon } from "../../assets/icons/statuscircle.svg";
import "./modals.less";
@ -58,18 +59,47 @@ 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<{}, {}> {
shareCopied: OV<boolean> = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" });
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
screen: Screen;
sessionId: string;
screenId: string;
remotes: RemoteType[];
constructor(props: any) {
constructor(props) {
super(props);
let { sessionId, screenId } = props;
let screenSettingsModal = GlobalModel.screenSettingsModal.get();
let { sessionId, screenId } = screenSettingsModal;
this.sessionId = sessionId;
this.screenId = screenId;
this.screen = GlobalModel.getScreenById(sessionId, screenId);
if (this.screen == null) {
if (this.screen == null || sessionId == null || screenId == null) {
return;
}
this.remotes = GlobalModel.remotes;
}
@boundMethod
getOptions(): { label: string; value: string }[] {
return this.remotes
.filter((r) => !r.archived)
.map((remote) => ({
...remote,
label:
remote.remotealias && !util.isBlank(remote.remotealias)
? `${remote.remotecanonicalname}`
: remote.remotecanonicalname,
value: remote.remotecanonicalname,
}))
.sort((a, b) => {
let connValA = util.getRemoteConnVal(a);
let connValB = util.getRemoteConnVal(b);
if (connValA !== connValB) {
return connValA - connValB;
}
return a.remoteidx - b.remoteidx;
});
}
@boundMethod
@ -77,19 +107,18 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
mobx.action(() => {
GlobalModel.screenSettingsModal.set(null);
})();
GlobalModel.modalsModel.popModal();
}
@boundMethod
selectTabColor(color: string): void {
let { sessionId, screenId } = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
if (this.screen == null) {
return;
}
if (screen.getTabColor() == color) {
if (this.screen.getTabColor() == color) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, { tabcolor: color }, false);
let prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { tabcolor: color }, false);
commandRtnHandler(prtn, this.errorMessage);
}
@ -104,26 +133,22 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
@boundMethod
handleChangeArchived(val: boolean): void {
let { sessionId, screenId } = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
if (this.screen == null) {
return;
}
if (screen.archived.get() == val) {
if (this.screen.archived.get() == val) {
return;
}
let prtn = GlobalCommandRunner.screenArchive(this.props.screenId, val);
let prtn = GlobalCommandRunner.screenArchive(this.screenId, val);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeWebShare(val: boolean): void {
let { sessionId, screenId } = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
if (this.screen == null) {
return;
}
if (screen.isWebShared() == val) {
if (this.screen.isWebShared() == val) {
return;
}
let message = val ? WebShareConfirmMarkdown : WebStopShareConfirmMarkdown;
@ -132,19 +157,17 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
if (!result) {
return;
}
let prtn = GlobalCommandRunner.screenWebShare(screen.screenId, val);
let prtn = GlobalCommandRunner.screenWebShare(this.screen.screenId, val);
commandRtnHandler(prtn, this.errorMessage);
});
}
@boundMethod
copyShareLink(): void {
let { sessionId, screenId } = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
if (this.screen == null) {
return null;
}
let shareLink = screen.getWebShareUrl();
let shareLink = this.screen.getWebShareUrl();
if (shareLink == null) {
return;
}
@ -161,29 +184,25 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
@boundMethod
inlineUpdateName(val: string): void {
let { sessionId, screenId } = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
if (this.screen == null) {
return;
}
if (util.isStrEq(val, screen.name.get())) {
if (util.isStrEq(val, this.screen.name.get())) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, { name: val }, false);
let prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { name: val }, false);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
inlineUpdateShareName(val: string): void {
let { sessionId, screenId } = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
if (this.screen == null) {
return;
}
if (util.isStrEq(val, screen.getShareName())) {
if (util.isStrEq(val, this.screen.getShareName())) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, { sharename: val }, false);
let prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { sharename: val }, false);
commandRtnHandler(prtn, this.errorMessage);
}
@ -196,9 +215,7 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
@boundMethod
handleDeleteScreen(): void {
let { sessionId, screenId } = this.props;
let screen = GlobalModel.getScreenById(sessionId, screenId);
if (screen == null) {
if (this.screen == null) {
return;
}
let message = ScreenDeleteMessage;
@ -207,8 +224,9 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
if (!result) {
return;
}
let prtn = GlobalCommandRunner.screenPurge(screenId);
let prtn = GlobalCommandRunner.screenPurge(this.screenId);
commandRtnHandler(prtn, this.errorMessage);
GlobalModel.modalsModel.popModal();
});
}
@ -219,16 +237,15 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
}
render() {
let { sessionId, screenId } = this.props;
let inline = false;
let screen = this.screen;
if (screen == null) {
return null;
}
console.log("screen.getTabIcon()", screen.getTabIcon());
let color: string = null;
let icon: string = null;
let index: number = 0;
let curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
return (
<Modal className="screen-settings-modal">
<Modal.Header onClose={this.closeModal} title={`tab settings (${screen.name.get()})`} />
@ -253,10 +270,22 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
<div className="settings-field">
<div className="settings-label">Connection</div>
<div className="settings-input">
<ConnectionDropdown
curRemote={curRemote}
onSelectRemote={this.selectRemote}
allowNewConn={false}
<Dropdown
className="screen-settings-dropdown"
label={curRemote.remotealias}
options={this.getOptions()}
defaultValue={curRemote.remotecanonicalname}
onChange={this.selectRemote}
decoration={{
startDecoration: (
<div className="lefticon">
<GlobeIcon className="globe-icon" />
<StatusCircleIcon
className={cn("status-icon", "status-" + curRemote.status)}
/>
</div>
),
}}
/>
</div>
</div>
@ -295,9 +324,9 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
<span className="tab-icon-name">{screen.getTabIcon()}</span>
</div>
<div className="tab-icon-sep">|</div>
<For each="icon" of={TabIcons}>
<For each="icon" index="index" of={TabIcons}>
<div
key={color}
key={`${color}-${index}`}
className="tab-icon-select"
onClick={() => this.selectTabIcon(icon)}
>
@ -308,22 +337,30 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
</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 className="settings-label archived-label">
<div className="">Archived</div>
<Tooltip
message={`Archive will hide the tab. Commands and output will be retained in history.`}
icon={<i className="fa-sharp fa-regular fa-circle-question" />}
className="screen-settings-tooltip"
>
<i className="fa-sharp fa-regular fa-circle-question" />
</Tooltip>
</div>
<div className="settings-input">
<Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived} />
</div>
</div>
<div className="settings-field">
<div className="settings-label">
<div className="settings-label actions-label">
<div>Actions</div>
<InfoMessage width={400}>
Delete will remove the tab, removing all commands and output from history.
</InfoMessage>
<Tooltip
message={`Delete will remove the tab, removing all commands and output from history.`}
icon={<i className="fa-sharp fa-regular fa-circle-question" />}
className="screen-settings-tooltip"
>
<i className="fa-sharp fa-regular fa-circle-question" />
</Tooltip>
</div>
<div className="settings-input">
<div
@ -343,14 +380,16 @@ class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId:
}
@mobxReact.observer
class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
class SessionSettingsModal extends React.Component<{}, {}> {
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
session: Session;
sessionId: string;
constructor(props: any) {
super(props);
let { sessionId } = props;
let session = GlobalModel.getSessionById(sessionId);
if (session == null) {
let sessionId = GlobalModel.sessionSettingsModal.get();
this.session = GlobalModel.getSessionById(sessionId);
if (this.session == null) {
return;
}
}
@ -360,46 +399,42 @@ class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
mobx.action(() => {
GlobalModel.sessionSettingsModal.set(null);
})();
GlobalModel.modalsModel.popModal();
}
@boundMethod
handleInlineChangeName(newVal: string): void {
let { sessionId } = this.props;
let session = GlobalModel.getSessionById(sessionId);
if (session == null) {
if (this.session == null) {
return;
}
if (util.isStrEq(newVal, session.name.get())) {
if (util.isStrEq(newVal, this.session.name.get())) {
return;
}
let prtn = GlobalCommandRunner.sessionSetSettings(this.props.sessionId, { name: newVal }, false);
let prtn = GlobalCommandRunner.sessionSetSettings(this.sessionId, { name: newVal }, false);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeArchived(val: boolean): void {
let { sessionId } = this.props;
let session = GlobalModel.getSessionById(sessionId);
if (session == null) {
if (this.session == null) {
return;
}
if (session.archived.get() == val) {
if (this.session.archived.get() == val) {
return;
}
let prtn = GlobalCommandRunner.sessionArchive(this.props.sessionId, val);
let prtn = GlobalCommandRunner.sessionArchive(this.sessionId, val);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleDeleteSession(): void {
let { sessionId } = this.props;
let message = SessionDeleteMessage;
let alertRtn = GlobalModel.showAlert({ message: message, confirm: true, markdown: true });
alertRtn.then((result) => {
if (!result) {
return;
}
let prtn = GlobalCommandRunner.sessionPurge(this.props.sessionId);
let prtn = GlobalCommandRunner.sessionPurge(this.sessionId);
commandRtnHandler(prtn, this.errorMessage);
});
}
@ -412,86 +447,82 @@ class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
}
render() {
let { sessionId } = this.props;
let session = GlobalModel.getSessionById(sessionId);
if (session == null) {
if (this.session == null) {
return null;
}
return (
<div className={cn("modal session-settings-modal settings-modal prompt-modal is-active")}>
<div className="modal-background" />
<div className="modal-content">
<header>
<div className="modal-title">workspace settings ({session.name.get()})</div>
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
<XmarkIcon />
<Modal className="session-settings-modal">
<Modal.Header onClose={this.closeModal} title={`workspace settings (${this.session.name.get()})`} />
<div className="wave-modal-body">
<div className="settings-field">
<div className="settings-label">Name</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder="name"
text={this.session.name.get() ?? "(none)"}
value={this.session.name.get() ?? ""}
onChange={this.handleInlineChangeName}
maxLength={50}
showIcon={true}
/>
</div>
</header>
<div className="inner-content">
<div className="settings-field">
<div className="settings-label">Name</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder="name"
text={session.name.get() ?? "(none)"}
value={session.name.get() ?? ""}
onChange={this.handleInlineChangeName}
maxLength={50}
showIcon={true}
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">
<div>Archived</div>
<InfoMessage width={400}>
Archive will hide the workspace from the active menu. Commands and output will be
retained in history.
</InfoMessage>
</div>
<div className="settings-input">
<Toggle checked={session.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 workspace, removing all commands and output from history.
</InfoMessage>
</div>
<div className="settings-input">
<div
onClick={this.handleDeleteSession}
className="button is-prompt-danger is-outlined is-small"
>
Delete Workspace
</div>
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
</div>
<footer>
<div onClick={this.closeModal} className="button is-wave-green is-outlined is-small">
Close
<div className="settings-field">
<div className="settings-label">
<div>Archived</div>
<InfoMessage width={400}>
Archive will hide the workspace from the active menu. Commands and output will be
retained in history.
</InfoMessage>
</div>
</footer>
<div className="settings-input">
<Toggle checked={this.session.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 workspace, removing all commands and output from history.
</InfoMessage>
</div>
<div className="settings-input">
<div
onClick={this.handleDeleteSession}
className="button is-prompt-danger is-outlined is-small"
>
Delete Workspace
</div>
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
</div>
</div>
<Modal.Footer cancelLabel="Close" onCancel={this.closeModal} />
</Modal>
);
}
}
@mobxReact.observer
class LineSettingsModal extends React.Component<{ linenum: number }, {}> {
class LineSettingsModal extends React.Component<{}, {}> {
rendererDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "lineSettings-rendererDropdownActive" });
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
linenum: number;
constructor(props: any) {
super(props);
this.linenum = GlobalModel.lineSettingsModal.get();
if (this.linenum == null) {
return;
}
}
@boundMethod
closeModal(): void {
mobx.action(() => {
GlobalModel.lineSettingsModal.set(null);
})();
GlobalModel.modalsModel.popModal();
}
@boundMethod
@ -516,7 +547,7 @@ class LineSettingsModal extends React.Component<{ linenum: number }, {}> {
if (screen == null) {
return;
}
return screen.getLineByNum(this.props.linenum);
return screen.getLineByNum(this.linenum);
}
@boundMethod
@ -532,47 +563,42 @@ class LineSettingsModal extends React.Component<{ linenum: number }, {}> {
})();
}
renderRendererDropdown(): any {
let line = this.getLine();
if (line == null) {
return null;
}
let plugins = PluginModel.rendererPlugins;
let plugin: RendererPluginType = null;
let renderer = line.renderer ?? "terminal";
return (
<div className={cn("dropdown", "renderer-dropdown", { "is-active": this.rendererDropdownActive.get() })}>
<div className="dropdown-trigger">
<button onClick={this.toggleRendererDropdown} className="button is-small is-dark">
<span>
<i className="fa-sharp fa-solid fa-fill" /> {renderer}
</span>
<span className="icon is-small">
<i className="fa-sharp fa-regular fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div className="dropdown-menu" role="menu">
<div className="dropdown-content has-background-black">
<div onClick={() => this.clickSetRenderer(null)} key="terminal" className="dropdown-item">
terminal
</div>
<For each="plugin" of={plugins}>
<div
onClick={() => this.clickSetRenderer(plugin.name)}
key={plugin.name}
className="dropdown-item"
>
{plugin.name}
</div>
</For>
<div onClick={() => this.clickSetRenderer("none")} key="none" className="dropdown-item">
none
</div>
</div>
</div>
</div>
);
getOptions(plugins: RendererPluginType[]) {
// Add label and value to each object in the array
const options = plugins.map((item) => ({
...item,
label: item.name,
value: item.name,
}));
// Create an additional object with label "terminal" and value null
const terminalItem = {
label: "terminal",
value: null,
name: null,
rendererType: null,
heightType: null,
dataType: null,
collapseType: null,
globalCss: null,
mimeTypes: null,
};
// Create an additional object with label "none" and value none
const noneItem = {
label: "none",
value: "none",
name: null,
rendererType: null,
heightType: null,
dataType: null,
collapseType: null,
globalCss: null,
mimeTypes: null,
};
// Combine the options with the terminal item
return [terminalItem, ...options, noneItem];
}
render() {
@ -583,37 +609,35 @@ class LineSettingsModal extends React.Component<{ linenum: number }, {}> {
}, 0);
return null;
}
let plugins = PluginModel.rendererPlugins;
let renderer = line.renderer ?? "terminal";
return (
<div className={cn("modal line-settings-modal settings-modal prompt-modal is-active")}>
<div className="modal-background" />
<div className="modal-content">
<header>
<div className="modal-title">line settings ({line.linenum})</div>
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
<XmarkIcon />
<Modal className="line-settings-modal">
<Modal.Header onClose={this.closeModal} title={`line settings (${line.linenum})`} />
<div className="wave-modal-body">
<div className="settings-field">
<div className="settings-label">Renderer</div>
<div className="settings-input">
<Dropdown
className="renderer-dropdown"
options={this.getOptions(plugins)}
defaultValue={renderer}
onChange={this.clickSetRenderer}
/>
</div>
</header>
<div className="inner-content">
<div className="settings-field">
<div className="settings-label">Renderer</div>
<div className="settings-input">{this.renderRendererDropdown()}</div>
</div>
<div className="settings-field">
<div className="settings-label">Archived</div>
<div className="settings-input">
<Toggle checked={!!line.archived} onChange={this.handleChangeArchived} />
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
<div style={{ height: 50 }} />
</div>
<footer>
<div onClick={this.closeModal} className="button is-wave-green is-outlined is-small">
Close
<div className="settings-field">
<div className="settings-label">Archived</div>
<div className="settings-input">
<Toggle checked={!!line.archived} onChange={this.handleChangeArchived} />
</div>
</footer>
</div>
<SettingsError errorMessage={this.errorMessage} />
<div style={{ height: 50 }} />
</div>
</div>
<Modal.Footer cancelLabel="Close" onCancel={this.closeModal} />
</Modal>
);
}
}
@ -625,9 +649,7 @@ class ClientSettingsModal extends React.Component<{}, {}> {
@boundMethod
closeModal(): void {
mobx.action(() => {
GlobalModel.clientSettingsModal.set(false);
})();
GlobalModel.modalsModel.popModal();
}
@boundMethod
@ -638,7 +660,8 @@ class ClientSettingsModal extends React.Component<{}, {}> {
}
@boundMethod
handleChangeFontSize(newFontSize: number): void {
handleChangeFontSize(fontSize: string): void {
let newFontSize = Number(fontSize);
this.fontSizeDropdownActive.set(false);
if (GlobalModel.termFontSize.get() == newFontSize) {
return;
@ -665,36 +688,12 @@ class ClientSettingsModal extends React.Component<{}, {}> {
commandRtnHandler(prtn, this.errorMessage);
}
renderFontSizeDropdown(): any {
let availableFontSizes = [];
getFontSizes(): any {
let availableFontSizes: { label: string; value: number }[] = [];
for (let s = MinFontSize; s <= MaxFontSize; s++) {
availableFontSizes.push(s);
availableFontSizes.push({ label: s + "px", value: s });
}
let fsize: number = 0;
let curSize = GlobalModel.termFontSize.get();
return (
<div className={cn("dropdown", "font-size-dropdown", { "is-active": this.fontSizeDropdownActive.get() })}>
<div className="dropdown-trigger">
<button onClick={this.togglefontSizeDropdown} className="button">
<span>{curSize}px</span>
<AngleDownIcon className="icon" />
</button>
</div>
<div className="dropdown-menu" role="menu">
<div className="dropdown-content has-background-black">
<For each="fsize" of={availableFontSizes}>
<div
onClick={() => this.handleChangeFontSize(fsize)}
key={fsize + "px"}
className="dropdown-item"
>
{fsize}px
</div>
</For>
</div>
</div>
</div>
);
return availableFontSizes;
}
@boundMethod
@ -729,89 +728,85 @@ class ClientSettingsModal extends React.Component<{}, {}> {
let maxTokensStr = String(
openAIOpts.maxtokens == null || openAIOpts.maxtokens == 0 ? 1000 : openAIOpts.maxtokens
);
let curFontSize = GlobalModel.termFontSize.get();
return (
<div className={cn("modal client-settings-modal settings-modal prompt-modal is-active")}>
<div className="modal-background" />
<div className="modal-content">
<header>
<div className="modal-title">Client settings</div>
<div className="close-icon hoverEffect" title="Close (Escape)" onClick={this.closeModal}>
<XmarkIcon />
<Modal className="client-settings-modal">
<Modal.Header onClose={this.closeModal} title="Client settings" />
<div className="wave-modal-body">
<div className="settings-field">
<div className="settings-label">Term Font Size</div>
<div className="settings-input">
<Dropdown
className="font-size-dropdown"
options={this.getFontSizes()}
defaultValue={`${curFontSize}px`}
onChange={this.handleChangeFontSize}
/>
</div>
</header>
<div className="inner-content">
<div className="settings-field">
<div className="settings-label">Term Font Size</div>
<div className="settings-input">{this.renderFontSizeDropdown()}</div>
</div>
<div className="settings-field">
<div className="settings-label">Client ID</div>
<div className="settings-input">{cdata.clientid}</div>
</div>
<div className="settings-field">
<div className="settings-label">Client Version</div>
<div className="settings-input">
{VERSION} {BUILD}
</div>
</div>
<div className="settings-field">
<div className="settings-label">DB Version</div>
<div className="settings-input">{cdata.dbversion}</div>
</div>
<div className="settings-field">
<div className="settings-label">Basic Telemetry</div>
<div className="settings-input">
<Toggle checked={!cdata.clientopts.notelemetry} onChange={this.handleChangeTelemetry} />
</div>
</div>
<div className="settings-field">
<div className="settings-label">OpenAI Token</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder=""
text={apiTokenStr}
value={""}
onChange={this.inlineUpdateOpenAIToken}
maxLength={100}
showIcon={true}
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">OpenAI Model</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder="gpt-3.5-turbo"
text={util.isBlank(openAIOpts.model) ? "gpt-3.5-turbo" : openAIOpts.model}
value={openAIOpts.model ?? ""}
onChange={this.inlineUpdateOpenAIModel}
maxLength={100}
showIcon={true}
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">OpenAI MaxTokens</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder=""
text={maxTokensStr}
value={maxTokensStr}
onChange={this.inlineUpdateOpenAIMaxTokens}
maxLength={10}
showIcon={true}
/>
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
</div>
<footer>
<div onClick={this.closeModal} className="button is-wave-green is-outlined is-small">
Close
<div className="settings-field">
<div className="settings-label">Client ID</div>
<div className="settings-input">{cdata.clientid}</div>
</div>
<div className="settings-field">
<div className="settings-label">Client Version</div>
<div className="settings-input">
{VERSION} {BUILD}
</div>
</footer>
</div>
<div className="settings-field">
<div className="settings-label">DB Version</div>
<div className="settings-input">{cdata.dbversion}</div>
</div>
<div className="settings-field">
<div className="settings-label">Basic Telemetry</div>
<div className="settings-input">
<Toggle checked={!cdata.clientopts.notelemetry} onChange={this.handleChangeTelemetry} />
</div>
</div>
<div className="settings-field">
<div className="settings-label">OpenAI Token</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder=""
text={apiTokenStr}
value={""}
onChange={this.inlineUpdateOpenAIToken}
maxLength={100}
showIcon={true}
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">OpenAI Model</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder="gpt-3.5-turbo"
text={util.isBlank(openAIOpts.model) ? "gpt-3.5-turbo" : openAIOpts.model}
value={openAIOpts.model ?? ""}
onChange={this.inlineUpdateOpenAIModel}
maxLength={100}
showIcon={true}
/>
</div>
</div>
<div className="settings-field">
<div className="settings-label">OpenAI MaxTokens</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder=""
text={maxTokensStr}
value={maxTokensStr}
onChange={this.inlineUpdateOpenAIMaxTokens}
maxLength={10}
showIcon={true}
/>
</div>
</div>
<SettingsError errorMessage={this.errorMessage} />
</div>
</div>
<Modal.Footer cancelLabel="Close" onCancel={this.closeModal} />
</Modal>
);
}
}

View File

@ -39,6 +39,7 @@ import { PluginModel } from "../../plugins/plugins";
import { Prompt } from "../common/prompt/prompt";
import * as lineutil from "./lineutil";
import { ErrorBoundary } from "../../app/common/error/errorboundary";
import * as constants from "../appconst";
import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg";
import { ReactComponent as CommentIcon } from "../assets/icons/line/comment.svg";
@ -439,6 +440,7 @@ class LineCmd extends React.Component<
mobx.action(() => {
GlobalModel.lineSettingsModal.set(line.linenum);
})();
GlobalModel.modalsModel.pushModal(constants.LINE_SETTINGS);
}
}

View File

@ -15,7 +15,6 @@ import { ReactComponent as HelpIcon } from "../assets/icons/help.svg";
import { ReactComponent as SettingsIcon } from "../assets/icons/settings.svg";
import { ReactComponent as DiscordIcon } from "../assets/icons/discord.svg";
import { ReactComponent as HistoryIcon } from "../assets/icons/history.svg";
import { ReactComponent as FavoritesIcon } from "../assets/icons/favourites.svg";
import { ReactComponent as AppsIcon } from "../assets/icons/apps.svg";
import { ReactComponent as ConnectionsIcon } from "../assets/icons/connections.svg";
import { ReactComponent as WorkspacesIcon } from "../assets/icons/workspaces.svg";
@ -25,6 +24,7 @@ import { ReactComponent as ActionsIcon } from "../assets/icons/tab/actions.svg";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner, Session } from "../../model/model";
import { sortAndFilterRemotes, isBlank, openLink } from "../../util/util";
import * as constants from "../appconst";
import "./sidebar.less";
@ -130,9 +130,7 @@ class MainSideBar extends React.Component<{}, {}> {
@boundMethod
handleSettingsClick(): void {
mobx.action(() => {
GlobalModel.clientSettingsModal.set(true);
})();
GlobalModel.modalsModel.pushModal(constants.CLIENT_SETTINGS);
}
@boundMethod
@ -142,6 +140,7 @@ class MainSideBar extends React.Component<{}, {}> {
mobx.action(() => {
GlobalModel.sessionSettingsModal.set(session.sessionId);
})();
GlobalModel.modalsModel.pushModal(constants.SESSION_SETTINGS);
}
getSessions() {

View File

@ -131,6 +131,29 @@
gap: 8px;
align-self: stretch;
.conn-dropdown {
width: 412px;
.lefticon {
position: absolute;
top: 50%;
left: 16px;
transform: translateY(-50%);
.globe-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.status-icon {
position: absolute;
left: 7px;
top: 8px;
}
}
}
&.conn-section {
gap: 8px;
}
@ -152,7 +175,7 @@
padding: 8px 0 8px 2px;
align-items: flex-start;
gap: 14px;
&.tabicon-list {
gap: 12px;
}
@ -164,7 +187,6 @@
position: relative;
font-size: 14px;
&.tabicon {
display: flex;
align-items: center;

View File

@ -19,12 +19,13 @@ 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_deprecated/connections";
import * as util from "../../../util/util";
import { TextField } from "../../common/common";
import { TextField, Dropdown } from "../../common/common";
import { ReactComponent as EllipseIcon } from "../../assets/icons/ellipse.svg";
import { ReactComponent as Check12Icon } from "../../assets/icons/check12.svg";
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg";
import { ReactComponent as StatusCircleIcon } from "../../assets/icons/statuscircle.svg";
import "./screenview.less";
import "./tabs.less";
@ -53,6 +54,12 @@ class ScreenView extends React.Component<{ session: Session; screen: Screen }, {
class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
connDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "NewTabSettings-connDropdownActive" });
errorMessage: OV<string | null> = mobx.observable.box(null, { name: "NewTabSettings-errorMessage" });
remotes: T.RemoteType[];
constructor(props) {
super(props);
this.remotes = GlobalModel.remotes;
}
@boundMethod
selectTabColor(color: string): void {
@ -99,6 +106,28 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
}
@boundMethod
getOptions(): { label: string; value: string }[] {
return this.remotes
.filter((r) => !r.archived)
.map((remote) => ({
...remote,
label:
remote.remotealias && !util.isBlank(remote.remotealias)
? `${remote.remotecanonicalname}`
: remote.remotecanonicalname,
value: remote.remotecanonicalname,
}))
.sort((a, b) => {
let connValA = util.getRemoteConnVal(a);
let connValB = util.getRemoteConnVal(b);
if (connValA !== connValB) {
return connValA - connValB;
}
return a.remoteidx - b.remoteidx;
});
}
renderTabIconSelector(): React.ReactNode {
let { screen } = this.props;
let curIcon = screen.getTabIcon();
@ -163,6 +192,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
let { screen } = this.props;
let rptr = screen.curRemote.get();
let curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
return (
<div className="newtab-container">
<div className="newtab-section name-section">
@ -179,11 +209,20 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
You're connected to [{getRemoteStr(rptr)}]. Do you want to change it?
</div>
<div>
<ConnectionDropdown
curRemote={curRemote}
allowNewConn={true}
onSelectRemote={this.selectRemote}
onNewConn={this.clickNewConnection}
<Dropdown
className="conn-dropdown"
label={curRemote.remotealias}
options={this.getOptions()}
defaultValue={curRemote.remotecanonicalname}
onChange={this.selectRemote}
decoration={{
startDecoration: (
<div className="lefticon">
<GlobeIcon className="globe-icon" />
<StatusCircleIcon className={cn("status-icon", "status-" + curRemote.status)} />
</div>
),
}}
/>
</div>
<div className="text-caption cr-help-text">

View File

@ -11,11 +11,12 @@ import cn from "classnames";
import { debounce } from "throttle-debounce";
import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel, GlobalCommandRunner, Session, Screen, TabIcons } from "../../../model/model";
import { GlobalModel, GlobalCommandRunner, Session, Screen } 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";
import { ReactComponent as AddIcon } from "../../assets/icons/add.svg";
import * as constants from "../../appconst";
import "../workspace.less";
import "./tabs.less";
@ -100,6 +101,7 @@ class ScreenTabs extends React.Component<{ session: Session }, {}> {
mobx.action(() => {
GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId });
})();
GlobalModel.modalsModel.pushModal(constants.SCREEN_SETTINGS);
}
renderTabIcon = (screen: Screen): React.ReactNode => {

View File

@ -390,7 +390,7 @@ function getColorRGB(colorInput) {
return computedColorStyle;
}
function commandRtnHandler(prtn: Promise<CommandRtnType>, errorMessage: OV<string>) {
function commandRtnHandler(prtn: Promise<CommandRtnType>, errorMessage: OV<string>) {
prtn.then((crtn) => {
if (crtn.success) {
return;
@ -424,4 +424,5 @@ export {
openLink,
getColorRGB,
commandRtnHandler,
getRemoteConnVal,
};