mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
new config system (#283)
This commit is contained in:
+6
-4
@@ -164,17 +164,19 @@ tasks:
|
||||
generate:
|
||||
desc: Generate Typescript bindings for the Go backend.
|
||||
cmds:
|
||||
- go run cmd/generate/main-generate.go
|
||||
- go run cmd/generatewshclient/main-generatewshclient.go
|
||||
- go run cmd/generatets/main-generatets.go
|
||||
- go run cmd/generatego/main-generatego.go
|
||||
sources:
|
||||
- "cmd/generate/*.go"
|
||||
- "cmd/generatewshclient/*.go"
|
||||
- "cmd/generatego/*.go"
|
||||
- "cmd/generatets/*.go"
|
||||
- "pkg/service/**/*.go"
|
||||
- "pkg/waveobj/wtype.go"
|
||||
- "pkg/wconfig/**/*.go"
|
||||
- "pkg/wstore/*.go"
|
||||
- "pkg/wshrpc/**/*.go"
|
||||
- "pkg/tsgen/**/*.go"
|
||||
- "pkg/gogen/**/*.go"
|
||||
- "pkg/wconfig/**/*.go"
|
||||
- "pkg/eventbus/eventbus.go"
|
||||
generates:
|
||||
- frontend/types/gotypes.d.ts
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/wavetermdev/thenextwave/pkg/gogen"
|
||||
"github.com/wavetermdev/thenextwave/pkg/util/utilfn"
|
||||
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
||||
"github.com/wavetermdev/thenextwave/pkg/wconfig"
|
||||
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
|
||||
)
|
||||
|
||||
const WshClientFileName = "pkg/wshrpc/wshclient/wshclient.go"
|
||||
const WaveObjMetaConstsFileName = "pkg/waveobj/metaconsts.go"
|
||||
const SettingsMetaConstsFileName = "pkg/wconfig/metaconsts.go"
|
||||
|
||||
func GenerateWshClient() {
|
||||
fmt.Fprintf(os.Stderr, "generating wshclient file to %s\n", WshClientFileName)
|
||||
var buf strings.Builder
|
||||
gogen.GenerateBoilerplate(&buf, "wshclient", []string{
|
||||
"github.com/wavetermdev/thenextwave/pkg/wshutil",
|
||||
"github.com/wavetermdev/thenextwave/pkg/wshrpc",
|
||||
"github.com/wavetermdev/thenextwave/pkg/waveobj",
|
||||
})
|
||||
wshDeclMap := wshrpc.GenerateWshCommandDeclMap()
|
||||
for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) {
|
||||
methodDecl := wshDeclMap[key]
|
||||
if methodDecl.CommandType == wshrpc.RpcType_ResponseStream {
|
||||
gogen.GenMethod_ResponseStream(&buf, methodDecl)
|
||||
} else if methodDecl.CommandType == wshrpc.RpcType_Call {
|
||||
gogen.GenMethod_Call(&buf, methodDecl)
|
||||
} else {
|
||||
panic("unsupported command type " + methodDecl.CommandType)
|
||||
}
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
err := os.WriteFile(WshClientFileName, []byte(buf.String()), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateWaveObjMetaConsts() {
|
||||
fmt.Fprintf(os.Stderr, "generating waveobj meta consts file to %s\n", WaveObjMetaConstsFileName)
|
||||
var buf strings.Builder
|
||||
gogen.GenerateBoilerplate(&buf, "waveobj", []string{})
|
||||
gogen.GenerateMetaMapConsts(&buf, "MetaKey_", reflect.TypeOf(waveobj.MetaTSType{}))
|
||||
buf.WriteString("\n")
|
||||
err := os.WriteFile(WaveObjMetaConstsFileName, []byte(buf.String()), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateSettingsMetaConsts() {
|
||||
fmt.Fprintf(os.Stderr, "generating settings meta consts file to %s\n", SettingsMetaConstsFileName)
|
||||
var buf strings.Builder
|
||||
gogen.GenerateBoilerplate(&buf, "wconfig", []string{})
|
||||
gogen.GenerateMetaMapConsts(&buf, "ConfigKey_", reflect.TypeOf(wconfig.SettingsType{}))
|
||||
buf.WriteString("\n")
|
||||
err := os.WriteFile(SettingsMetaConstsFileName, []byte(buf.String()), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
GenerateWshClient()
|
||||
GenerateWaveObjMetaConsts()
|
||||
GenerateSettingsMetaConsts()
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/wavetermdev/thenextwave/pkg/util/utilfn"
|
||||
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
|
||||
)
|
||||
|
||||
func genMethod_ResponseStream(fd *os.File, methodDecl *wshrpc.WshRpcMethodDecl) {
|
||||
fmt.Fprintf(fd, "// command %q, wshserver.%s\n", methodDecl.Command, methodDecl.MethodName)
|
||||
var dataType string
|
||||
dataVarName := "nil"
|
||||
if methodDecl.CommandDataType != nil {
|
||||
dataType = ", data " + methodDecl.CommandDataType.String()
|
||||
dataVarName = "data"
|
||||
}
|
||||
respType := "any"
|
||||
if methodDecl.DefaultResponseDataType != nil {
|
||||
respType = methodDecl.DefaultResponseDataType.String()
|
||||
}
|
||||
fmt.Fprintf(fd, "func %s(w *wshutil.WshRpc%s, opts *wshrpc.RpcOpts) chan wshrpc.RespOrErrorUnion[%s] {\n", methodDecl.MethodName, dataType, respType)
|
||||
fmt.Fprintf(fd, " return sendRpcRequestResponseStreamHelper[%s](w, %q, %s, opts)\n", respType, methodDecl.Command, dataVarName)
|
||||
fmt.Fprintf(fd, "}\n\n")
|
||||
}
|
||||
|
||||
func genMethod_Call(fd *os.File, methodDecl *wshrpc.WshRpcMethodDecl) {
|
||||
fmt.Fprintf(fd, "// command %q, wshserver.%s\n", methodDecl.Command, methodDecl.MethodName)
|
||||
var dataType string
|
||||
dataVarName := "nil"
|
||||
if methodDecl.CommandDataType != nil {
|
||||
dataType = ", data " + methodDecl.CommandDataType.String()
|
||||
dataVarName = "data"
|
||||
}
|
||||
returnType := "error"
|
||||
respName := "_"
|
||||
tParamVal := "any"
|
||||
if methodDecl.DefaultResponseDataType != nil {
|
||||
returnType = "(" + methodDecl.DefaultResponseDataType.String() + ", error)"
|
||||
respName = "resp"
|
||||
tParamVal = methodDecl.DefaultResponseDataType.String()
|
||||
}
|
||||
fmt.Fprintf(fd, "func %s(w *wshutil.WshRpc%s, opts *wshrpc.RpcOpts) %s {\n", methodDecl.MethodName, dataType, returnType)
|
||||
fmt.Fprintf(fd, " %s, err := sendRpcRequestCallHelper[%s](w, %q, %s, opts)\n", respName, tParamVal, methodDecl.Command, dataVarName)
|
||||
if methodDecl.DefaultResponseDataType != nil {
|
||||
fmt.Fprintf(fd, " return resp, err\n")
|
||||
} else {
|
||||
fmt.Fprintf(fd, " return err\n")
|
||||
}
|
||||
fmt.Fprintf(fd, "}\n\n")
|
||||
}
|
||||
|
||||
func main() {
|
||||
fd, err := os.Create("pkg/wshrpc/wshclient/wshclient.go")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
fmt.Fprintf(os.Stderr, "generating wshclient file to %s\n", fd.Name())
|
||||
fmt.Fprintf(fd, "// Copyright 2024, Command Line Inc.\n")
|
||||
fmt.Fprintf(fd, "// SPDX-License-Identifier: Apache-2.0\n\n")
|
||||
fmt.Fprintf(fd, "// generated by cmd/generatewshclient/main-generatewshclient.go\n\n")
|
||||
fmt.Fprintf(fd, "package wshclient\n\n")
|
||||
fmt.Fprintf(fd, "import (\n")
|
||||
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/wshutil\"\n")
|
||||
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/wshrpc\"\n")
|
||||
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/waveobj\"\n")
|
||||
fmt.Fprintf(fd, ")\n\n")
|
||||
|
||||
wshDeclMap := wshrpc.GenerateWshCommandDeclMap()
|
||||
for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) {
|
||||
methodDecl := wshDeclMap[key]
|
||||
if methodDecl.CommandType == wshrpc.RpcType_ResponseStream {
|
||||
genMethod_ResponseStream(fd, methodDecl)
|
||||
} else if methodDecl.CommandType == wshrpc.RpcType_Call {
|
||||
genMethod_Call(fd, methodDecl)
|
||||
} else {
|
||||
panic("unsupported command type " + methodDecl.CommandType)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(fd, "\n")
|
||||
}
|
||||
+10
-13
@@ -191,8 +191,8 @@ async function handleWSEvent(evtMsg: WSEventType) {
|
||||
return;
|
||||
}
|
||||
const clientData = await services.ClientService.GetClientData();
|
||||
const settings = await services.FileService.GetSettingsConfig();
|
||||
const newWin = createBrowserWindow(clientData.oid, windowData, settings);
|
||||
const fullConfig = await services.FileService.GetFullConfig();
|
||||
const newWin = createBrowserWindow(clientData.oid, windowData, fullConfig);
|
||||
await newWin.readyPromise;
|
||||
newWin.show();
|
||||
} else if (evtMsg.eventtype == "electron:closewindow") {
|
||||
@@ -268,11 +268,7 @@ function shFrameNavHandler(event: Electron.Event<Electron.WebContentsWillFrameNa
|
||||
|
||||
// note, this does not *show* the window.
|
||||
// to show, await win.readyPromise and then win.show()
|
||||
function createBrowserWindow(
|
||||
clientId: string,
|
||||
waveWindow: WaveWindow,
|
||||
settings: SettingsConfigType
|
||||
): WaveBrowserWindow {
|
||||
function createBrowserWindow(clientId: string, waveWindow: WaveWindow, fullConfig: FullConfigType): WaveBrowserWindow {
|
||||
let winWidth = waveWindow?.winsize?.width;
|
||||
let winHeight = waveWindow?.winsize?.height;
|
||||
let winPosX = waveWindow.pos.x;
|
||||
@@ -326,8 +322,9 @@ function createBrowserWindow(
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
};
|
||||
const isTransparent = settings?.window?.transparent ?? false;
|
||||
const isBlur = !isTransparent && (settings?.window?.blur ?? false);
|
||||
const settings = fullConfig?.settings;
|
||||
const isTransparent = settings?.["window:transparent"] ?? false;
|
||||
const isBlur = !isTransparent && (settings?.["window:blur"] ?? false);
|
||||
if (isTransparent) {
|
||||
winOpts.transparent = true;
|
||||
} else if (isBlur) {
|
||||
@@ -582,8 +579,8 @@ if (unamePlatform !== "darwin") {
|
||||
async function createNewWaveWindow(): Promise<void> {
|
||||
const clientData = await services.ClientService.GetClientData();
|
||||
const newWindow = await services.ClientService.MakeWindow();
|
||||
const settings = await services.FileService.GetSettingsConfig();
|
||||
const newBrowserWindow = createBrowserWindow(clientData.oid, newWindow, settings);
|
||||
const fullConfig = await services.FileService.GetFullConfig();
|
||||
const newBrowserWindow = createBrowserWindow(clientData.oid, newWindow, fullConfig);
|
||||
newBrowserWindow.show();
|
||||
}
|
||||
|
||||
@@ -700,7 +697,7 @@ async function relaunchBrowserWindows(): Promise<void> {
|
||||
globalIsRelaunching = false;
|
||||
|
||||
const clientData = await services.ClientService.GetClientData();
|
||||
const settings = await services.FileService.GetSettingsConfig();
|
||||
const fullConfig = await services.FileService.GetFullConfig();
|
||||
const wins: WaveBrowserWindow[] = [];
|
||||
for (const windowId of clientData.windowids.slice().reverse()) {
|
||||
const windowData: WaveWindow = (await services.ObjectService.GetObject("window:" + windowId)) as WaveWindow;
|
||||
@@ -710,7 +707,7 @@ async function relaunchBrowserWindows(): Promise<void> {
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const win = createBrowserWindow(clientData.oid, windowData, settings);
|
||||
const win = createBrowserWindow(clientData.oid, windowData, fullConfig);
|
||||
wins.push(win);
|
||||
}
|
||||
for (const win of wins) {
|
||||
|
||||
@@ -27,6 +27,10 @@ ipcMain.on("get-is-dev", (event) => {
|
||||
ipcMain.on("get-platform", (event, url) => {
|
||||
event.returnValue = unamePlatform;
|
||||
});
|
||||
ipcMain.on("get-user-name", (event) => {
|
||||
const userInfo = os.userInfo();
|
||||
event.returnValue = userInfo.username;
|
||||
});
|
||||
|
||||
// must match golang
|
||||
function getWaveHomeDir() {
|
||||
|
||||
@@ -8,6 +8,7 @@ contextBridge.exposeInMainWorld("api", {
|
||||
getIsDev: () => ipcRenderer.sendSync("get-is-dev"),
|
||||
getPlatform: () => ipcRenderer.sendSync("get-platform"),
|
||||
getCursorPoint: () => ipcRenderer.sendSync("get-cursor-point"),
|
||||
getUserName: () => ipcRenderer.sendSync("get-user-name"),
|
||||
openNewWindow: () => ipcRenderer.send("open-new-window"),
|
||||
showContextMenu: (menu, position) => ipcRenderer.send("contextmenu-show", menu, position),
|
||||
onContextMenuClick: (callback) => ipcRenderer.on("contextmenu-click", (_event, id) => callback(id)),
|
||||
|
||||
@@ -5,7 +5,7 @@ import { appHandleKeyDown, appHandleKeyUp } from "@/app/appkey";
|
||||
import { useWaveObjectValue } from "@/app/store/wos";
|
||||
import { Workspace } from "@/app/workspace/workspace";
|
||||
import { ContextMenuModel } from "@/store/contextmenu";
|
||||
import { PLATFORM, WOS, atoms, getApi, globalStore } from "@/store/global";
|
||||
import { PLATFORM, WOS, atoms, getApi, globalStore, useSettingsPrefixAtom } from "@/store/global";
|
||||
import { getWebServerEndpoint } from "@/util/endpoints";
|
||||
import * as keyutil from "@/util/keyutil";
|
||||
import * as util from "@/util/util";
|
||||
@@ -80,12 +80,13 @@ function handleContextMenu(e: React.MouseEvent<HTMLDivElement>) {
|
||||
}
|
||||
|
||||
function AppSettingsUpdater() {
|
||||
const settings = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||
const windowSettings = useSettingsPrefixAtom("window");
|
||||
React.useEffect(() => {
|
||||
const isTransparentOrBlur = (settings?.window?.transparent || settings?.window?.blur) ?? false;
|
||||
const opacity = util.boundNumber(settings?.window?.opacity ?? 0.8, 0, 1);
|
||||
let baseBgColor = settings?.window?.bgcolor;
|
||||
console.log("window settings", settings.window);
|
||||
const isTransparentOrBlur =
|
||||
(windowSettings?.["window:transparent"] || windowSettings?.["window:blur"]) ?? false;
|
||||
const opacity = util.boundNumber(windowSettings?.["window:opacity"] ?? 0.8, 0, 1);
|
||||
let baseBgColor = windowSettings?.["window:bgcolor"];
|
||||
console.log("window settings", windowSettings);
|
||||
if (isTransparentOrBlur) {
|
||||
document.body.classList.add("is-transparent");
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
@@ -99,7 +100,7 @@ function AppSettingsUpdater() {
|
||||
document.body.classList.remove("is-transparent");
|
||||
document.body.style.opacity = null;
|
||||
}
|
||||
}, [settings?.window]);
|
||||
}, [windowSettings]);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { Button } from "@/app/element/button";
|
||||
import { TypeAheadModal } from "@/app/modals/typeaheadmodal";
|
||||
import { ContextMenuModel } from "@/app/store/contextmenu";
|
||||
import { atoms, globalStore, useBlockAtom, WOS } from "@/app/store/global";
|
||||
import { atoms, globalStore, useBlockAtom, useSettingsKeyAtom, WOS } from "@/app/store/global";
|
||||
import * as services from "@/app/store/services";
|
||||
import { WshServer } from "@/app/store/wshserver";
|
||||
import { MagnifyIcon } from "@/element/magnify";
|
||||
@@ -132,7 +132,8 @@ const BlockFrame_Header = ({
|
||||
}: BlockFrameProps & { changeConnModalAtom: jotai.PrimitiveAtom<boolean> }) => {
|
||||
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
|
||||
const viewName = util.useAtomValueSafe(viewModel.viewName) ?? blockViewToName(blockData?.meta?.view);
|
||||
const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||
const showBlockIds = jotai.useAtomValue(useSettingsKeyAtom("blockheader:showblockids"));
|
||||
const settingsConfig = jotai.useAtomValue(atoms.settingsAtom);
|
||||
const viewIconUnion = util.useAtomValueSafe(viewModel.viewIcon) ?? blockViewToIcon(blockData?.meta?.view);
|
||||
const preIconButton = util.useAtomValueSafe(viewModel.preIconButton);
|
||||
const headerTextUnion = util.useAtomValueSafe(viewModel.viewText);
|
||||
@@ -190,9 +191,7 @@ const BlockFrame_Header = ({
|
||||
<div className="block-frame-default-header-iconview">
|
||||
{viewIconElem}
|
||||
<div className="block-frame-view-type">{viewName}</div>
|
||||
{settingsConfig?.blockheader?.showblockids && (
|
||||
<div className="block-frame-blockid">[{nodeModel.blockId.substring(0, 8)}]</div>
|
||||
)}
|
||||
{showBlockIds && <div className="block-frame-blockid">[{nodeModel.blockId.substring(0, 8)}]</div>}
|
||||
</div>
|
||||
|
||||
<div className="block-frame-textelems-wrapper">{headerTextElems}</div>
|
||||
|
||||
@@ -135,31 +135,6 @@ export function getBlockHeaderIcon(blockIcon: string, blockData: Block): React.R
|
||||
return blockIconElem;
|
||||
}
|
||||
|
||||
export function getBlockHeaderText(blockIcon: string, blockData: Block, settings: SettingsConfigType): React.ReactNode {
|
||||
if (!blockData) {
|
||||
return "no block data";
|
||||
}
|
||||
let blockIdStr = "";
|
||||
if (settings?.blockheader?.showblockids) {
|
||||
blockIdStr = ` [${blockData.oid.substring(0, 8)}]`;
|
||||
}
|
||||
let blockIconElem = getBlockHeaderIcon(blockIcon, blockData);
|
||||
if (!util.isBlank(blockData?.meta?.title)) {
|
||||
try {
|
||||
const rtn = processTitleString(blockData.meta.title) ?? [];
|
||||
return [blockIconElem, ...rtn, blockIdStr == "" ? null : blockIdStr];
|
||||
} catch (e) {
|
||||
console.error("error processing title", blockData.meta.title, e);
|
||||
return [blockIconElem, blockData.meta.title + blockIdStr];
|
||||
}
|
||||
}
|
||||
let viewString = blockData?.meta?.view;
|
||||
if (blockData?.meta?.controller == "cmd") {
|
||||
viewString = "cmd";
|
||||
}
|
||||
return [blockIconElem, viewString + blockIdStr];
|
||||
}
|
||||
|
||||
export const IconButton = React.memo(({ decl, className }: { decl: HeaderIconButton; className?: string }) => {
|
||||
const buttonRef = React.useRef<HTMLDivElement>(null);
|
||||
useLongClick(buttonRef, decl.click, decl.longClick);
|
||||
|
||||
@@ -96,7 +96,10 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
|
||||
}
|
||||
return WOS.getObjectValue(WOS.makeORef("workspace", windowData.workspaceid), get);
|
||||
});
|
||||
const settingsConfigAtom = jotai.atom(null) as jotai.PrimitiveAtom<SettingsConfigType>;
|
||||
const fullConfigAtom = jotai.atom(null) as jotai.PrimitiveAtom<FullConfigType>;
|
||||
const settingsAtom = jotai.atom((get) => {
|
||||
return get(fullConfigAtom)?.settings ?? {};
|
||||
}) as jotai.Atom<SettingsType>;
|
||||
const tabAtom: jotai.Atom<Tab> = jotai.atom((get) => {
|
||||
const windowData = get(windowDataAtom);
|
||||
if (windowData == null) {
|
||||
@@ -121,7 +124,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
|
||||
} catch (_) {
|
||||
// do nothing
|
||||
}
|
||||
const reducedMotionPreferenceAtom = jotai.atom((get) => get(settingsConfigAtom).window.reducedmotion);
|
||||
const reducedMotionPreferenceAtom = jotai.atom((get) => get(settingsAtom)?.["window:reducedmotion"]);
|
||||
const typeAheadModalAtom = jotai.atom({});
|
||||
atoms = {
|
||||
// initialized in wave.ts (will not be null inside of application)
|
||||
@@ -131,7 +134,8 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
|
||||
client: clientAtom,
|
||||
waveWindow: windowDataAtom,
|
||||
workspace: workspaceAtom,
|
||||
settingsConfigAtom,
|
||||
fullConfigAtom,
|
||||
settingsAtom,
|
||||
tabAtom,
|
||||
activeTabId: activeTabIdAtom,
|
||||
isFullScreen: isFullScreenAtom,
|
||||
@@ -271,19 +275,35 @@ function useBlockCache<T>(blockId: string, name: string, makeFn: () => T): T {
|
||||
|
||||
const settingsAtomCache = new Map<string, jotai.Atom<any>>();
|
||||
|
||||
function useSettingsAtom<T>(name: string, settingsFn: (settings: SettingsConfigType) => T): jotai.Atom<T> {
|
||||
let atom = settingsAtomCache.get(name);
|
||||
function useSettingsKeyAtom<T extends keyof SettingsType>(key: T): jotai.Atom<SettingsType[T]> {
|
||||
let atom = settingsAtomCache.get(key) as jotai.Atom<SettingsType[T]>;
|
||||
if (atom == null) {
|
||||
atom = jotai.atom((get) => {
|
||||
const settings = get(atoms.settingsConfigAtom);
|
||||
const settings = get(atoms.settingsAtom);
|
||||
if (settings == null) {
|
||||
return null;
|
||||
}
|
||||
return settingsFn(settings);
|
||||
}) as jotai.Atom<T>;
|
||||
settingsAtomCache.set(name, atom);
|
||||
return settings[key];
|
||||
});
|
||||
settingsAtomCache.set(key, atom);
|
||||
}
|
||||
return atom as jotai.Atom<T>;
|
||||
return atom;
|
||||
}
|
||||
|
||||
function useSettingsPrefixAtom(prefix: string): jotai.Atom<SettingsType> {
|
||||
// TODO: use a shallow equal here to make this more efficient
|
||||
let atom = settingsAtomCache.get(prefix + ":");
|
||||
if (atom == null) {
|
||||
atom = jotai.atom((get) => {
|
||||
const settings = get(atoms.settingsAtom);
|
||||
if (settings == null) {
|
||||
return {};
|
||||
}
|
||||
return util.getPrefixedSettings(settings, prefix);
|
||||
});
|
||||
settingsAtomCache.set(prefix + ":", atom);
|
||||
}
|
||||
return atom;
|
||||
}
|
||||
|
||||
const blockAtomCache = new Map<string, Map<string, jotai.Atom<any>>>();
|
||||
@@ -337,7 +357,7 @@ function handleWSEventMessage(msg: WSEventType) {
|
||||
return;
|
||||
}
|
||||
if (msg.eventtype == "config") {
|
||||
globalStore.set(atoms.settingsConfigAtom, msg.data.settings);
|
||||
globalStore.set(atoms.fullConfigAtom, (msg.data as WatcherUpdate).fullconfig);
|
||||
return;
|
||||
}
|
||||
if (msg.eventtype == "userinput") {
|
||||
@@ -474,8 +494,17 @@ function isDev() {
|
||||
return cachedIsDev;
|
||||
}
|
||||
|
||||
let cachedUserName: string = null;
|
||||
|
||||
function getUserName(): string {
|
||||
if (cachedUserName == null) {
|
||||
cachedUserName = getApi().getUserName();
|
||||
}
|
||||
return cachedUserName;
|
||||
}
|
||||
|
||||
async function openLink(uri: string) {
|
||||
if (globalStore.get(atoms.settingsConfigAtom)?.web?.openlinksinternally) {
|
||||
if (globalStore.get(atoms.settingsAtom)?.["web:openlinksinternally"]) {
|
||||
const blockDef: BlockDef = {
|
||||
meta: {
|
||||
view: "web",
|
||||
@@ -563,6 +592,7 @@ export {
|
||||
getEventSubject,
|
||||
getFileSubject,
|
||||
getObjectId,
|
||||
getUserName,
|
||||
getViewModel,
|
||||
globalStore,
|
||||
globalWS,
|
||||
@@ -581,7 +611,8 @@ export {
|
||||
useBlockAtom,
|
||||
useBlockCache,
|
||||
useBlockDataLoaded,
|
||||
useSettingsAtom,
|
||||
useSettingsKeyAtom,
|
||||
useSettingsPrefixAtom,
|
||||
waveEventSubscribe,
|
||||
waveEventUnsubscribe,
|
||||
WOS,
|
||||
|
||||
@@ -53,16 +53,12 @@ export const ClientService = new ClientServiceType();
|
||||
|
||||
// fileservice.FileService (file)
|
||||
class FileServiceType {
|
||||
AddWidget(arg1: WidgetsConfigType): Promise<void> {
|
||||
return WOS.callBackendService("file", "AddWidget", Array.from(arguments))
|
||||
}
|
||||
|
||||
// delete file
|
||||
DeleteFile(connection: string, path: string): Promise<void> {
|
||||
return WOS.callBackendService("file", "DeleteFile", Array.from(arguments))
|
||||
}
|
||||
GetSettingsConfig(): Promise<SettingsConfigType> {
|
||||
return WOS.callBackendService("file", "GetSettingsConfig", Array.from(arguments))
|
||||
GetFullConfig(): Promise<FullConfigType> {
|
||||
return WOS.callBackendService("file", "GetFullConfig", Array.from(arguments))
|
||||
}
|
||||
GetWaveFile(arg1: string, arg2: string): Promise<any> {
|
||||
return WOS.callBackendService("file", "GetWaveFile", Array.from(arguments))
|
||||
@@ -72,9 +68,6 @@ class FileServiceType {
|
||||
ReadFile(connection: string, path: string): Promise<FullFile> {
|
||||
return WOS.callBackendService("file", "ReadFile", Array.from(arguments))
|
||||
}
|
||||
RemoveWidget(arg1: number): Promise<void> {
|
||||
return WOS.callBackendService("file", "RemoveWidget", Array.from(arguments))
|
||||
}
|
||||
|
||||
// save file
|
||||
SaveFile(connection: string, path: string, data64: string): Promise<void> {
|
||||
|
||||
@@ -139,33 +139,33 @@ const Tab = React.memo(
|
||||
function handleContextMenu(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||
e.preventDefault();
|
||||
let menu: ContextMenuItem[] = [];
|
||||
const settings = globalStore.get(atoms.settingsConfigAtom);
|
||||
console.log("settings", settings);
|
||||
const fullConfig = globalStore.get(atoms.fullConfigAtom);
|
||||
const bgPresets: string[] = [];
|
||||
for (const key in settings?.presets ?? {}) {
|
||||
for (const key in fullConfig?.presets ?? {}) {
|
||||
if (key.startsWith("bg@")) {
|
||||
bgPresets.push(key);
|
||||
}
|
||||
}
|
||||
bgPresets.sort((a, b) => {
|
||||
const aOrder = settings.presets[a]["display:order"] ?? 0;
|
||||
const bOrder = settings.presets[b]["display:order"] ?? 0;
|
||||
const aOrder = fullConfig.presets[a]["display:order"] ?? 0;
|
||||
const bOrder = fullConfig.presets[b]["display:order"] ?? 0;
|
||||
return aOrder - bOrder;
|
||||
});
|
||||
console.log("bgPresets", bgPresets);
|
||||
menu.push({ label: "Copy TabId", click: () => navigator.clipboard.writeText(id) });
|
||||
menu.push({ type: "separator" });
|
||||
if (bgPresets.length > 0) {
|
||||
const submenu: ContextMenuItem[] = [];
|
||||
const oref = WOS.makeORef("tab", id);
|
||||
for (const presetName of bgPresets) {
|
||||
const preset = settings.presets[presetName];
|
||||
const preset = fullConfig.presets[presetName];
|
||||
if (preset == null) {
|
||||
continue;
|
||||
}
|
||||
submenu.push({
|
||||
label: preset["display:name"] ?? presetName,
|
||||
click: () => services.ObjectService.UpdateObjectMeta(oref, preset),
|
||||
click: () => {
|
||||
services.ObjectService.UpdateObjectMeta(oref, preset);
|
||||
},
|
||||
});
|
||||
}
|
||||
menu.push({ label: "Backgrounds", type: "submenu", submenu });
|
||||
|
||||
@@ -134,11 +134,11 @@ function DirectoryTable({
|
||||
setSelectedPath,
|
||||
setRefreshVersion,
|
||||
}: DirectoryTableProps) {
|
||||
const settings = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||
const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom);
|
||||
const getIconFromMimeType = useCallback(
|
||||
(mimeType: string): string => {
|
||||
while (mimeType.length > 0) {
|
||||
let icon = settings.mimetypes?.[mimeType]?.icon ?? null;
|
||||
let icon = fullConfig.mimetypes?.[mimeType]?.icon ?? null;
|
||||
if (isIconValid(icon)) {
|
||||
return `fa fa-solid fa-${icon} fa-fw`;
|
||||
}
|
||||
@@ -146,14 +146,14 @@ function DirectoryTable({
|
||||
}
|
||||
return "fa fa-solid fa-file fa-fw";
|
||||
},
|
||||
[settings.mimetypes]
|
||||
[fullConfig.mimetypes]
|
||||
);
|
||||
const getIconColor = useCallback(
|
||||
(mimeType: string): string => {
|
||||
let iconColor = settings.mimetypes?.[mimeType]?.color ?? "inherit";
|
||||
let iconColor = fullConfig.mimetypes?.[mimeType]?.color ?? "inherit";
|
||||
return iconColor;
|
||||
},
|
||||
[settings.mimetypes]
|
||||
[fullConfig.mimetypes]
|
||||
);
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
@@ -208,7 +208,7 @@ function DirectoryTable({
|
||||
}),
|
||||
columnHelper.accessor("path", {}),
|
||||
],
|
||||
[settings]
|
||||
[fullConfig]
|
||||
);
|
||||
|
||||
const table = useReactTable({
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { WshServer } from "@/app/store/wshserver";
|
||||
import { VDomView } from "@/app/view/term/vdom";
|
||||
import { WOS, atoms, getEventORefSubject, globalStore, useBlockAtom, useSettingsAtom } from "@/store/global";
|
||||
import { WOS, atoms, getEventORefSubject, globalStore, useBlockAtom, useSettingsPrefixAtom } from "@/store/global";
|
||||
import * as services from "@/store/services";
|
||||
import * as keyutil from "@/util/keyutil";
|
||||
import * as util from "@/util/util";
|
||||
@@ -135,8 +135,8 @@ class TermViewModel {
|
||||
});
|
||||
this.blockBg = jotai.atom((get) => {
|
||||
const blockData = get(this.blockAtom);
|
||||
const settings = globalStore.get(atoms.settingsConfigAtom);
|
||||
const theme = computeTheme(settings, blockData?.meta?.["term:theme"]);
|
||||
const fullConfig = get(atoms.fullConfigAtom);
|
||||
const theme = computeTheme(fullConfig, blockData?.meta?.["term:theme"]);
|
||||
if (theme != null && theme.background != null) {
|
||||
return { bg: theme.background };
|
||||
}
|
||||
@@ -203,9 +203,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
||||
const htmlElemFocusRef = React.useRef<HTMLInputElement>(null);
|
||||
model.htmlElemFocusRef = htmlElemFocusRef;
|
||||
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
||||
const termSettingsAtom = useSettingsAtom<TerminalConfigType>("term", (settings: SettingsConfigType) => {
|
||||
return settings?.term;
|
||||
});
|
||||
const termSettingsAtom = useSettingsPrefixAtom("term");
|
||||
const termSettings = jotai.useAtomValue(termSettingsAtom);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -240,8 +238,8 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const settings = globalStore.get(atoms.settingsConfigAtom);
|
||||
const termTheme = computeTheme(settings, blockData?.meta?.["term:theme"]);
|
||||
const fullConfig = globalStore.get(atoms.fullConfigAtom);
|
||||
const termTheme = computeTheme(fullConfig, blockData?.meta?.["term:theme"]);
|
||||
const themeCopy = { ...termTheme };
|
||||
themeCopy.background = "#00000000";
|
||||
const termWrap = new TermWrap(
|
||||
@@ -249,8 +247,8 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
||||
connectElemRef.current,
|
||||
{
|
||||
theme: themeCopy,
|
||||
fontSize: termSettings?.fontsize ?? 12,
|
||||
fontFamily: termSettings?.fontfamily ?? "Hack",
|
||||
fontSize: termSettings?.["term:fontsize"] ?? 12,
|
||||
fontFamily: termSettings?.["term:fontfamily"] ?? "Hack",
|
||||
drawBoldTextInBrightColors: false,
|
||||
fontWeight: "normal",
|
||||
fontWeightBold: "bold",
|
||||
@@ -258,7 +256,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
||||
},
|
||||
{
|
||||
keydownHandler: handleTerminalKeydown,
|
||||
useWebGl: !termSettings?.disablewebgl,
|
||||
useWebGl: !termSettings?.["term:disablewebgl"],
|
||||
}
|
||||
);
|
||||
(window as any).term = termWrap;
|
||||
|
||||
@@ -13,7 +13,8 @@ interface TermThemeProps {
|
||||
}
|
||||
|
||||
const TermThemeUpdater = ({ blockId, termRef }: TermThemeProps) => {
|
||||
const { termthemes } = useAtomValue(atoms.settingsConfigAtom);
|
||||
const fullConfig = useAtomValue(atoms.fullConfigAtom);
|
||||
const termthemes = fullConfig?.termthemes;
|
||||
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
||||
let defaultThemeName = "default-dark";
|
||||
let themeName = blockData.meta?.["term:theme"] ?? "default-dark";
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
import * as util from "@/util/util";
|
||||
|
||||
function computeTheme(settings: SettingsConfigType, themeName: string): TermThemeType {
|
||||
function computeTheme(fullConfig: FullConfigType, 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 defaultTheme: TermThemeType = fullConfig?.termthemes?.[defaultThemeName] || ({} as any);
|
||||
const theme: TermThemeType = fullConfig?.termthemes?.[themeName] || ({} as any);
|
||||
const combinedTheme = { ...defaultTheme };
|
||||
for (const key in theme) {
|
||||
if (!util.isBlank(theme[key])) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { Markdown } from "@/app/element/markdown";
|
||||
import { TypingIndicator } from "@/app/element/typingindicator";
|
||||
import { WOS, atoms, fetchWaveFile, globalStore } from "@/store/global";
|
||||
import { WOS, atoms, fetchWaveFile, getUserName, globalStore } from "@/store/global";
|
||||
import * as services from "@/store/services";
|
||||
import { WshServer } from "@/store/wshserver";
|
||||
import * as jotai from "jotai";
|
||||
@@ -107,7 +107,7 @@ export class WaveAiModel implements ViewModel {
|
||||
const viewTextChildren: HeaderElem[] = [
|
||||
{
|
||||
elemtype: "text",
|
||||
text: get(atoms.settingsConfigAtom).ai?.model ?? "gpt-3.5-turbo",
|
||||
text: get(atoms.settingsAtom)["ai:model"] ?? "gpt-3.5-turbo",
|
||||
},
|
||||
];
|
||||
return viewTextChildren;
|
||||
@@ -152,18 +152,21 @@ export class WaveAiModel implements ViewModel {
|
||||
};
|
||||
addMessage(newMessage);
|
||||
// send message to backend and get response
|
||||
const settings = globalStore.get(atoms.settingsConfigAtom);
|
||||
const settings = globalStore.get(atoms.settingsAtom);
|
||||
const opts: OpenAIOptsType = {
|
||||
model: settings.ai.model,
|
||||
apitoken: settings.ai.apitoken,
|
||||
maxtokens: settings.ai.maxtokens,
|
||||
timeout: settings.ai.timeoutms / 1000,
|
||||
baseurl: settings.ai.baseurl,
|
||||
model: settings["ai:model"],
|
||||
apitoken: settings["ai:apitoken"],
|
||||
maxtokens: settings["ai:maxtokens"],
|
||||
timeout: settings["ai:timeoutms"] / 1000,
|
||||
baseurl: settings["ai:baseurl"],
|
||||
};
|
||||
const newPrompt: OpenAIPromptMessageType = {
|
||||
role: "user",
|
||||
content: text,
|
||||
};
|
||||
if (newPrompt.name == "*username") {
|
||||
newPrompt.name = getUserName();
|
||||
}
|
||||
let temp = async () => {
|
||||
const history = await this.fetchAiData();
|
||||
const beMsg: OpenAiStreamRequest = {
|
||||
|
||||
@@ -14,10 +14,28 @@ import "./workspace.less";
|
||||
|
||||
const iconRegex = /^[a-z0-9-]+$/;
|
||||
|
||||
function keyLen(obj: Object): number {
|
||||
if (obj == null) {
|
||||
return 0;
|
||||
}
|
||||
return Object.keys(obj).length;
|
||||
}
|
||||
|
||||
function sortByDisplayOrder(wmap: { [key: string]: WidgetConfigType }): WidgetConfigType[] {
|
||||
if (wmap == null) {
|
||||
return [];
|
||||
}
|
||||
const wlist = Object.values(wmap);
|
||||
wlist.sort((a, b) => {
|
||||
return a["display:order"] - b["display:order"];
|
||||
});
|
||||
return wlist;
|
||||
}
|
||||
|
||||
const Widgets = React.memo(() => {
|
||||
const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||
const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom);
|
||||
const newWidgetModalVisible = React.useState(false);
|
||||
const helpWidget: WidgetsConfigType = {
|
||||
const helpWidget: WidgetConfigType = {
|
||||
icon: "circle-question",
|
||||
label: "help",
|
||||
blockdef: {
|
||||
@@ -26,13 +44,17 @@ const Widgets = React.memo(() => {
|
||||
},
|
||||
},
|
||||
};
|
||||
const showHelp = settingsConfig?.["widget:showhelp"] ?? true;
|
||||
const showDivider = settingsConfig?.defaultwidgets?.length > 0 && settingsConfig?.widgets?.length > 0;
|
||||
const showHelp = fullConfig?.settings?.["widget:showhelp"] ?? true;
|
||||
const showDivider = keyLen(fullConfig?.defaultwidgets) > 0 && keyLen(fullConfig?.widgets) > 0;
|
||||
const defaultWidgets = sortByDisplayOrder(fullConfig?.defaultwidgets);
|
||||
const widgets = sortByDisplayOrder(fullConfig?.widgets);
|
||||
return (
|
||||
<div className="workspace-widgets">
|
||||
{settingsConfig?.defaultwidgets?.map((data, idx) => <Widget key={`defwidget-${idx}`} widget={data} />)}
|
||||
{defaultWidgets.map((data, idx) => (
|
||||
<Widget key={`defwidget-${idx}`} widget={data} />
|
||||
))}
|
||||
{showDivider ? <div className="widget-divider" /> : null}
|
||||
{settingsConfig?.widgets?.map((data, idx) => <Widget key={`widget-${idx}`} widget={data} />)}
|
||||
{widgets?.map((data, idx) => <Widget key={`widget-${idx}`} widget={data} />)}
|
||||
{showHelp ? (
|
||||
<>
|
||||
<div className="widget-spacer" />
|
||||
@@ -61,7 +83,7 @@ function getIconClass(icon: string): string {
|
||||
return `fa fa-solid fa-${icon} fa-fw`;
|
||||
}
|
||||
|
||||
const Widget = React.memo(({ widget }: { widget: WidgetsConfigType }) => {
|
||||
const Widget = React.memo(({ widget }: { widget: WidgetConfigType }) => {
|
||||
return (
|
||||
<div
|
||||
className="widget"
|
||||
|
||||
Vendored
+3
-3
@@ -12,7 +12,8 @@ declare global {
|
||||
uiContext: jotai.Atom<UIContext>; // driven from windowId, activetabid, etc.
|
||||
waveWindow: jotai.Atom<WaveWindow>; // driven from WOS
|
||||
workspace: jotai.Atom<Workspace>; // driven from WOS
|
||||
settingsConfigAtom: jotai.PrimitiveAtom<SettingsConfigType>; // driven from WOS, settings -- updated via WebSocket
|
||||
fullConfigAtom: jotai.PrimitiveAtom<FullConfigType>; // driven from WOS, settings -- updated via WebSocket
|
||||
settingsAtom: jotai.Atom<SettingsType>; // derrived from fullConfig
|
||||
tabAtom: jotai.Atom<Tab>; // driven from WOS
|
||||
activeTabId: jotai.Atom<string>; // derrived from windowDataAtom
|
||||
isFullScreen: jotai.PrimitiveAtom<boolean>;
|
||||
@@ -49,10 +50,9 @@ declare global {
|
||||
getAuthKey(): string;
|
||||
getIsDev(): boolean;
|
||||
getCursorPoint: () => Electron.Point;
|
||||
|
||||
getPlatform: () => NodeJS.Platform;
|
||||
getEnv: (varName: string) => string;
|
||||
|
||||
getUserName: () => string;
|
||||
showContextMenu: (menu?: ElectronContextMenuItem[]) => void;
|
||||
onContextMenuClick: (callback: (id: string) => void) => void;
|
||||
onNavigate: (callback: (url: string) => void) => void;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user