diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 5a34b718c..5a48a7beb 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -151,6 +151,23 @@ function useBlockCache(blockId: string, name: string, makeFn: () => T): T { return value as T; } +const settingsAtomCache = new Map>(); + +function useSettingsAtom(name: string, settingsFn: (settings: SettingsConfigType) => T): jotai.Atom { + let atom = settingsAtomCache.get(name); + if (atom == null) { + atom = jotai.atom((get) => { + const settings = get(settingsConfigAtom); + if (settings == null) { + return null; + } + return settingsFn(settings); + }) as jotai.Atom; + settingsAtomCache.set(name, atom); + } + return atom as jotai.Atom; +} + const blockAtomCache = new Map>>(); function useBlockAtom(blockId: string, name: string, makeFn: () => jotai.Atom): jotai.Atom { @@ -322,4 +339,5 @@ export { setBlockFocus, useBlockAtom, useBlockCache, + useSettingsAtom, }; diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index cc4703b0e..d1dad220f 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -1,7 +1,7 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { WOS, atoms, globalStore, sendWSCommand, useBlockAtom } from "@/store/global"; +import { WOS, atoms, globalStore, sendWSCommand, useBlockAtom, useSettingsAtom } from "@/store/global"; import * as services from "@/store/services"; import { FitAddon } from "@xterm/addon-fit"; import type { ITheme } from "@xterm/xterm"; @@ -151,12 +151,16 @@ const TerminalView = ({ blockId }: { blockId: string }) => { return winData.activeblockid === blockId; }); }); + const termSettingsAtom = useSettingsAtom("term", (settings: SettingsConfigType) => { + return settings?.term; + }); + const termSettings = jotai.useAtomValue(termSettingsAtom); const isFocused = jotai.useAtomValue(isFocusedAtom); React.useEffect(() => { const termWrap = new TermWrap(blockId, connectElemRef.current, { theme: getThemeFromCSSVars(connectElemRef.current), - fontSize: 12, - fontFamily: "Hack", + fontSize: termSettings?.fontsize ?? 12, + fontFamily: termSettings?.fontfamily ?? "Hack", drawBoldTextInBrightColors: false, fontWeight: "normal", fontWeightBold: "bold", diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index cc98f13d6..0e559ea15 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -31,7 +31,7 @@ declare global { type BlockCommand = { command: string; - } & ( CreateBlockCommand | BlockInputCommand | BlockAppendFileCommand | ResolveIdsCommand | BlockMessageCommand | BlockAppendIJsonCommand | BlockSetViewCommand | BlockSetMetaCommand | BlockGetMetaCommand ); + } & ( BlockAppendFileCommand | BlockAppendIJsonCommand | BlockInputCommand | CreateBlockCommand | BlockGetMetaCommand | BlockMessageCommand | ResolveIdsCommand | BlockSetMetaCommand | BlockSetViewCommand ); // wstore.BlockDef type BlockDef = { @@ -180,6 +180,7 @@ declare global { // wconfig.SettingsConfigType type SettingsConfigType = { widgets: WidgetsConfigType[]; + term: TerminalConfigType; }; // wstore.StickerClickOptsType @@ -217,6 +218,12 @@ declare global { cols: number; }; + // wconfig.TerminalConfigType + type TerminalConfigType = { + fontsize?: number; + fontfamily?: string; + }; + // wstore.UIContext type UIContext = { windowid: string; diff --git a/frontend/util/util.ts b/frontend/util/util.ts index f5ee40822..e64aa0623 100644 --- a/frontend/util/util.ts +++ b/frontend/util/util.ts @@ -32,4 +32,43 @@ function base64ToArray(b64: string): Uint8Array { return rtnArr; } -export { base64ToArray, base64ToString, isBlank, stringToBase64 }; +// works for json-like objects (arrays, objects, strings, numbers, booleans) +function jsonDeepEqual(v1: any, v2: any): boolean { + if (v1 === v2) { + return true; + } + if (typeof v1 !== typeof v2) { + return false; + } + if ((v1 == null && v2 != null) || (v1 != null && v2 == null)) { + return false; + } + if (typeof v1 === "object") { + if (Array.isArray(v1) && Array.isArray(v2)) { + if (v1.length !== v2.length) { + return false; + } + for (let i = 0; i < v1.length; i++) { + if (!jsonDeepEqual(v1[i], v2[i])) { + return false; + } + } + return true; + } else { + const keys1 = Object.keys(v1); + const keys2 = Object.keys(v2); + if (keys1.length !== keys2.length) { + return false; + } + for (let key of keys1) { + if (!jsonDeepEqual(v1[key], v2[key])) { + return false; + } + } + return true; + } + } + return false; +} + +export { base64ToArray, base64ToString, isBlank, jsonDeepEqual, stringToBase64 }; diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 9a1e5040c..df9e98694 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -19,8 +19,14 @@ type WidgetsConfigType struct { BlockDef wstore.BlockDef `json:"blockdef"` } +type TerminalConfigType struct { + FontSize int `json:"fontsize,omitempty"` + FontFamily string `json:"fontfamily,omitempty"` +} + type SettingsConfigType struct { Widgets []WidgetsConfigType `json:"widgets"` + Term TerminalConfigType `json:"term"` } func getSettingsConfigDefaults() SettingsConfigType { diff --git a/pkg/wshutil/wshcommands.go b/pkg/wshutil/wshcommands.go index 8f9b8175e..6cd02e07f 100644 --- a/pkg/wshutil/wshcommands.go +++ b/pkg/wshutil/wshcommands.go @@ -11,6 +11,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/ijson" "github.com/wavetermdev/thenextwave/pkg/shellexec" "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" + "github.com/wavetermdev/thenextwave/pkg/util/utilfn" "github.com/wavetermdev/thenextwave/pkg/wstore" ) @@ -42,7 +43,9 @@ var CommandToTypeMap = map[string]reflect.Type{ func CommandTypeUnionMeta() tsgenmeta.TypeUnionMeta { var rtypes []reflect.Type - for _, rtype := range CommandToTypeMap { + orderedKeys := utilfn.GetOrderedMapKeys(CommandToTypeMap) + for _, typeKey := range orderedKeys { + rtype := CommandToTypeMap[typeKey] rtypes = append(rtypes, rtype) } return tsgenmeta.TypeUnionMeta{