Move the Client Settings Modal into its own "main view" (#226)

* init

* client settings view

* add some extra padding and a border below the header

* move settings-field classes under the main class to prevent conflicts

* fix style overrides
This commit is contained in:
Red J Adaya 2024-01-16 04:08:58 +08:00 committed by GitHub
parent ff65a3f042
commit da69c0411d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 447 additions and 18 deletions

View File

@ -16,6 +16,7 @@ import { PluginsView } from "./pluginsview/pluginsview";
import { BookmarksView } from "./bookmarks/bookmarks"; import { BookmarksView } from "./bookmarks/bookmarks";
import { HistoryView } from "./history/history"; import { HistoryView } from "./history/history";
import { ConnectionsView } from "./connections/connections"; import { ConnectionsView } from "./connections/connections";
import { ClientSettingsView } from "./clientsettings/clientsettings";
import { MainSideBar } from "./sidebar/sidebar"; import { MainSideBar } from "./sidebar/sidebar";
import { DisconnectedModal, ClientStopModal } from "./common/modals"; import { DisconnectedModal, ClientStopModal } from "./common/modals";
import { ModalsProvider } from "./common/modals/provider"; import { ModalsProvider } from "./common/modals/provider";
@ -109,6 +110,7 @@ class App extends React.Component<{}, {}> {
<HistoryView /> <HistoryView />
<BookmarksView /> <BookmarksView />
<ConnectionsView model={remotesModel} /> <ConnectionsView model={remotesModel} />
<ClientSettingsView model={remotesModel} />
</ErrorBoundary> </ErrorBoundary>
</div> </div>
<ModalsProvider /> <ModalsProvider />

View File

@ -0,0 +1,200 @@
@import "../../app/common/themes/themes.less";
.clientsettings-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;
border: 1px solid rgba(241, 246, 243, 0.08);
background: rgba(13, 13, 13, 0.85);
.header {
margin: 24px 18px;
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 20px;
border-bottom: 1px solid white;
}
.content {
padding: 0 18px 0 30px;
}
// just marked these as important since we're keeping the
// settings-field styles below this intact until we figure out what do with them
.settings-field {
&:not(:first-child) {
margin-top: 15px !important;
}
.settings-label {
width: 250px !important;
}
}
// @TODO: These styles are duplicated in the settings modals
.settings-field {
display: flex;
flex-direction: row;
align-items: center;
&.settings-field.sub-field {
.settings-label {
font-weight: normal;
text-align: right;
padding-right: 20px;
}
}
&.settings-error {
color: @term-red;
margin-top: 20px;
padding: 10px;
border-radius: 5px;
background-color: #200707;
border: 1px solid @term-red;
font-weight: bold;
.error-dismiss {
padding: 2px;
cursor: pointer;
}
}
.settings-label {
font-weight: bold;
width: 12em;
display: flex;
flex-direction: row;
align-items: center;
margin-right: 10px;
.info-message {
margin-left: 5px;
}
}
.settings-input {
display: flex;
flex-direction: row;
align-items: center;
&.settings-clickable {
cursor: pointer;
}
&.inline-edit.edit-active {
input.input {
padding: 0;
height: 20px;
}
.button {
height: 20px;
}
}
input {
padding: 4px;
border-radius: 3px;
}
.control {
.icon {
width: 1.5em;
height: 1.5em;
margin: 0;
}
}
.tab-color-icon.color-default path {
fill: @tab-green;
}
.tab-color-icon.color-green path {
fill: @tab-green;
}
.tab-color-icon.color-orange path {
fill: @tab-orange;
}
.tab-color-icon.color-red path {
fill: @tab-red;
}
.tab-color-icon.color-yellow path {
fill: @tab-yellow;
}
.tab-color-icon.color-blue path {
fill: @tab-blue;
}
.tab-color-icon.color-mint path {
fill: @tab-mint;
}
.tab-color-icon.color-cyan path {
fill: @tab-cyan;
}
.tab-color-icon.color-white path {
fill: @tab-white;
}
.tab-color-icon.color-violet path {
fill: @tab-violet;
}
.tab-color-icon.color-pink path {
fill: @tab-pink;
}
.tab-colors,
.tab-icons {
display: flex;
flex-direction: row;
align-items: center;
.tab-color-sep,
.tab-icon-sep {
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
}
.tab-color-icon,
.tab-icon-icon {
width: 1.1em;
vertical-align: middle;
}
.tab-color-name,
.tab-icon-name {
margin-left: 1em;
}
.tab-color-select,
.tab-icon-select {
cursor: pointer;
margin: 5px;
&:hover {
outline: 2px solid white;
}
}
}
.action-text {
margin-left: 20px;
color: @term-red;
}
.settings-share-link {
width: 160px;
}
}
&:not(:first-child) {
margin-top: 10px;
}
}
}

View File

@ -0,0 +1,219 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { boundMethod } from "autobind-decorator";
import cn from "classnames";
import { GlobalModel, GlobalCommandRunner, MinFontSize, MaxFontSize, RemotesModel } from "../../model/model";
import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "../common/common";
import { CommandRtnType, ClientDataType } from "../../types/types";
import { commandRtnHandler, isBlank } from "../../util/util";
import "./clientsettings.less";
type OV<V> = mobx.IObservableValue<V>;
// @ts-ignore
const VERSION = __WAVETERM_VERSION__;
// @ts-ignore
const BUILD = __WAVETERM_BUILD__;
@mobxReact.observer
class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hoveredItemId: string }> {
fontSizeDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "clientSettings-fontSizeDropdownActive" });
errorMessage: OV<string> = mobx.observable.box(null, { name: "ClientSettings-errorMessage" });
@boundMethod
closeModal(): void {
GlobalModel.modalsModel.popModal();
}
@boundMethod
dismissError(): void {
mobx.action(() => {
this.errorMessage.set(null);
})();
}
@boundMethod
handleChangeFontSize(fontSize: string): void {
let newFontSize = Number(fontSize);
this.fontSizeDropdownActive.set(false);
if (GlobalModel.termFontSize.get() == newFontSize) {
return;
}
let prtn = GlobalCommandRunner.setTermFontSize(newFontSize, false);
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
togglefontSizeDropdown(): void {
mobx.action(() => {
this.fontSizeDropdownActive.set(!this.fontSizeDropdownActive.get());
})();
}
@boundMethod
handleChangeTelemetry(val: boolean): void {
let prtn: Promise<CommandRtnType> = null;
if (val) {
prtn = GlobalCommandRunner.telemetryOn(false);
} else {
prtn = GlobalCommandRunner.telemetryOff(false);
}
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeReleaseCheck(val: boolean): void {
let prtn: Promise<CommandRtnType> = null;
if (val) {
prtn = GlobalCommandRunner.releaseCheckAutoOn(false);
} else {
prtn = GlobalCommandRunner.releaseCheckAutoOff(false);
}
commandRtnHandler(prtn, this.errorMessage);
}
getFontSizes(): any {
let availableFontSizes: { label: string; value: number }[] = [];
for (let s = MinFontSize; s <= MaxFontSize; s++) {
availableFontSizes.push({ label: s + "px", value: s });
}
return availableFontSizes;
}
@boundMethod
inlineUpdateOpenAIModel(newModel: string): void {
let prtn = GlobalCommandRunner.setClientOpenAISettings({ model: newModel });
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
inlineUpdateOpenAIToken(newToken: string): void {
let prtn = GlobalCommandRunner.setClientOpenAISettings({ apitoken: newToken });
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
inlineUpdateOpenAIMaxTokens(newMaxTokensStr: string): void {
let prtn = GlobalCommandRunner.setClientOpenAISettings({ maxtokens: newMaxTokensStr });
commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
setErrorMessage(msg: string): void {
mobx.action(() => {
this.errorMessage.set(msg);
})();
}
render() {
let isHidden = GlobalModel.activeMainView.get() != "clientsettings";
if (isHidden) {
return null;
}
let cdata: ClientDataType = GlobalModel.clientData.get();
let openAIOpts = cdata.openaiopts ?? {};
let apiTokenStr = isBlank(openAIOpts.apitoken) ? "(not set)" : "********";
let maxTokensStr = String(
openAIOpts.maxtokens == null || openAIOpts.maxtokens == 0 ? 1000 : openAIOpts.maxtokens
);
let curFontSize = GlobalModel.termFontSize.get();
return (
<div className={cn("clientsettings-view")}>
<header className="header">
<div className="clientsettings-title text-primary">Client Settings</div>
</header>
<div className="content">
<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>
</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">Check for Updates</div>
<div className="settings-input">
<Toggle
checked={!cdata.clientopts.noreleasecheck}
onChange={this.handleChangeReleaseCheck}
/>
</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={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>
);
}
}
export { ClientSettingsView };

View File

@ -10,7 +10,7 @@ import {
EditRemoteConnModal, EditRemoteConnModal,
TabSwitcherModal, TabSwitcherModal,
} from "../modals"; } from "../modals";
import { ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal } from "./settings"; import { ScreenSettingsModal, SessionSettingsModal, LineSettingsModal } from "./settings";
import * as constants from "../../appconst"; import * as constants from "../../appconst";
const modalsRegistry: { [key: string]: () => React.ReactElement } = { const modalsRegistry: { [key: string]: () => React.ReactElement } = {
@ -22,7 +22,6 @@ const modalsRegistry: { [key: string]: () => React.ReactElement } = {
[constants.SCREEN_SETTINGS]: () => <ScreenSettingsModal />, [constants.SCREEN_SETTINGS]: () => <ScreenSettingsModal />,
[constants.SESSION_SETTINGS]: () => <SessionSettingsModal />, [constants.SESSION_SETTINGS]: () => <SessionSettingsModal />,
[constants.LINE_SETTINGS]: () => <LineSettingsModal />, [constants.LINE_SETTINGS]: () => <LineSettingsModal />,
[constants.CLIENT_SETTINGS]: () => <ClientSettingsModal />,
[constants.TAB_SWITCHER]: () => <TabSwitcherModal />, [constants.TAB_SWITCHER]: () => <TabSwitcherModal />,
}; };

View File

@ -17,22 +17,12 @@ import {
Screen, Screen,
Session, Session,
} from "../../../model/model"; } from "../../../model/model";
import { import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage, Modal, Dropdown, Tooltip } from "../common";
Toggle,
InlineSettingsTextEdit,
SettingsError,
InfoMessage,
Modal,
Dropdown,
Tooltip,
Button,
} from "../common";
import { LineType, RendererPluginType, ClientDataType, CommandRtnType, RemoteType } from "../../../types/types"; import { LineType, RendererPluginType, ClientDataType, CommandRtnType, RemoteType } from "../../../types/types";
import { PluginModel } from "../../../plugins/plugins"; import { PluginModel } from "../../../plugins/plugins";
import * as util from "../../../util/util"; import * as util from "../../../util/util";
import { commandRtnHandler } from "../../../util/util"; import { commandRtnHandler } from "../../../util/util";
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg"; import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
import { ReactComponent as AngleDownIcon } from "../../assets/icons/history/angle-down.svg";
import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg"; import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg";
import { ReactComponent as StatusCircleIcon } from "../../assets/icons/statuscircle.svg"; import { ReactComponent as StatusCircleIcon } from "../../assets/icons/statuscircle.svg";

View File

@ -131,7 +131,11 @@ class MainSideBar extends React.Component<{}, {}> {
@boundMethod @boundMethod
handleSettingsClick(): void { handleSettingsClick(): void {
GlobalModel.modalsModel.pushModal(constants.CLIENT_SETTINGS); if (GlobalModel.activeMainView.get() == "clientsettings") {
GlobalModel.showSessionView();
return;
}
GlobalCommandRunner.clientSettingsView();
} }
@boundMethod @boundMethod

View File

@ -2576,6 +2576,14 @@ class ConnectionsViewModel {
} }
} }
class ClientSettingsViewModel {
showClientSettingsView(): void {
mobx.action(() => {
GlobalModel.activeMainView.set("clientsettings");
})();
}
}
class BookmarksModel { class BookmarksModel {
bookmarks: OArr<BookmarkType> = mobx.observable.array([], { bookmarks: OArr<BookmarkType> = mobx.observable.array([], {
name: "Bookmarks", name: "Bookmarks",
@ -3305,10 +3313,11 @@ class Model {
authKey: string; authKey: string;
isDev: boolean; isDev: boolean;
platform: string; platform: string;
activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare" | "connections"> = activeMainView: OV<
mobx.observable.box("session", { "plugins" | "session" | "history" | "bookmarks" | "webshare" | "connections" | "clientsettings"
name: "activeMainView", > = mobx.observable.box("session", {
}); name: "activeMainView",
});
termFontSize: CV<number>; termFontSize: CV<number>;
alertMessage: OV<AlertMessageType> = mobx.observable.box(null, { alertMessage: OV<AlertMessageType> = mobx.observable.box(null, {
name: "alertMessage", name: "alertMessage",
@ -3337,6 +3346,7 @@ class Model {
bookmarksModel: BookmarksModel; bookmarksModel: BookmarksModel;
historyViewModel: HistoryViewModel; historyViewModel: HistoryViewModel;
connectionViewModel: ConnectionsViewModel; connectionViewModel: ConnectionsViewModel;
clientSettingsViewModel: ClientSettingsViewModel;
modalsModel: ModalsModel; modalsModel: ModalsModel;
clientData: OV<ClientDataType> = mobx.observable.box(null, { clientData: OV<ClientDataType> = mobx.observable.box(null, {
name: "clientData", name: "clientData",
@ -3360,6 +3370,7 @@ class Model {
this.bookmarksModel = new BookmarksModel(); this.bookmarksModel = new BookmarksModel();
this.historyViewModel = new HistoryViewModel(); this.historyViewModel = new HistoryViewModel();
this.connectionViewModel = new ConnectionsViewModel(); this.connectionViewModel = new ConnectionsViewModel();
this.clientSettingsViewModel = new ClientSettingsViewModel();
this.remotesModalModel = new RemotesModalModel(); this.remotesModalModel = new RemotesModalModel();
this.remotesModel = new RemotesModel(); this.remotesModel = new RemotesModel();
this.modalsModel = new ModalsModel(); this.modalsModel = new ModalsModel();
@ -4794,6 +4805,10 @@ class CommandRunner {
GlobalModel.connectionViewModel.showConnectionsView(); GlobalModel.connectionViewModel.showConnectionsView();
} }
clientSettingsView() {
GlobalModel.clientSettingsViewModel.showClientSettingsView();
}
historyView(params: HistorySearchParams) { historyView(params: HistorySearchParams) {
let kwargs = { nohist: "1" }; let kwargs = { nohist: "1" };
kwargs["offset"] = String(params.offset); kwargs["offset"] = String(params.offset);