From 6065ee931fcddbbfd7efe527e01bcd43b1bd30bf Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 26 Mar 2024 19:14:03 -0700 Subject: [PATCH] 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 --- public/index-dev.html | 12 +- public/index.html | 12 +- public/themes/themequery.css | 3 + src/app/app.tsx | 4 +- src/app/clientsettings/clientsettings.tsx | 24 ++-- src/electron/emain.ts | 19 ++++ src/electron/preload.js | 4 + src/models/commandrunner.ts | 2 +- src/models/model.ts | 32 +++--- src/plugins/code/code.tsx | 131 ++++++++++------------ src/plugins/terminal/terminal.tsx | 55 +++++---- src/types/custom.d.ts | 7 +- src/util/themeutil.ts | 10 -- wavesrv/pkg/cmdrunner/cmdrunner.go | 14 +-- 14 files changed, 173 insertions(+), 156 deletions(-) create mode 100644 public/themes/themequery.css delete mode 100644 src/util/themeutil.ts diff --git a/public/index-dev.html b/public/index-dev.html index cd5b16f8..324ecf1c 100644 --- a/public/index-dev.html +++ b/public/index-dev.html @@ -5,14 +5,14 @@ - - - - - + + + + + - +
diff --git a/public/index.html b/public/index.html index af410542..df078c8a 100644 --- a/public/index.html +++ b/public/index.html @@ -5,14 +5,14 @@ - - - - - + + + + + - +
diff --git a/public/themes/themequery.css b/public/themes/themequery.css new file mode 100644 index 00000000..d3284ecc --- /dev/null +++ b/public/themes/themequery.css @@ -0,0 +1,3 @@ +@import url("./default.css") screen; + +@import url("./light.css") screen and (prefers-color-scheme: light); diff --git a/src/app/app.tsx b/src/app/app.tsx index a5745c9d..f70c1eda 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -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 (
@@ -225,9 +227,9 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
diff --git a/src/electron/emain.ts b/src/electron/emain.ts index 3e46f369..dd421b6c 100644 --- a/src/electron/emain.ts +++ b/src/electron/emain.ts @@ -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) => { diff --git a/src/electron/preload.js b/src/electron/preload.js index 2ef26f7d..81961077 100644 --- a/src/electron/preload.js +++ b/src/electron/preload.js @@ -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), diff --git a/src/models/commandrunner.ts b/src/models/commandrunner.ts index b8b72119..749bfaa9 100644 --- a/src/models/commandrunner.ts +++ b/src/models/commandrunner.ts @@ -365,7 +365,7 @@ class CommandRunner { return GlobalModel.submitCommand("client", "set", null, kwargs, interactive); } - setTheme(theme: string, interactive: boolean): Promise { + setTheme(theme: NativeThemeSource, interactive: boolean): Promise { let kwargs = { nohist: "1", theme: theme, diff --git a/src/models/model.ts b/src/models/model.ts index f8fa0874..557a47fc 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -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 = mobx.observable.box(getApi().getShouldUseDarkColors(), { + name: "isDarkTheme", + }); clientData: OV = 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(); } } diff --git a/src/plugins/code/code.tsx b/src/plugins/code/code.tsx index abb7a2b1..a6647cc2 100644 --- a/src/plugins/code/code.tsx +++ b/src/plugins/code/code.tsx @@ -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; 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 (
{this.state.showReadonly &&
{"read-only"}
} @@ -447,9 +433,9 @@ class SourceCodeRenderer extends React.Component< />
); - }; + } - getPreviewer = () => { + getPreviewer() { return (
); - }; + } - 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 ( <> @@ -507,20 +496,22 @@ class SourceCodeRenderer extends React.Component< ); - }; + } - getMessage = () => ( -
-
- {this.state.message.text} + getMessage() { + return ( +
+
+ {this.state.message.text} +
-
- ); + ); + } - 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<
); } - 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 (
@@ -559,7 +552,7 @@ class SourceCodeRenderer extends React.Component< - {this.getCodeEditor()} + {this.getCodeEditor(theme)} {isPreviewerAvailable && showPreview && this.getPreviewer()}
diff --git a/src/plugins/terminal/terminal.tsx b/src/plugins/terminal/terminal.tsx index 4de5cd6c..d0c0e087 100644 --- a/src/plugins/terminal/terminal.tsx +++ b/src/plugins/terminal/terminal.tsx @@ -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 (
= mobx.IObservableValue; type OArr = mobx.IObservableArray; @@ -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; diff --git a/src/util/themeutil.ts b/src/util/themeutil.ts deleted file mode 100644 index 1c622248..00000000 --- a/src/util/themeutil.ts +++ /dev/null @@ -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 }; diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index 5d8f410e..d57a6e1f 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -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)