diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index 15c4b364a..0c67f13aa 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -264,6 +264,7 @@ const BlockFrame_Default_Component = ({ const headerTextUnion = util.useAtomValueSafe(viewModel.viewText); const preIconButton = util.useAtomValueSafe(viewModel.preIconButton); const endIconButtons = util.useAtomValueSafe(viewModel.endIconButtons); + const customBg = util.useAtomValueSafe(viewModel.blockBg); if (preview) { isFocused = true; } @@ -381,6 +382,16 @@ const BlockFrame_Default_Component = ({ layoutModel?.onMagnifyToggle(); } + const innerStyle: React.CSSProperties = {}; + if (customBg?.bg != null) { + innerStyle.background = customBg.bg; + if (customBg["bg:opacity"] != null) { + innerStyle.opacity = customBg["bg:opacity"]; + } + if (customBg["bg:blendmode"] != null) { + innerStyle.backgroundBlendMode = customBg["bg:blendmode"]; + } + } return (
-
+
; viewText: jotai.Atom; viewName: jotai.Atom; + blockBg: jotai.Atom; constructor(blockId: string) { this.blockId = blockId; @@ -152,6 +139,15 @@ class TermViewModel { const blockData = get(this.blockAtom); return blockData?.meta?.title ?? ""; }); + this.blockBg = jotai.atom((get) => { + const blockData = get(this.blockAtom); + const settings = globalStore.get(atoms.settingsConfigAtom); + const theme = computeTheme(settings, blockData?.meta?.["term:theme"]); + if (theme != null && theme.background != null) { + return { bg: theme.background }; + } + return null; + }); } giveFocus(): boolean { @@ -169,6 +165,23 @@ class TermViewModel { } return false; } + + setTerminalTheme(themeName: string) { + WshServer.SetMetaCommand({ oref: WOS.makeORef("block", this.blockId), meta: { "term:theme": themeName } }); + } + + getSettingsMenuItems(): ContextMenuItem[] { + return [ + { + label: "Themes", + submenu: [ + { label: "Default Dark", click: () => this.setTerminalTheme("default") }, + { label: "Dracula", click: () => this.setTerminalTheme("dracula") }, + { label: "Campbell", click: () => this.setTerminalTheme("campbell") }, + ], + }, + ]; + } } function makeTerminalModel(blockId: string): TermViewModel { @@ -220,11 +233,13 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { } const settings = globalStore.get(atoms.settingsConfigAtom); const termTheme = computeTheme(settings, blockData?.meta?.["term:theme"]); + const themeCopy = { ...termTheme }; + themeCopy.background = "#00000000"; const termWrap = new TermWrap( blockId, connectElemRef.current, { - theme: termTheme, + theme: themeCopy, fontSize: termSettings?.fontsize ?? 12, fontFamily: termSettings?.fontfamily ?? "Hack", drawBoldTextInBrightColors: false, diff --git a/frontend/app/view/term/termtheme.ts b/frontend/app/view/term/termtheme.ts index e39c18fc4..5f8b22ce6 100644 --- a/frontend/app/view/term/termtheme.ts +++ b/frontend/app/view/term/termtheme.ts @@ -29,7 +29,9 @@ const TermThemeUpdater = ({ blockId, termRef }: TermThemeProps) => { } } if (termRef.current?.terminal) { - termRef.current.terminal.options.theme = combinedTheme; + let themeCopy = { ...combinedTheme }; + themeCopy.background = "#00000000"; + termRef.current.terminal.options.theme = themeCopy; } }, [defaultTheme, theme]); diff --git a/frontend/app/view/term/termutil.ts b/frontend/app/view/term/termutil.ts new file mode 100644 index 000000000..574864048 --- /dev/null +++ b/frontend/app/view/term/termutil.ts @@ -0,0 +1,20 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as util from "@/util/util"; + +function computeTheme(settings: SettingsConfigType, themeName: string): TermThemeType { + let defaultThemeName = "default-dark"; + themeName = themeName ?? "default-dark"; + const defaultTheme: TermThemeType = settings?.termthemes?.[defaultThemeName] || ({} as any); + const theme: TermThemeType = settings?.termthemes?.[themeName] || ({} as any); + const combinedTheme = { ...defaultTheme }; + for (const key in theme) { + if (!util.isBlank(theme[key])) { + combinedTheme[key] = theme[key]; + } + } + return combinedTheme; +} + +export { computeTheme }; diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index d3bec07df..c7efbfce4 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -166,6 +166,7 @@ declare global { viewText?: jotai.Atom; preIconButton?: jotai.Atom; endIconButtons?: jotai.Atom; + blockBg?: jotai.Atom; onBack?: () => void; onForward?: () => void; diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 06a90502d..b55f51724 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -125,10 +125,60 @@ var DefaultTermDarkTheme = TermThemeType{ CmdText: "#f0f0f0", Foreground: "#c1c1c1", SelectionBackground: "", - Background: "#00000000", + Background: "#00000077", CursorAccent: "", } +var DraculaTheme = TermThemeType{ + Black: "#21222C", // AnsiBlack + Red: "#FF5555", // AnsiRed + Green: "#50FA7B", // AnsiGreen + Yellow: "#F1FA8C", // AnsiYellow + Blue: "#BD93F9", // AnsiBlue + Magenta: "#FF79C6", // AnsiMagenta + Cyan: "#8BE9FD", // AnsiCyan + White: "#F8F8F2", // AnsiWhite + BrightBlack: "#6272A4", // AnsiBrightBlack + BrightRed: "#FF6E6E", // AnsiBrightRed + BrightGreen: "#69FF94", // AnsiBrightGreen + BrightYellow: "#FFFFA5", // AnsiBrightYellow + BrightBlue: "#D6ACFF", // AnsiBrightBlue + BrightMagenta: "#FF92DF", // AnsiBrightMagenta + BrightCyan: "#A4FFFF", // AnsiBrightCyan + BrightWhite: "#FFFFFF", // AnsiBrightWhite + Gray: "#6272A4", // Comment or closest approximation + CmdText: "#F8F8F2", // Foreground + Foreground: "#F8F8F2", // Foreground + SelectionBackground: "#44475a", // Selection + Background: "#282a36", // Background + CursorAccent: "#f8f8f2", // Foreground (used for cursor accent) +} + +var CampbellTheme = TermThemeType{ + Black: "#0C0C0C", // Black + Red: "#C50F1F", // Red + Green: "#13A10E", // Green + Yellow: "#C19C00", // Yellow + Blue: "#0037DA", // Blue + Magenta: "#881798", // Purple (used as Magenta) + Cyan: "#3A96DD", // Cyan + White: "#CCCCCC", // White + BrightBlack: "#767676", // BrightBlack + BrightRed: "#E74856", // BrightRed + BrightGreen: "#16C60C", // BrightGreen + BrightYellow: "#F9F1A5", // BrightYellow + BrightBlue: "#3B78FF", // BrightBlue + BrightMagenta: "#B4009E", // BrightPurple (used as BrightMagenta) + BrightCyan: "#61D6D6", // BrightCyan + BrightWhite: "#F2F2F2", // BrightWhite + Gray: "#767676", // BrightBlack or closest approximation + CmdText: "#CCCCCC", // Foreground + Foreground: "#CCCCCC", // Foreground + SelectionBackground: "#3A96DD", // Cyan (chosen for selection background) + Background: "#0C0C0C", // Background + CursorAccent: "#CCCCCC", // Foreground (used for cursor accent) +} + func applyDefaultSettings(settings *SettingsConfigType) { defaultMimeTypes := map[string]MimeTypeConfigType{ "audio": {Icon: "file-audio"}, @@ -229,4 +279,10 @@ func applyDefaultSettings(settings *SettingsConfigType) { if _, found := settings.TermThemes["default-dark"]; !found { settings.TermThemes["default-dark"] = DefaultTermDarkTheme } + if _, found := settings.TermThemes["dracula"]; !found { + settings.TermThemes["dracula"] = DraculaTheme + } + if _, found := settings.TermThemes["campbell"]; !found { + settings.TermThemes["campbell"] = CampbellTheme + } }