mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-31 23:11:28 +01:00
Support os-native theming (#495)
* save work * Add native theme support * update index * update var name * remove comment * fix code setting * bump render version on change * remove themeutil
This commit is contained in:
parent
69b9ce33b3
commit
6065ee931f
@ -5,14 +5,14 @@
|
||||
<base href="../" />
|
||||
<script charset="UTF-8" src="dist-dev/waveterm.js"></script>
|
||||
<link rel="stylesheet" href="public/bulma-0.9.4.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/fontawesome.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/brands.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/solid.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-solid.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-regular.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/fontawesome.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/brands.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/solid.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-solid.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-regular.min.css" />
|
||||
<link rel="stylesheet" href="dist-dev/waveterm.css" />
|
||||
|
||||
<link rel="stylesheet" id="theme-stylesheet" href="public/themes/default.css">
|
||||
<link rel="stylesheet" id="theme-stylesheet" href="public/themes/themequery.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="measure"></div>
|
||||
|
@ -5,14 +5,14 @@
|
||||
<base href="../" />
|
||||
<script charset="UTF-8" src="dist/waveterm.js"></script>
|
||||
<link rel="stylesheet" href="public/bulma-0.9.4.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/fontawesome.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/brands.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/solid.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-solid.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-regular.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/fontawesome.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/brands.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/solid.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-solid.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-regular.min.css" />
|
||||
<link rel="stylesheet" href="dist/waveterm.css" />
|
||||
|
||||
<link rel="stylesheet" id="theme-stylesheet" href="public/themes/default.css" />
|
||||
<link rel="stylesheet" id="theme-stylesheet" href="public/themes/themequery.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="measure"></div>
|
||||
|
3
public/themes/themequery.css
Normal file
3
public/themes/themequery.css
Normal file
@ -0,0 +1,3 @@
|
||||
@import url("./default.css") screen;
|
||||
|
||||
@import url("./light.css") screen and (prefers-color-scheme: light);
|
@ -8,7 +8,7 @@ import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalCommandRunner, GlobalModel } from "@/models";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { isBlank } from "@/util/util";
|
||||
import { WorkspaceView } from "./workspace/workspaceview";
|
||||
import { PluginsView } from "./pluginsview/pluginsview";
|
||||
@ -123,7 +123,7 @@ class App extends React.Component<{}, {}> {
|
||||
const mainSidebarCollapsed = GlobalModel.mainSidebarModel.getCollapsed();
|
||||
const rightSidebarCollapsed = GlobalModel.rightSidebarModel.getCollapsed();
|
||||
const activeMainView = GlobalModel.activeMainView.get();
|
||||
const lightDarkClass = GlobalModel.isThemeDark() ? "is-dark" : "is-light";
|
||||
const lightDarkClass = GlobalModel.isDarkTheme.get() ? "is-dark" : "is-light";
|
||||
return (
|
||||
<div
|
||||
key={"version-" + renderVersion}
|
||||
|
@ -64,11 +64,12 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeTheme(theme: string): void {
|
||||
if (GlobalModel.getTheme() == theme) {
|
||||
handleChangeThemeSource(themeSource: NativeThemeSource): void {
|
||||
if (GlobalModel.getThemeSource() == themeSource) {
|
||||
return;
|
||||
}
|
||||
const prtn = GlobalCommandRunner.setTheme(theme, false);
|
||||
const prtn = GlobalCommandRunner.setTheme(themeSource, false);
|
||||
getApi().setNativeThemeSource(themeSource);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@ -111,11 +112,12 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
return availableFontFamilies;
|
||||
}
|
||||
|
||||
getThemes(): DropdownItem[] {
|
||||
const themes: DropdownItem[] = [];
|
||||
themes.push({ label: "Dark", value: "dark" });
|
||||
themes.push({ label: "Light", value: "light" });
|
||||
return themes;
|
||||
getThemeSources(): DropdownItem[] {
|
||||
const themeSources: DropdownItem[] = [];
|
||||
themeSources.push({ label: "Dark", value: "dark" });
|
||||
themeSources.push({ label: "Light", value: "light" });
|
||||
themeSources.push({ label: "System", value: "system" });
|
||||
return themeSources;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@ -190,7 +192,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
);
|
||||
const curFontSize = GlobalModel.getTermFontSize();
|
||||
const curFontFamily = GlobalModel.getTermFontFamily();
|
||||
const curTheme = GlobalModel.getTheme();
|
||||
const curTheme = GlobalModel.getThemeSource();
|
||||
|
||||
return (
|
||||
<MainView className="clientsettings-view" title="Client Settings" onClose={this.handleClose}>
|
||||
@ -225,9 +227,9 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
<div className="settings-input">
|
||||
<Dropdown
|
||||
className="theme-dropdown"
|
||||
options={this.getThemes()}
|
||||
options={this.getThemeSources()}
|
||||
defaultValue={curTheme}
|
||||
onChange={this.handleChangeTheme}
|
||||
onChange={this.handleChangeThemeSource}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -539,6 +539,25 @@ electron.ipcMain.on("get-last-logs", (event, numberOfLines) => {
|
||||
});
|
||||
});
|
||||
|
||||
electron.ipcMain.on("get-shouldusedarkcolors", (event) => {
|
||||
event.returnValue = electron.nativeTheme.shouldUseDarkColors;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("get-nativethemesource", (event) => {
|
||||
event.returnValue = electron.nativeTheme.themeSource;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("set-nativethemesource", (event, themeSource: "system" | "light" | "dark") => {
|
||||
electron.nativeTheme.themeSource = themeSource;
|
||||
event.returnValue = true;
|
||||
});
|
||||
|
||||
electron.nativeTheme.on("updated", () => {
|
||||
if (MainWindow != null) {
|
||||
MainWindow.webContents.send("nativetheme-updated");
|
||||
}
|
||||
});
|
||||
|
||||
function readLastLinesOfFile(filePath: string, lineCount: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
child_process.exec(`tail -n ${lineCount} "${filePath}"`, (err, stdout, stderr) => {
|
||||
|
@ -13,6 +13,10 @@ contextBridge.exposeInMainWorld("api", {
|
||||
ipcRenderer.once("last-logs", (event, data) => callback(data));
|
||||
},
|
||||
getInitialTermFontFamily: () => ipcRenderer.sendSync("get-initial-termfontfamily"),
|
||||
getShouldUseDarkColors: () => ipcRenderer.sendSync("get-shouldusedarkcolors"),
|
||||
getNativeThemeSource: () => ipcRenderer.sendSync("get-nativethemesource"),
|
||||
setNativeThemeSource: (source) => ipcRenderer.send("set-nativethemesource", source),
|
||||
onNativeThemeUpdated: (callback) => ipcRenderer.on("nativetheme-updated", callback),
|
||||
restartWaveSrv: () => ipcRenderer.sendSync("restart-server"),
|
||||
reloadWindow: () => ipcRenderer.sendSync("reload-window"),
|
||||
reregisterGlobalShortcut: (shortcut) => ipcRenderer.sendSync("reregister-global-shortcut", shortcut),
|
||||
|
@ -365,7 +365,7 @@ class CommandRunner {
|
||||
return GlobalModel.submitCommand("client", "set", null, kwargs, interactive);
|
||||
}
|
||||
|
||||
setTheme(theme: string, interactive: boolean): Promise<CommandRtnType> {
|
||||
setTheme(theme: NativeThemeSource, interactive: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
theme: theme,
|
||||
|
@ -13,12 +13,11 @@ import {
|
||||
isModKeyPress,
|
||||
isBlank,
|
||||
} from "@/util/util";
|
||||
import { loadTheme } from "@/util/themeutil";
|
||||
import { WSControl } from "./ws";
|
||||
import { cmdStatusIsRunning } from "@/app/line/lineutil";
|
||||
import * as appconst from "@/app/appconst";
|
||||
import { remotePtrToString, cmdPacketString } from "@/util/modelutil";
|
||||
import { KeybindManager, checkKeyPressed, adaptFromReactOrNativeKeyEvent, setKeyUtilPlatform } from "@/util/keyutil";
|
||||
import { KeybindManager, adaptFromReactOrNativeKeyEvent, setKeyUtilPlatform } from "@/util/keyutil";
|
||||
import { Session } from "./session";
|
||||
import { ScreenLines } from "./screenlines";
|
||||
import { InputModel } from "./input";
|
||||
@ -118,6 +117,9 @@ class Model {
|
||||
modalsModel: ModalsModel;
|
||||
mainSidebarModel: MainSidebarModel;
|
||||
rightSidebarModel: RightSidebarModel;
|
||||
isDarkTheme: OV<boolean> = mobx.observable.box(getApi().getShouldUseDarkColors(), {
|
||||
name: "isDarkTheme",
|
||||
});
|
||||
clientData: OV<ClientDataType> = mobx.observable.box(null, {
|
||||
name: "clientData",
|
||||
});
|
||||
@ -181,6 +183,7 @@ class Model {
|
||||
getApi().onMenuItemAbout(this.onMenuItemAbout.bind(this));
|
||||
getApi().onWaveSrvStatusChange(this.onWaveSrvStatusChange.bind(this));
|
||||
getApi().onAppUpdateStatus(this.onAppUpdateStatus.bind(this));
|
||||
getApi().onNativeThemeUpdated(this.onNativeThemeUpdated.bind(this));
|
||||
document.addEventListener("keydown", this.docKeyDownHandler.bind(this));
|
||||
document.addEventListener("selectionchange", this.docSelectionChangeHandler.bind(this));
|
||||
setTimeout(() => this.getClientDataLoop(1), 10);
|
||||
@ -389,18 +392,19 @@ class Model {
|
||||
return ff;
|
||||
}
|
||||
|
||||
getTheme(): string {
|
||||
let cdata = this.clientData.get();
|
||||
let theme = cdata?.feopts?.theme;
|
||||
if (theme == null) {
|
||||
theme = appconst.DefaultTheme;
|
||||
}
|
||||
return theme;
|
||||
getThemeSource(): NativeThemeSource {
|
||||
return getApi().getNativeThemeSource();
|
||||
}
|
||||
|
||||
isThemeDark(): boolean {
|
||||
let cdata = this.clientData.get();
|
||||
return cdata?.feopts?.theme != "light";
|
||||
onNativeThemeUpdated(): void {
|
||||
console.log("native theme updated");
|
||||
const isDark = getApi().getShouldUseDarkColors();
|
||||
if (isDark != this.isDarkTheme.get()) {
|
||||
mobx.action(() => {
|
||||
this.isDarkTheme.set(isDark);
|
||||
this.bumpRenderVersion();
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
getTermFontSize(): number {
|
||||
@ -1197,7 +1201,7 @@ class Model {
|
||||
if (newTheme == null) {
|
||||
newTheme = appconst.DefaultTheme;
|
||||
}
|
||||
const themeUpdated = newTheme != this.getTheme();
|
||||
const themeUpdated = newTheme != this.getThemeSource();
|
||||
mobx.action(() => {
|
||||
this.clientData.set(clientData);
|
||||
})();
|
||||
@ -1215,7 +1219,7 @@ class Model {
|
||||
this.updateTermFontSizeVars();
|
||||
}
|
||||
if (themeUpdated) {
|
||||
loadTheme(newTheme);
|
||||
getApi().setNativeThemeSource(newTheme);
|
||||
this.bumpRenderVersion();
|
||||
}
|
||||
}
|
||||
|
@ -7,17 +7,11 @@ import Editor, { Monaco } from "@monaco-editor/react";
|
||||
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import cn from "classnames";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { Markdown } from "@/elements";
|
||||
import { Markdown, Button } from "@/elements";
|
||||
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
||||
import Split from "react-split-it";
|
||||
import loader from "@monaco-editor/loader";
|
||||
import {
|
||||
checkKeyPressed,
|
||||
adaptFromReactOrNativeKeyEvent,
|
||||
KeybindManager,
|
||||
adaptFromElectronKeyEvent,
|
||||
} from "@/util/keyutil";
|
||||
import { Button, Dropdown } from "@/elements";
|
||||
import { adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||
|
||||
import "./code.less";
|
||||
|
||||
@ -99,7 +93,7 @@ class SourceCodeRenderer extends React.Component<
|
||||
* codeCache is a Hashmap with key=screenId:lineId:filepath and value=code
|
||||
* Editor should never read the code directly from the filesystem. it should read from the cache.
|
||||
*/
|
||||
static codeCache = new Map();
|
||||
static readonly codeCache = new Map();
|
||||
|
||||
// which languages have preview options
|
||||
languagesWithPreviewer: string[] = ["markdown", "mdx"];
|
||||
@ -109,7 +103,6 @@ class SourceCodeRenderer extends React.Component<
|
||||
monacoEditor: MonacoTypes.editor.IStandaloneCodeEditor; // reference to mounted monaco editor. TODO need the correct type
|
||||
markdownRef: React.RefObject<HTMLDivElement>;
|
||||
syncing: boolean;
|
||||
monacoOptions: MonacoTypes.editor.IEditorOptions & MonacoTypes.editor.IGlobalEditorOptions;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -117,7 +110,7 @@ class SourceCodeRenderer extends React.Component<
|
||||
const editorHeight = Math.max(props.savedHeight - this.getEditorHeightBuffer(), 0); // must subtract the padding/margin to get the real editorHeight
|
||||
this.markdownRef = React.createRef();
|
||||
this.syncing = false;
|
||||
let isClosed = props.lineState["prompt:closed"];
|
||||
const isClosed = props.lineState["prompt:closed"];
|
||||
this.state = {
|
||||
code: null,
|
||||
languages: [],
|
||||
@ -161,12 +154,12 @@ class SourceCodeRenderer extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
saveLineState = (kvp) => {
|
||||
saveLineState(kvp) {
|
||||
const { screenId, lineId } = this.props.context;
|
||||
GlobalCommandRunner.setLineState(screenId, lineId, { ...this.props.lineState, ...kvp }, false);
|
||||
};
|
||||
}
|
||||
|
||||
setInitialLanguage = (editor) => {
|
||||
setInitialLanguage(editor) {
|
||||
// set all languages
|
||||
const languages = monaco.languages.getLanguages().map((lang) => lang.id);
|
||||
this.setState({ languages });
|
||||
@ -175,7 +168,7 @@ class SourceCodeRenderer extends React.Component<
|
||||
// if not found, we try to grab the filename from with filePath (coming from lineState["prompt:file"]) or cmdstr
|
||||
if (!detectedLanguage) {
|
||||
const strForFilePath = this.filePath || this.props.cmdstr;
|
||||
const extension = strForFilePath.match(/(?:[^\\\/:*?"<>|\r\n]+\.)([a-zA-Z0-9]+)\b/)?.[1] || "";
|
||||
const extension = RegExp(/(?:[^\\/:*?"<>|\r\n]+\.)([a-zA-Z0-9]+)\b/).exec(strForFilePath)?.[1] || "";
|
||||
const detectedLanguageObj = monaco.languages
|
||||
.getLanguages()
|
||||
.find((lang) => lang.extensions?.includes("." + extension));
|
||||
@ -194,12 +187,12 @@ class SourceCodeRenderer extends React.Component<
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
registerKeybindings() {
|
||||
const { lineId } = this.props.context;
|
||||
let domain = "code-" + lineId;
|
||||
let keybindManager = GlobalModel.keybindManager;
|
||||
const domain = "code-" + lineId;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
keybindManager.registerKeybinding("plugin", domain, "codeedit:save", (waveEvent) => {
|
||||
this.doSave();
|
||||
return true;
|
||||
@ -216,20 +209,20 @@ class SourceCodeRenderer extends React.Component<
|
||||
|
||||
unregisterKeybindings() {
|
||||
const { lineId } = this.props.context;
|
||||
let domain = "code-" + lineId;
|
||||
const domain = "code-" + lineId;
|
||||
GlobalModel.keybindManager.unregisterDomain(domain);
|
||||
}
|
||||
|
||||
handleEditorDidMount = (editor: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => {
|
||||
handleEditorDidMount(editor: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) {
|
||||
this.monacoEditor = editor;
|
||||
this.setInitialLanguage(editor);
|
||||
this.setEditorHeight();
|
||||
setTimeout(() => {
|
||||
let opts = this.getEditorOptions();
|
||||
const opts = this.getEditorOptions();
|
||||
editor.updateOptions(opts);
|
||||
}, 2000);
|
||||
editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent);
|
||||
const waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent);
|
||||
console.log("keydown?", waveEvent);
|
||||
if (
|
||||
GlobalModel.keybindManager.checkKeysPressed(waveEvent, [
|
||||
@ -261,7 +254,7 @@ class SourceCodeRenderer extends React.Component<
|
||||
});
|
||||
}
|
||||
if (!this.getAllowEditing()) this.setState({ showReadonly: true });
|
||||
};
|
||||
}
|
||||
|
||||
handleEditorScrollChange(e) {
|
||||
if (!this.state.showPreview) return;
|
||||
@ -291,7 +284,7 @@ class SourceCodeRenderer extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
handleLanguageChange = (e: any) => {
|
||||
handleLanguageChange(e: any) {
|
||||
const selectedLanguage = e.target.value;
|
||||
this.setState({
|
||||
selectedLanguage,
|
||||
@ -304,9 +297,9 @@ class SourceCodeRenderer extends React.Component<
|
||||
this.saveLineState({ lang: selectedLanguage });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
doSave = (onSave = () => {}) => {
|
||||
doSave(onSave = () => {}) {
|
||||
if (!this.state.isSave) return;
|
||||
const { screenId, lineId } = this.props.context;
|
||||
const encodedCode = new TextEncoder().encode(this.state.code);
|
||||
@ -326,9 +319,9 @@ class SourceCodeRenderer extends React.Component<
|
||||
this.setState({ message: { status: "error", text: e.message } });
|
||||
setTimeout(() => this.setState({ message: null }), 3000);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
doClose = () => {
|
||||
doClose() {
|
||||
// if there is unsaved data
|
||||
if (this.state.isSave)
|
||||
return GlobalModel.showAlert({
|
||||
@ -363,15 +356,15 @@ class SourceCodeRenderer extends React.Component<
|
||||
if (this.props.shouldFocus) {
|
||||
GlobalCommandRunner.screenSetFocus("input");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleEditorChange = (code) => {
|
||||
handleEditorChange(code) {
|
||||
SourceCodeRenderer.codeCache.set(this.cacheKey, code);
|
||||
this.setState({ code }, () => {
|
||||
this.setEditorHeight();
|
||||
this.setState({ isSave: code !== this.originalCode });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
getEditorHeightBuffer(): number {
|
||||
const heightBuffer = GlobalModel.lineHeightEnv.lineHeight + 11;
|
||||
@ -381,7 +374,7 @@ class SourceCodeRenderer extends React.Component<
|
||||
setEditorHeight = () => {
|
||||
const maxEditorHeight = this.props.opts.maxSize.height - this.getEditorHeightBuffer();
|
||||
let _editorHeight = maxEditorHeight;
|
||||
let allowEditing = this.getAllowEditing();
|
||||
const allowEditing = this.getAllowEditing();
|
||||
if (!allowEditing) {
|
||||
const noOfLines = Math.max(this.state.code.split("\n").length, 5);
|
||||
const lineHeight = Math.ceil(GlobalModel.lineHeightEnv.lineHeight);
|
||||
@ -395,8 +388,8 @@ class SourceCodeRenderer extends React.Component<
|
||||
};
|
||||
|
||||
getAllowEditing(): boolean {
|
||||
let lineState = this.props.lineState;
|
||||
let mode = lineState["mode"] || "view";
|
||||
const lineState = this.props.lineState;
|
||||
const mode = lineState["mode"] || "view";
|
||||
if (mode == "view") {
|
||||
return false;
|
||||
}
|
||||
@ -407,32 +400,25 @@ class SourceCodeRenderer extends React.Component<
|
||||
if (!this.monacoEditor) {
|
||||
return;
|
||||
}
|
||||
let opts = this.getEditorOptions();
|
||||
const opts = this.getEditorOptions();
|
||||
this.monacoEditor.updateOptions(opts);
|
||||
}
|
||||
|
||||
getEditorOptions(): MonacoTypes.editor.IEditorOptions {
|
||||
let opts: MonacoTypes.editor.IEditorOptions = {
|
||||
const opts: MonacoTypes.editor.IEditorOptions = {
|
||||
scrollBeyondLastLine: false,
|
||||
fontSize: GlobalModel.getTermFontSize(),
|
||||
fontFamily: GlobalModel.getTermFontFamily(),
|
||||
readOnly: !this.getAllowEditing(),
|
||||
};
|
||||
let lineState = this.props.lineState;
|
||||
let minimap = true;
|
||||
if (this.state.showPreview) {
|
||||
minimap = false;
|
||||
} else if ("minimap" in lineState && !lineState["minimap"]) {
|
||||
minimap = false;
|
||||
}
|
||||
if (!minimap) {
|
||||
const lineState = this.props.lineState;
|
||||
if (this.state.showPreview || ("minimap" in lineState && !lineState["minimap"])) {
|
||||
opts.minimap = { enabled: false };
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
getCodeEditor = () => {
|
||||
let theme = GlobalModel.isThemeDark() ? "wave-theme-dark" : "wave-theme-light";
|
||||
getCodeEditor(theme: string) {
|
||||
return (
|
||||
<div className="editor-wrap" style={{ maxHeight: this.state.editorHeight }}>
|
||||
{this.state.showReadonly && <div className="readonly">{"read-only"}</div>}
|
||||
@ -447,9 +433,9 @@ class SourceCodeRenderer extends React.Component<
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
getPreviewer = () => {
|
||||
getPreviewer() {
|
||||
return (
|
||||
<div
|
||||
className="scroller"
|
||||
@ -460,17 +446,20 @@ class SourceCodeRenderer extends React.Component<
|
||||
<Markdown text={this.state.code} style={{ width: "100%", padding: "1rem" }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
togglePreview = () => {
|
||||
this.saveLineState({ showPreview: !this.state.showPreview });
|
||||
this.setState({ showPreview: !this.state.showPreview });
|
||||
togglePreview() {
|
||||
this.setState((prevState) => {
|
||||
const newPreviewState = { showPreview: !prevState.showPreview };
|
||||
this.saveLineState(newPreviewState);
|
||||
return newPreviewState;
|
||||
});
|
||||
setTimeout(() => this.updateEditorOpts(), 0);
|
||||
};
|
||||
}
|
||||
|
||||
getEditorControls = () => {
|
||||
const { selectedLanguage, isSave, languages, isPreviewerAvailable, showPreview } = this.state;
|
||||
let allowEditing = this.getAllowEditing();
|
||||
getEditorControls() {
|
||||
const { selectedLanguage, languages, isPreviewerAvailable, showPreview } = this.state;
|
||||
const allowEditing = this.getAllowEditing();
|
||||
return (
|
||||
<>
|
||||
<If condition={isPreviewerAvailable}>
|
||||
@ -507,20 +496,22 @@ class SourceCodeRenderer extends React.Component<
|
||||
</If>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
getMessage = () => (
|
||||
<div className="messageContainer">
|
||||
<div className={`message ${this.state.message.status === "error" ? "error" : ""}`}>
|
||||
{this.state.message.text}
|
||||
getMessage() {
|
||||
return (
|
||||
<div className="messageContainer">
|
||||
<div className={`message ${this.state.message.status === "error" ? "error" : ""}`}>
|
||||
{this.state.message.text}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
setSizes = (sizes) => {
|
||||
setSizes(sizes: number[]) {
|
||||
this.setState({ editorFraction: sizes[0] });
|
||||
this.saveLineState({ editorFraction: sizes[0] });
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { exitcode } = this.props;
|
||||
@ -544,14 +535,16 @@ class SourceCodeRenderer extends React.Component<
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let { lineNum } = this.props.context;
|
||||
let screen = GlobalModel.getActiveScreen();
|
||||
let lineIsSelected = mobx.computed(
|
||||
const { lineNum } = this.props.context;
|
||||
const screen = GlobalModel.getActiveScreen();
|
||||
const lineIsSelected = mobx.computed(
|
||||
() => screen.getSelectedLine() == lineNum && screen.getFocusType() == "cmd",
|
||||
{
|
||||
name: "code-lineisselected",
|
||||
}
|
||||
);
|
||||
|
||||
const theme = `wave-theme-${GlobalModel.isDarkTheme.get() ? "dark" : "light"}`;
|
||||
console.log("lineis selected:", lineIsSelected.get());
|
||||
return (
|
||||
<div className="code-renderer">
|
||||
@ -559,7 +552,7 @@ class SourceCodeRenderer extends React.Component<
|
||||
<CodeKeybindings codeObject={this}></CodeKeybindings>
|
||||
</If>
|
||||
<Split sizes={[editorFraction, 1 - editorFraction]} onSetSizes={this.setSizes}>
|
||||
{this.getCodeEditor()}
|
||||
{this.getCodeEditor(theme)}
|
||||
{isPreviewerAvailable && showPreview && this.getPreviewer()}
|
||||
</Split>
|
||||
<div className="flex-spacer" />
|
||||
|
@ -23,17 +23,17 @@ class TerminalKeybindings extends React.Component<{ termWrap: any; lineid: strin
|
||||
}
|
||||
|
||||
registerKeybindings() {
|
||||
let keybindManager = GlobalModel.keybindManager;
|
||||
let domain = "line-" + this.props.lineid;
|
||||
let termWrap = this.props.termWrap;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
const domain = "line-" + this.props.lineid;
|
||||
const termWrap = this.props.termWrap;
|
||||
keybindManager.registerKeybinding("plugin", domain, "terminal:copy", (waveEvent) => {
|
||||
let termWrap = this.props.termWrap;
|
||||
let sel = termWrap.terminal.getSelection();
|
||||
const termWrap = this.props.termWrap;
|
||||
const sel = termWrap.terminal.getSelection();
|
||||
navigator.clipboard.writeText(sel);
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("plugin", domain, "terminal:paste", (waveEvent) => {
|
||||
let p = navigator.clipboard.readText();
|
||||
const p = navigator.clipboard.readText();
|
||||
p.then((text) => {
|
||||
termWrap.dataHandler?.(text, termWrap);
|
||||
});
|
||||
@ -105,7 +105,7 @@ class TerminalRenderer extends React.Component<
|
||||
}
|
||||
|
||||
getSnapshotBeforeUpdate(prevProps, prevState): { height: number } {
|
||||
let elem = this.elemRef.current;
|
||||
const elem = this.elemRef.current;
|
||||
if (elem == null) {
|
||||
return { height: 0 };
|
||||
}
|
||||
@ -116,9 +116,8 @@ class TerminalRenderer extends React.Component<
|
||||
if (this.props.onHeightChange == null) {
|
||||
return;
|
||||
}
|
||||
let { line } = this.props;
|
||||
let curHeight = 0;
|
||||
let elem = this.elemRef.current;
|
||||
const elem = this.elemRef.current;
|
||||
if (elem != null) {
|
||||
curHeight = elem.offsetHeight;
|
||||
}
|
||||
@ -133,12 +132,12 @@ class TerminalRenderer extends React.Component<
|
||||
}
|
||||
|
||||
checkLoad(): void {
|
||||
let { line, staticRender, visible, collapsed } = this.props;
|
||||
let { staticRender, visible, collapsed } = this.props;
|
||||
if (staticRender) {
|
||||
return;
|
||||
}
|
||||
let vis = visible && visible.get() && !collapsed;
|
||||
let curVis = this.termLoaded.get();
|
||||
const vis = visible?.get() && !collapsed;
|
||||
const curVis = this.termLoaded.get();
|
||||
if (vis && !curVis) {
|
||||
this.loadTerminal();
|
||||
} else if (!vis && curVis) {
|
||||
@ -147,13 +146,12 @@ class TerminalRenderer extends React.Component<
|
||||
}
|
||||
|
||||
loadTerminal(): void {
|
||||
let { screen, line } = this.props;
|
||||
let model = GlobalModel;
|
||||
let cmd = screen.getCmd(line);
|
||||
const { screen, line } = this.props;
|
||||
const cmd = screen.getCmd(line);
|
||||
if (cmd == null) {
|
||||
return;
|
||||
}
|
||||
let termElem = this.termRef.current;
|
||||
const termElem = this.termRef.current;
|
||||
if (termElem == null) {
|
||||
console.log("cannot load terminal, no term elem found", line);
|
||||
return;
|
||||
@ -163,11 +161,11 @@ class TerminalRenderer extends React.Component<
|
||||
}
|
||||
|
||||
unloadTerminal(unmount: boolean): void {
|
||||
let { screen, line } = this.props;
|
||||
const { screen, line } = this.props;
|
||||
screen.unloadRenderer(line.lineid);
|
||||
if (!unmount) {
|
||||
mobx.action(() => this.termLoaded.set(false))();
|
||||
let termElem = this.termRef.current;
|
||||
const termElem = this.termRef.current;
|
||||
if (termElem != null) {
|
||||
termElem.replaceChildren();
|
||||
}
|
||||
@ -176,22 +174,21 @@ class TerminalRenderer extends React.Component<
|
||||
|
||||
@boundMethod
|
||||
clickTermBlock(e: any) {
|
||||
let { screen, line } = this.props;
|
||||
let model = GlobalModel;
|
||||
let termWrap = screen.getTermWrap(line.lineid);
|
||||
const { screen, line } = this.props;
|
||||
const termWrap = screen.getTermWrap(line.lineid);
|
||||
if (termWrap != null) {
|
||||
termWrap.giveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { screen, line, width, staticRender, visible, collapsed } = this.props;
|
||||
let isPhysicalFocused = mobx
|
||||
const { screen, line, width, collapsed } = this.props;
|
||||
const isPhysicalFocused = mobx
|
||||
.computed(() => screen.getIsFocused(line.linenum), {
|
||||
name: "computed-getIsFocused",
|
||||
})
|
||||
.get();
|
||||
let isFocused = mobx
|
||||
const isFocused = mobx
|
||||
.computed(
|
||||
() => {
|
||||
let screenFocusType = screen.getFocusType();
|
||||
@ -200,15 +197,15 @@ class TerminalRenderer extends React.Component<
|
||||
{ name: "computed-isFocused" }
|
||||
)
|
||||
.get();
|
||||
let cmd = screen.getCmd(line); // will not be null
|
||||
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
|
||||
const cmd = screen.getCmd(line); // will not be null
|
||||
const usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
|
||||
let termHeight = termHeightFromRows(usedRows, GlobalModel.getTermFontSize(), cmd.getTermMaxRows());
|
||||
if (usedRows === 0) {
|
||||
termHeight = 0;
|
||||
}
|
||||
let termLoaded = this.termLoaded.get();
|
||||
let lineid = line.lineid;
|
||||
let termWrap = screen.getTermWrap(lineid);
|
||||
const termLoaded = this.termLoaded.get();
|
||||
const lineid = line.lineid;
|
||||
const termWrap = screen.getTermWrap(lineid);
|
||||
return (
|
||||
<div
|
||||
ref={this.elemRef}
|
||||
|
7
src/types/custom.d.ts
vendored
7
src/types/custom.d.ts
vendored
@ -12,6 +12,7 @@ declare global {
|
||||
type RemoteStatusTypeStrs = "connected" | "connecting" | "disconnected" | "error";
|
||||
type LineContainerStrs = "main" | "sidebar" | "history";
|
||||
type AppUpdateStatusType = "unavailable" | "ready";
|
||||
type NativeThemeSource = "system" | "light" | "dark";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
@ -563,7 +564,7 @@ declare global {
|
||||
type FeOptsType = {
|
||||
termfontsize: number;
|
||||
termfontfamily: string;
|
||||
theme: string;
|
||||
theme: NativeThemeSource;
|
||||
};
|
||||
|
||||
type ConfirmFlagsType = {
|
||||
@ -898,6 +899,10 @@ declare global {
|
||||
getAuthKey: () => string;
|
||||
getWaveSrvStatus: () => boolean;
|
||||
getInitialTermFontFamily: () => string;
|
||||
getShouldUseDarkColors: () => boolean;
|
||||
getNativeThemeSource: () => NativeThemeSource;
|
||||
setNativeThemeSource: (source: NativeThemeSource) => void;
|
||||
onNativeThemeUpdated: (callback: () => void) => void;
|
||||
restartWaveSrv: () => boolean;
|
||||
reloadWindow: () => void;
|
||||
openExternalLink: (url: string) => void;
|
||||
|
@ -1,10 +0,0 @@
|
||||
function loadTheme(theme: string) {
|
||||
const linkTag: any = document.getElementById("theme-stylesheet");
|
||||
if (theme === "dark") {
|
||||
linkTag.href = "public/themes/default.css";
|
||||
} else {
|
||||
linkTag.href = `public/themes/${theme}.css`;
|
||||
}
|
||||
}
|
||||
|
||||
export { loadTheme };
|
@ -104,7 +104,7 @@ var RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cy
|
||||
var RemoteSetArgs = []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}
|
||||
var ConfirmFlags = []string{"hideshellprompt"}
|
||||
var SidebarNames = []string{"main"}
|
||||
var ThemeNames = []string{"light", "dark"}
|
||||
var ThemeSources = []string{"light", "dark", "system"}
|
||||
|
||||
var ScreenCmds = []string{"run", "comment", "cd", "cr", "clear", "sw", "reset", "signal", "chat"}
|
||||
var NoHistCmds = []string{"_compgen", "line", "history", "_killserver"}
|
||||
@ -5529,20 +5529,20 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
|
||||
}
|
||||
varsUpdated = append(varsUpdated, "termfontfamily")
|
||||
}
|
||||
if themeStr, found := pk.Kwargs["theme"]; found {
|
||||
newTheme := themeStr
|
||||
if themeSourceStr, found := pk.Kwargs["theme"]; found {
|
||||
newThemeSource := themeSourceStr
|
||||
found := false
|
||||
for _, theme := range ThemeNames {
|
||||
if newTheme == theme {
|
||||
for _, theme := range ThemeSources {
|
||||
if newThemeSource == theme {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("invalid theme name")
|
||||
return nil, fmt.Errorf("invalid theme source")
|
||||
}
|
||||
feOpts := clientData.FeOpts
|
||||
feOpts.Theme = newTheme
|
||||
feOpts.Theme = newThemeSource
|
||||
err = sstore.UpdateClientFeOpts(ctx, feOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating client feopts: %v", err)
|
||||
|
Loading…
Reference in New Issue
Block a user