mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
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:
parent
ff65a3f042
commit
da69c0411d
@ -16,6 +16,7 @@ import { PluginsView } from "./pluginsview/pluginsview";
|
||||
import { BookmarksView } from "./bookmarks/bookmarks";
|
||||
import { HistoryView } from "./history/history";
|
||||
import { ConnectionsView } from "./connections/connections";
|
||||
import { ClientSettingsView } from "./clientsettings/clientsettings";
|
||||
import { MainSideBar } from "./sidebar/sidebar";
|
||||
import { DisconnectedModal, ClientStopModal } from "./common/modals";
|
||||
import { ModalsProvider } from "./common/modals/provider";
|
||||
@ -109,6 +110,7 @@ class App extends React.Component<{}, {}> {
|
||||
<HistoryView />
|
||||
<BookmarksView />
|
||||
<ConnectionsView model={remotesModel} />
|
||||
<ClientSettingsView model={remotesModel} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<ModalsProvider />
|
||||
|
200
src/app/clientsettings/clientsettings.less
Normal file
200
src/app/clientsettings/clientsettings.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
219
src/app/clientsettings/clientsettings.tsx
Normal file
219
src/app/clientsettings/clientsettings.tsx
Normal 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 };
|
@ -10,7 +10,7 @@ import {
|
||||
EditRemoteConnModal,
|
||||
TabSwitcherModal,
|
||||
} from "../modals";
|
||||
import { ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal } from "./settings";
|
||||
import { ScreenSettingsModal, SessionSettingsModal, LineSettingsModal } from "./settings";
|
||||
import * as constants from "../../appconst";
|
||||
|
||||
const modalsRegistry: { [key: string]: () => React.ReactElement } = {
|
||||
@ -22,7 +22,6 @@ const modalsRegistry: { [key: string]: () => React.ReactElement } = {
|
||||
[constants.SCREEN_SETTINGS]: () => <ScreenSettingsModal />,
|
||||
[constants.SESSION_SETTINGS]: () => <SessionSettingsModal />,
|
||||
[constants.LINE_SETTINGS]: () => <LineSettingsModal />,
|
||||
[constants.CLIENT_SETTINGS]: () => <ClientSettingsModal />,
|
||||
[constants.TAB_SWITCHER]: () => <TabSwitcherModal />,
|
||||
};
|
||||
|
||||
|
@ -17,22 +17,12 @@ import {
|
||||
Screen,
|
||||
Session,
|
||||
} from "../../../model/model";
|
||||
import {
|
||||
Toggle,
|
||||
InlineSettingsTextEdit,
|
||||
SettingsError,
|
||||
InfoMessage,
|
||||
Modal,
|
||||
Dropdown,
|
||||
Tooltip,
|
||||
Button,
|
||||
} from "../common";
|
||||
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 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";
|
||||
|
||||
|
@ -131,7 +131,11 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
|
||||
@boundMethod
|
||||
handleSettingsClick(): void {
|
||||
GlobalModel.modalsModel.pushModal(constants.CLIENT_SETTINGS);
|
||||
if (GlobalModel.activeMainView.get() == "clientsettings") {
|
||||
GlobalModel.showSessionView();
|
||||
return;
|
||||
}
|
||||
GlobalCommandRunner.clientSettingsView();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
|
@ -2576,6 +2576,14 @@ class ConnectionsViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
class ClientSettingsViewModel {
|
||||
showClientSettingsView(): void {
|
||||
mobx.action(() => {
|
||||
GlobalModel.activeMainView.set("clientsettings");
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
class BookmarksModel {
|
||||
bookmarks: OArr<BookmarkType> = mobx.observable.array([], {
|
||||
name: "Bookmarks",
|
||||
@ -3305,10 +3313,11 @@ class Model {
|
||||
authKey: string;
|
||||
isDev: boolean;
|
||||
platform: string;
|
||||
activeMainView: OV<"plugins" | "session" | "history" | "bookmarks" | "webshare" | "connections"> =
|
||||
mobx.observable.box("session", {
|
||||
name: "activeMainView",
|
||||
});
|
||||
activeMainView: OV<
|
||||
"plugins" | "session" | "history" | "bookmarks" | "webshare" | "connections" | "clientsettings"
|
||||
> = mobx.observable.box("session", {
|
||||
name: "activeMainView",
|
||||
});
|
||||
termFontSize: CV<number>;
|
||||
alertMessage: OV<AlertMessageType> = mobx.observable.box(null, {
|
||||
name: "alertMessage",
|
||||
@ -3337,6 +3346,7 @@ class Model {
|
||||
bookmarksModel: BookmarksModel;
|
||||
historyViewModel: HistoryViewModel;
|
||||
connectionViewModel: ConnectionsViewModel;
|
||||
clientSettingsViewModel: ClientSettingsViewModel;
|
||||
modalsModel: ModalsModel;
|
||||
clientData: OV<ClientDataType> = mobx.observable.box(null, {
|
||||
name: "clientData",
|
||||
@ -3360,6 +3370,7 @@ class Model {
|
||||
this.bookmarksModel = new BookmarksModel();
|
||||
this.historyViewModel = new HistoryViewModel();
|
||||
this.connectionViewModel = new ConnectionsViewModel();
|
||||
this.clientSettingsViewModel = new ClientSettingsViewModel();
|
||||
this.remotesModalModel = new RemotesModalModel();
|
||||
this.remotesModel = new RemotesModel();
|
||||
this.modalsModel = new ModalsModel();
|
||||
@ -4794,6 +4805,10 @@ class CommandRunner {
|
||||
GlobalModel.connectionViewModel.showConnectionsView();
|
||||
}
|
||||
|
||||
clientSettingsView() {
|
||||
GlobalModel.clientSettingsViewModel.showClientSettingsView();
|
||||
}
|
||||
|
||||
historyView(params: HistorySearchParams) {
|
||||
let kwargs = { nohist: "1" };
|
||||
kwargs["offset"] = String(params.offset);
|
||||
|
Loading…
Reference in New Issue
Block a user