mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08f2da159e | |||
| 641c8da1c7 | |||
| 85c49032f7 | |||
| a4f405451c | |||
| bfe9d8f9ac | |||
| 65cab8a59a | |||
| c737c9df57 | |||
| 17cc4769af | |||
| b4d04c3a57 | |||
| 5e4eb738b3 | |||
| 936e9fa7b4 | |||
| af6486e8bb | |||
| 1038a08789 | |||
| 7a377c4a2a | |||
| 8afa62fff2 | |||
| 97595a7718 | |||
| e4d353b5c1 | |||
| 2462c64793 | |||
| b451ee1ef9 | |||
| 2c64668382 | |||
| de21303fb1 | |||
| f6cdd53fd4 | |||
| 840b69d100 | |||
| 683c66f386 |
+10
-2
@@ -20,7 +20,7 @@ import { MainSideBar } from "./sidebar/main";
|
||||
import { RightSideBar } from "./sidebar/right";
|
||||
import { DisconnectedModal, ClientStopModal } from "@/modals";
|
||||
import { ModalsProvider } from "@/modals/provider";
|
||||
import { Button } from "@/elements";
|
||||
import { Button, TermStyleBlock } from "@/elements";
|
||||
import { ErrorBoundary } from "@/common/error/errorboundary";
|
||||
import cn from "classnames";
|
||||
import "./app.less";
|
||||
@@ -124,9 +124,11 @@ class App extends React.Component<{}, {}> {
|
||||
const rightSidebarCollapsed = GlobalModel.rightSidebarModel.getCollapsed();
|
||||
const activeMainView = GlobalModel.activeMainView.get();
|
||||
const lightDarkClass = GlobalModel.isDarkTheme.get() ? "is-dark" : "is-light";
|
||||
const termTheme = GlobalModel.getTermTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
key={"version-" + renderVersion}
|
||||
key={`version- + ${renderVersion}`}
|
||||
id="main"
|
||||
className={cn(
|
||||
"platform-" + platform,
|
||||
@@ -151,6 +153,12 @@ class App extends React.Component<{}, {}> {
|
||||
</div>
|
||||
</If>
|
||||
<div ref={this.mainContentRef} className="main-content">
|
||||
<TermStyleBlock
|
||||
scope="main"
|
||||
termTheme={termTheme}
|
||||
themeSrcEl={this.mainContentRef.current}
|
||||
themeKey="main"
|
||||
/>
|
||||
<MainSideBar parentRef={this.mainContentRef} />
|
||||
<ErrorBoundary>
|
||||
<PluginsView />
|
||||
|
||||
@@ -76,14 +76,14 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
|
||||
@boundMethod
|
||||
handleChangeTermTheme(theme: string): void {
|
||||
// For global terminal theme, the key is global, otherwise it's either
|
||||
// For main terminal theme, the key is main, otherwise it's either
|
||||
// sessionId or screenId.
|
||||
const currTheme = GlobalModel.getTermTheme()["global"];
|
||||
const currTheme = GlobalModel.getTermTheme()["main"];
|
||||
if (currTheme == theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prtn = GlobalCommandRunner.setGlobalTermTheme(theme, false);
|
||||
const prtn = GlobalCommandRunner.setMainTermTheme(theme, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
const curFontFamily = GlobalModel.getTermFontFamily();
|
||||
const curTheme = GlobalModel.getThemeSource();
|
||||
const termThemes = getTermThemes(GlobalModel.termThemes, "Wave Default");
|
||||
const currTermTheme = GlobalModel.getTermTheme()["global"] ?? termThemes[0].label;
|
||||
const currTermTheme = GlobalModel.getTermTheme()["main"] ?? termThemes[0].label;
|
||||
|
||||
return (
|
||||
<MainView className="clientsettings-view" title="Client Settings" onClose={this.handleClose}>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
width: 100%;
|
||||
border: 2px solid var(--form-element-border-color);
|
||||
border-radius: 6px;
|
||||
line-height: 22px;
|
||||
line-height: 20px;
|
||||
background: var(--form-element-bg-color);
|
||||
|
||||
&.no-label {
|
||||
@@ -63,11 +63,14 @@
|
||||
display: flex;
|
||||
min-width: 120px;
|
||||
padding: 5px 8px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
border-radius: 6px;
|
||||
|
||||
i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&-highlighted,
|
||||
&:hover {
|
||||
background: rgba(241, 246, 243, 0.08);
|
||||
|
||||
@@ -163,7 +163,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
if (isOpen) {
|
||||
const option = options[highlightedIndex];
|
||||
if (option) {
|
||||
this.handleSelect(option.value, undefined);
|
||||
this.handleSelect(option, undefined);
|
||||
}
|
||||
} else {
|
||||
this.toggleDropdown();
|
||||
@@ -185,18 +185,19 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
handleKeyDown(event: React.KeyboardEvent) {}
|
||||
|
||||
@boundMethod
|
||||
handleSelect(value: string, event?: React.MouseEvent | React.KeyboardEvent) {
|
||||
handleSelect({ value, noop }: DropdownItem, event?: React.MouseEvent | React.KeyboardEvent) {
|
||||
const { onChange } = this.props;
|
||||
if (event) {
|
||||
event.stopPropagation(); // This stops the event from bubbling up to the wrapper
|
||||
}
|
||||
|
||||
if (!("value" in this.props)) {
|
||||
this.setState({ internalValue: value });
|
||||
}
|
||||
onChange(value);
|
||||
this.setState({ isOpen: false, isTouched: true });
|
||||
this.unregisterKeybindings();
|
||||
|
||||
if (!("value" in this.props) && !noop) {
|
||||
this.setState({ internalValue: value });
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@@ -245,10 +246,11 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
className={cn("wave-dropdown-item unselectable", {
|
||||
"wave-dropdown-item-highlighted": index === highlightedIndex,
|
||||
})}
|
||||
onClick={(e) => this.handleSelect(option.value, e)}
|
||||
onClick={(e) => this.handleSelect(option, e)}
|
||||
onMouseEnter={() => this.setState({ highlightedIndex: index })}
|
||||
onMouseLeave={() => this.setState({ highlightedIndex: -1 })}
|
||||
>
|
||||
{option.icon && <span className="wave-dropdown-item-icon">{option.icon}</span>}
|
||||
{option.label}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -18,4 +18,5 @@ export { Toggle } from "./toggle";
|
||||
export { Tooltip } from "./tooltip";
|
||||
export { TabIcon } from "./tabicon";
|
||||
export { DatePicker } from "./datepicker";
|
||||
export { TermStyleBlock } from "./termstyleblock";
|
||||
export { CopyButton } from "./copybutton";
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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 { GlobalModel } from "@/models";
|
||||
import { isBlank } from "@/util/util";
|
||||
|
||||
const VALID_CSS_VARIABLES = [
|
||||
"--term-black",
|
||||
"--term-red",
|
||||
"--term-green",
|
||||
"--term-yellow",
|
||||
"--term-blue",
|
||||
"--term-magenta",
|
||||
"--term-cyan",
|
||||
"--term-white",
|
||||
"--term-bright-black",
|
||||
"--term-bright-red",
|
||||
"--term-bright-green",
|
||||
"--term-bright-yellow",
|
||||
"--term-bright-blue",
|
||||
"--term-bright-magenta",
|
||||
"--term-bright-cyan",
|
||||
"--term-bright-white",
|
||||
"--term-gray",
|
||||
"--term-cmdtext",
|
||||
"--term-foreground",
|
||||
"--term-background",
|
||||
"--term-selection-background",
|
||||
"--term-cursor-accent",
|
||||
];
|
||||
|
||||
@mobxReact.observer
|
||||
class TermStyleBlock extends React.Component<
|
||||
{ scope: "main" | "session" | "screen"; themeSrcEl: HTMLElement; themeKey: string; termTheme: TermThemeType },
|
||||
{ styleRules: string }
|
||||
> {
|
||||
styleRules: OV<string> = mobx.observable.box("", { name: "StyleBlock-styleRules" });
|
||||
theme: string;
|
||||
|
||||
componentDidMount(): void {
|
||||
this.loadThemeStyles();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps): void {
|
||||
const { themeKey, termTheme } = this.props;
|
||||
const currTheme = termTheme[themeKey];
|
||||
if (themeKey !== prevProps.themeKey || currTheme !== this.theme) {
|
||||
this.loadThemeStyles();
|
||||
}
|
||||
}
|
||||
|
||||
isValidCSSColor(color) {
|
||||
const element = document.createElement("div");
|
||||
element.style.color = color;
|
||||
return element.style.color !== "";
|
||||
}
|
||||
|
||||
isValidTermCSSVariable(key, value) {
|
||||
const cssVarName = `--term-${key}`;
|
||||
console.log("cssVarName", cssVarName, "value", value);
|
||||
return VALID_CSS_VARIABLES.includes(cssVarName);
|
||||
}
|
||||
|
||||
camelCaseToKebabCase(str) {
|
||||
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
||||
}
|
||||
|
||||
loadThemeStyles() {
|
||||
const { themeKey, termTheme, scope } = this.props;
|
||||
const currTheme = termTheme[themeKey];
|
||||
|
||||
if (currTheme && currTheme !== this.theme && currTheme) {
|
||||
const rtn = GlobalModel.getTermThemeJson(currTheme);
|
||||
rtn.then((termThemeJson) => {
|
||||
if (termThemeJson && typeof termThemeJson === "object") {
|
||||
const styleProperties = Object.entries(termThemeJson)
|
||||
.filter(([key, value]) => {
|
||||
const cssVarName = `--term-${this.camelCaseToKebabCase(key)}`;
|
||||
return VALID_CSS_VARIABLES.includes(cssVarName) && this.isValidCSSColor(value);
|
||||
})
|
||||
.map(([key, value]) => `--term-${key}: ${value};`)
|
||||
.join(" ");
|
||||
|
||||
mobx.action(() => {
|
||||
this.styleRules.set(`:root { ${styleProperties} }`);
|
||||
GlobalModel.termThemeSrcEls.set(scope, this.props.themeSrcEl);
|
||||
})();
|
||||
GlobalModel.bumpTermRenderVersion();
|
||||
this.theme = currTheme;
|
||||
} else {
|
||||
console.error("termThemeJson is not an object:", termThemeJson);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error("error loading theme styles:", error);
|
||||
});
|
||||
} else {
|
||||
mobx.action(() => {
|
||||
this.styleRules.set("");
|
||||
GlobalModel.termThemeSrcEls.set(scope, null);
|
||||
})();
|
||||
this.theme = currTheme;
|
||||
GlobalModel.bumpTermRenderVersion();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (isBlank(this.styleRules.get())) {
|
||||
return null;
|
||||
}
|
||||
return <style>{this.styleRules.get()}</style>;
|
||||
}
|
||||
}
|
||||
|
||||
export { TermStyleBlock };
|
||||
@@ -649,11 +649,6 @@ class LineContainer extends React.Component<{ historyId: string; width: number }
|
||||
this.line = hvm.getLineById(this.historyItem.lineid);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
GlobalModel.bumpTermRenderVersion();
|
||||
GlobalModel.termThemeSrcEl.set(null);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleHeightChange(lineNum: number, newHeight: number, oldHeight: number): void {
|
||||
return;
|
||||
|
||||
@@ -5,12 +5,10 @@ import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { GlobalCommandRunner, GlobalModel, Screen } from "@/models";
|
||||
import { Button, TextField, Dropdown } from "@/elements";
|
||||
import { getRemoteStr, getRemoteStrWithAlias } from "@/common/prompt/prompt";
|
||||
import { TextField, Dropdown } from "@/elements";
|
||||
import { getRemoteStrWithAlias } from "@/common/prompt/prompt";
|
||||
import * as util from "@/util/util";
|
||||
import { TabIcon } from "@/elements/tabicon";
|
||||
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 * as appconst from "@/app/appconst";
|
||||
@@ -18,52 +16,6 @@ import * as appconst from "@/app/appconst";
|
||||
import "./screenview.less";
|
||||
import "./tabs.less";
|
||||
|
||||
@mobxReact.observer
|
||||
class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
errorMessage: OV<string | null> = mobx.observable.box(null, { name: "NewTabSettings-errorMessage" });
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickNewConnection(): void {
|
||||
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
|
||||
}
|
||||
|
||||
render() {
|
||||
let { screen } = this.props;
|
||||
let rptr = screen.curRemote.get();
|
||||
return (
|
||||
<div className="newtab-container">
|
||||
<div className="newtab-section name-section">
|
||||
<TabNameTextField screen={screen} errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
<div className="newtab-spacer" />
|
||||
<div className="newtab-section conn-section">
|
||||
<div className="unselectable">
|
||||
You're connected to <b>[{getRemoteStrWithAlias(rptr)}]</b>. Do you want to change it?
|
||||
</div>
|
||||
<div>
|
||||
<TabRemoteSelector screen={screen} errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
<div className="text-caption cr-help-text">
|
||||
To change connection from the command line use `/connect [alias|user@host]`
|
||||
</div>
|
||||
</div>
|
||||
<div className="newtab-spacer" />
|
||||
<div className="newtab-section">
|
||||
<TabIconSelector screen={screen} errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
<div className="newtab-spacer" />
|
||||
<div className="newtab-section">
|
||||
<TabColorSelector screen={screen} errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class TabNameTextField extends React.Component<{ screen: Screen; errorMessage?: OV<string> }, {}> {
|
||||
@boundMethod
|
||||
@@ -163,6 +115,10 @@ class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?:
|
||||
|
||||
@boundMethod
|
||||
selectRemote(cname: string): void {
|
||||
if (cname == null) {
|
||||
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteCN.set(cname);
|
||||
})();
|
||||
@@ -191,6 +147,14 @@ class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?:
|
||||
}
|
||||
return a.remoteidx - b.remoteidx;
|
||||
});
|
||||
|
||||
options.push({
|
||||
label: "New Connection",
|
||||
value: null,
|
||||
icon: <i className="fa-sharp fa-solid fa-plus"></i>,
|
||||
noop: true,
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -210,6 +174,7 @@ class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?:
|
||||
let curRemote = GlobalModel.getRemoteByName(selectedRemote);
|
||||
return (
|
||||
<Dropdown
|
||||
label="Connection"
|
||||
className="conn-dropdown"
|
||||
options={this.getOptions()}
|
||||
defaultValue={curRemote.remotecanonicalname}
|
||||
@@ -227,4 +192,4 @@ class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?:
|
||||
}
|
||||
}
|
||||
|
||||
export { NewTabSettings, TabColorSelector, TabIconSelector, TabNameTextField, TabRemoteSelector };
|
||||
export { TabColorSelector, TabIconSelector, TabNameTextField, TabRemoteSelector };
|
||||
|
||||
@@ -12,7 +12,7 @@ import { debounce } from "throttle-debounce";
|
||||
import dayjs from "dayjs";
|
||||
import { GlobalCommandRunner, ForwardLineContainer, GlobalModel, ScreenLines, Screen, Session } from "@/models";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { Button } from "@/elements";
|
||||
import { Button, TermStyleBlock } from "@/elements";
|
||||
import { Line } from "@/app/line/linecomps";
|
||||
import { LinesView } from "@/app/line/linesview";
|
||||
import * as util from "@/util/util";
|
||||
@@ -184,10 +184,19 @@ class ScreenView extends React.Component<{ session: Session; screen: Screen }, {
|
||||
winWidth = screenWidth - realWidth + "px";
|
||||
sidebarWidth = realWidth - MagicLayout.ScreenSidebarWidthPadding + "px";
|
||||
}
|
||||
const termTheme = GlobalModel.getTermTheme();
|
||||
const termRenderVersion = GlobalModel.termRenderVersion.get();
|
||||
|
||||
return (
|
||||
<div className="screen-view" data-screenid={screen.screenId} ref={this.screenViewRef}>
|
||||
<TermStyleBlock
|
||||
scope="screen"
|
||||
termTheme={termTheme}
|
||||
themeSrcEl={this.screenViewRef.current}
|
||||
themeKey={screen.screenId}
|
||||
/>
|
||||
<ScreenWindowView
|
||||
key={screen.screenId + ":" + fontSize + ":" + dprStr}
|
||||
key={screen.screenId + ":" + fontSize + ":" + dprStr + ":" + termRenderVersion}
|
||||
session={session}
|
||||
screen={screen}
|
||||
width={winWidth}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
.close-button {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
@@ -187,4 +187,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.terminal-theme-dropdown {
|
||||
width: 412px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,9 @@ import { ScreenTabs } from "./screen/tabs";
|
||||
import { ErrorBoundary } from "@/common/error/errorboundary";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import type { Screen } from "@/models";
|
||||
import { Button } from "@/elements";
|
||||
import { Button, Dropdown, TermStyleBlock } from "@/elements";
|
||||
import { commandRtnHandler } from "@/util/util";
|
||||
import { getTermThemes } from "@/util/themeutil";
|
||||
import { Dropdown } from "@/elements/dropdown";
|
||||
import { getRemoteStrWithAlias } from "@/common/prompt/prompt";
|
||||
import { TabColorSelector, TabIconSelector, TabNameTextField, TabRemoteSelector } from "./screen/newtabsettings";
|
||||
import * as util from "@/util/util";
|
||||
@@ -175,6 +174,7 @@ class TabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
<If condition={termThemes.length > 0}>
|
||||
<div className="newtab-section">
|
||||
<Dropdown
|
||||
label="Terminal Theme"
|
||||
className="terminal-theme-dropdown"
|
||||
options={termThemes}
|
||||
defaultValue={currTermTheme}
|
||||
@@ -208,59 +208,6 @@ class TabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
@mobxReact.observer
|
||||
class WorkspaceView extends React.Component<{}, {}> {
|
||||
sessionRef = React.createRef<HTMLDivElement>();
|
||||
theme: string;
|
||||
themeReactionDisposer: mobx.IReactionDisposer;
|
||||
|
||||
componentDidMount() {
|
||||
this.setupThemeReaction();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.setupThemeReaction();
|
||||
}
|
||||
|
||||
setupThemeReaction() {
|
||||
if (this.themeReactionDisposer) {
|
||||
this.themeReactionDisposer();
|
||||
}
|
||||
|
||||
// This handles session and screen-level terminal theming.
|
||||
// Ideally, screen-level theming should be handled in the inner-level component, but
|
||||
// the frequent mounting and unmounting of the screen view make it really difficult to work.
|
||||
this.themeReactionDisposer = mobx.reaction(
|
||||
() => {
|
||||
return {
|
||||
termTheme: GlobalModel.getTermTheme(),
|
||||
session: GlobalModel.getActiveSession(),
|
||||
screen: GlobalModel.getActiveScreen(),
|
||||
};
|
||||
},
|
||||
({ termTheme, session, screen }) => {
|
||||
let currTheme = termTheme[session.sessionId];
|
||||
if (termTheme[screen.screenId]) {
|
||||
currTheme = termTheme[screen.screenId];
|
||||
}
|
||||
if (session && currTheme !== this.theme && this.sessionRef.current) {
|
||||
const reset = currTheme == null;
|
||||
const theme = currTheme ?? this.theme;
|
||||
const themeSrcEl = reset ? null : this.sessionRef.current;
|
||||
const rtn = GlobalModel.updateTermTheme(this.sessionRef.current, theme, reset);
|
||||
rtn.then(() => {
|
||||
GlobalModel.termThemeSrcEl.set(themeSrcEl);
|
||||
}).then(() => {
|
||||
GlobalModel.bumpTermRenderVersion();
|
||||
});
|
||||
this.theme = currTheme;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.themeReactionDisposer) {
|
||||
this.themeReactionDisposer();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
toggleTabSettings() {
|
||||
@@ -277,11 +224,11 @@ class WorkspaceView extends React.Component<{}, {}> {
|
||||
sessionId = session.sessionId;
|
||||
activeScreen = session.getActiveScreen();
|
||||
}
|
||||
|
||||
const isHidden = GlobalModel.activeMainView.get() != "session";
|
||||
const mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||
const termRenderVersion = GlobalModel.termRenderVersion.get();
|
||||
const showTabSettings = GlobalModel.tabSettingsOpen.get();
|
||||
const termTheme = GlobalModel.getTermTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.sessionRef}
|
||||
@@ -291,15 +238,23 @@ class WorkspaceView extends React.Component<{}, {}> {
|
||||
width: `${window.innerWidth - mainSidebarModel.getWidth()}px`,
|
||||
}}
|
||||
>
|
||||
<If condition={session != null}>
|
||||
<TermStyleBlock
|
||||
scope="session"
|
||||
themeSrcEl={this.sessionRef.current}
|
||||
themeKey={session.sessionId}
|
||||
termTheme={termTheme}
|
||||
/>
|
||||
</If>
|
||||
<If condition={!isHidden}>
|
||||
<SessionKeybindings key="keybindings"></SessionKeybindings>
|
||||
</If>
|
||||
<ScreenTabs key={"tabs-" + sessionId} session={session} />
|
||||
<If condition={activeScreen != null}>
|
||||
<div key="pulldown" className={cn("tab-settings-pulldown", { closed: !showTabSettings })}>
|
||||
<button className="close-icon" onClick={this.toggleTabSettings}>
|
||||
<Button className="close-button secondary ghost" onClick={this.toggleTabSettings}>
|
||||
<i className="fa-solid fa-sharp fa-xmark-large" />
|
||||
</button>
|
||||
</Button>
|
||||
<TabSettings key={activeScreen.screenId} screen={activeScreen} />
|
||||
<If condition={showTabSettings && !isHidden}>
|
||||
<TabSettingsPulldownKeybindings />
|
||||
@@ -307,11 +262,7 @@ class WorkspaceView extends React.Component<{}, {}> {
|
||||
</div>
|
||||
</If>
|
||||
<ErrorBoundary key="eb">
|
||||
<ScreenView
|
||||
key={`screenview-${sessionId}-${termRenderVersion}`}
|
||||
session={session}
|
||||
screen={activeScreen}
|
||||
/>
|
||||
<ScreenView key={`screenview-${sessionId}`} session={session} screen={activeScreen} />
|
||||
<If condition={activeScreen != null}>
|
||||
<CmdInput key={"cmdinput-" + sessionId} />
|
||||
</If>
|
||||
|
||||
@@ -377,7 +377,7 @@ class CommandRunner {
|
||||
return GlobalModel.submitCommand("client", "set", null, kwargs, interactive);
|
||||
}
|
||||
|
||||
setGlobalTermTheme(theme: string, interactive: boolean): Promise<CommandRtnType> {
|
||||
setMainTermTheme(theme: string, interactive: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
termtheme: theme,
|
||||
@@ -386,7 +386,6 @@ class CommandRunner {
|
||||
}
|
||||
|
||||
setSessionTermTheme(sessionId: string, name: string, interactive: boolean): Promise<CommandRtnType> {
|
||||
console.log("setSessionTermTheme-------");
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
id: sessionId,
|
||||
|
||||
+32
-26
@@ -144,13 +144,15 @@ class Model {
|
||||
name: "terminalThemes",
|
||||
deep: false,
|
||||
});
|
||||
termThemeSrcEl: OV<HTMLElement> = mobx.observable.box(null, {
|
||||
name: "termThemeSrcEl",
|
||||
});
|
||||
termThemeSrcEls: OMap<string, HTMLElement> = mobx.observable.map(
|
||||
{},
|
||||
{
|
||||
name: "termThemeSrcEls",
|
||||
}
|
||||
);
|
||||
termRenderVersion: OV<number> = mobx.observable.box(0, {
|
||||
name: "termRenderVersion",
|
||||
});
|
||||
currGlobalTermTheme: string;
|
||||
|
||||
private constructor() {
|
||||
this.clientId = getApi().getId();
|
||||
@@ -215,6 +217,16 @@ class Model {
|
||||
};
|
||||
}
|
||||
|
||||
getThemeSrcElForScope() {
|
||||
const scopes = ["screen", "session", "main"];
|
||||
for (let scope of scopes) {
|
||||
if (this.termThemeSrcEls.get(scope)) {
|
||||
return this.termThemeSrcEls.get(scope);
|
||||
}
|
||||
}
|
||||
return document.documentElement;
|
||||
}
|
||||
|
||||
readConfigKeybindings() {
|
||||
const url = new URL(this.getBaseHostPort() + "/config/keybindings.json");
|
||||
let prtn = fetch(url, { method: "get", body: null, headers: this.getFetchHeaders() });
|
||||
@@ -253,19 +265,12 @@ class Model {
|
||||
});
|
||||
}
|
||||
|
||||
updateTermTheme(element: HTMLElement, themeFileName: string, reset: boolean) {
|
||||
getTermThemeJson(themeFileName: string) {
|
||||
const url = new URL(this.getBaseHostPort() + `/config/terminal-themes/${themeFileName}.json`);
|
||||
return fetch(url, { method: "get", body: null, headers: this.getFetchHeaders() })
|
||||
.then((resp) => resp.json())
|
||||
.then((themeVars: TermThemeType) => {
|
||||
Object.keys(themeVars).forEach((key) => {
|
||||
if (reset) {
|
||||
this.resetStyleVar(element, `--term-${key}`);
|
||||
} else {
|
||||
this.resetStyleVar(element, `--term-${key}`);
|
||||
this.setStyleVar(element, `--term-${key}`, themeVars[key]);
|
||||
}
|
||||
});
|
||||
return themeVars;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`error applying theme: ${themeFileName}`, error);
|
||||
@@ -1323,19 +1328,20 @@ class Model {
|
||||
}
|
||||
// Only for global terminal theme. For session and screen terminal theme,
|
||||
// they are handled in workspace view.
|
||||
if (newTermTheme) {
|
||||
const el = document.documentElement;
|
||||
const globaltt = newTermTheme["global"] ?? this.currGlobalTermTheme;
|
||||
const reset = newTermTheme["global"] == null;
|
||||
if (globaltt) {
|
||||
const rtn = this.updateTermTheme(el, globaltt, reset);
|
||||
rtn.then(() => {
|
||||
if (ttUpdated) {
|
||||
this.bumpTermRenderVersion();
|
||||
}
|
||||
});
|
||||
this.currGlobalTermTheme = globaltt;
|
||||
}
|
||||
if (ttUpdated) {
|
||||
// this.bumpTermRenderVersion();
|
||||
// const el = document.documentElement;
|
||||
// const globaltt = newTermTheme["global"] ?? this.currGlobalTermTheme;
|
||||
// const reset = newTermTheme["global"] == null;
|
||||
// if (globaltt) {
|
||||
// const rtn = this.updateTermTheme(el, globaltt, reset);
|
||||
// rtn.then(() => {
|
||||
// if (ttUpdated) {
|
||||
// this.bumpTermRenderVersion();
|
||||
// }
|
||||
// });
|
||||
// this.currGlobalTermTheme = globaltt;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ class TermWrap {
|
||||
let cols = windowWidthToCols(opts.winSize.width, opts.fontSize);
|
||||
this.termSize = { rows: opts.termOpts.rows, cols: cols };
|
||||
}
|
||||
const themeSrcEl = GlobalModel.termThemeSrcEl.get();
|
||||
const themeSrcEl = GlobalModel.getThemeSrcElForScope();
|
||||
let theme = getThemeFromCSSVars(themeSrcEl);
|
||||
this.terminal = new Terminal({
|
||||
rows: this.termSize.rows,
|
||||
|
||||
Vendored
+3
-1
@@ -308,7 +308,9 @@ declare global {
|
||||
|
||||
type DropdownItem = {
|
||||
label: string;
|
||||
value: string;
|
||||
value?: string;
|
||||
icon?: React.ReactNode;
|
||||
noop?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import * as electron from "electron";
|
||||
import { parse } from "node:path";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Model } from "@/models";
|
||||
import defaultKeybindingsFile from "../../assets/default-keybindings.json";
|
||||
const defaultKeybindings: KeybindConfigArray = defaultKeybindingsFile;
|
||||
|
||||
@@ -52,7 +51,7 @@ class KeybindManager {
|
||||
activeKeybindsVersion: OV<number>;
|
||||
lastKeyData: { domain: string; keyPress: string };
|
||||
|
||||
constructor(GlobalModel: any) {
|
||||
constructor(GlobalModel: Model) {
|
||||
this.levelMap = new Map();
|
||||
this.domainCallbacks = new Map();
|
||||
this.levelArray = KeybindLevels;
|
||||
|
||||
@@ -5814,9 +5814,9 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
|
||||
feOpts.TermTheme = make(map[string]string)
|
||||
}
|
||||
if termthemeStr == "" {
|
||||
delete(feOpts.TermTheme, "global")
|
||||
delete(feOpts.TermTheme, "main")
|
||||
} else {
|
||||
feOpts.TermTheme["global"] = termthemeStr
|
||||
feOpts.TermTheme["main"] = termthemeStr
|
||||
}
|
||||
err = sstore.UpdateClientFeOpts(ctx, feOpts)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user