Skip to content

Commit 134b388

Browse files
Copilotsawka
andauthored
Create WaveEnv narrowing for WaveConfig View (#3068)
Create a WaveEnv narrowing for the waveconfig view. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
1 parent 3099efd commit 134b388

File tree

3 files changed

+110
-76
lines changed

3 files changed

+110
-76
lines changed

frontend/app/view/waveconfig/waveconfig-model.ts

Lines changed: 79 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { BlockNodeModel } from "@/app/block/blocktypes";
5-
import { atoms, getApi, getBlockMetaKeyAtom, WOS } from "@/app/store/global";
65
import { globalStore } from "@/app/store/jotaiStore";
76
import type { TabModel } from "@/app/store/tab-model";
8-
import { RpcApi } from "@/app/store/wshclientapi";
7+
import { makeORef } from "@/app/store/wos";
98
import { TabRpcClient } from "@/app/store/wshrpcutil";
109
import { SecretsContent } from "@/app/view/waveconfig/secretscontent";
1110
import { WaveConfigView } from "@/app/view/waveconfig/waveconfig";
12-
import { isWindows } from "@/util/platformutil";
11+
import type { WaveConfigEnv } from "@/app/view/waveconfig/waveconfigenv";
1312
import { base64ToString, stringToBase64 } from "@/util/util";
1413
import { atom, type Atom, type PrimitiveAtom } from "jotai";
1514
import type * as MonacoTypes from "monaco-editor";
@@ -66,55 +65,57 @@ function validateWaveAiJson(parsed: any): ValidationResult {
6665
return { success: true };
6766
}
6867

69-
const configFiles: ConfigFile[] = [
70-
{
71-
name: "General",
72-
path: "settings.json",
73-
language: "json",
74-
docsUrl: "https://docs.waveterm.dev/config",
75-
hasJsonView: true,
76-
},
77-
{
78-
name: "Connections",
79-
path: "connections.json",
80-
language: "json",
81-
docsUrl: "https://docs.waveterm.dev/connections",
82-
description: isWindows() ? "SSH hosts and WSL distros" : "SSH hosts",
83-
hasJsonView: true,
84-
},
85-
{
86-
name: "Sidebar Widgets",
87-
path: "widgets.json",
88-
language: "json",
89-
docsUrl: "https://docs.waveterm.dev/customwidgets",
90-
hasJsonView: true,
91-
},
92-
{
93-
name: "Wave AI Modes",
94-
path: "waveai.json",
95-
language: "json",
96-
description: "Local models and BYOK",
97-
docsUrl: "https://docs.waveterm.dev/waveai-modes",
98-
validator: validateWaveAiJson,
99-
hasJsonView: true,
100-
// visualComponent: WaveAIVisualContent,
101-
},
102-
{
103-
name: "Tab Backgrounds",
104-
path: "presets/bg.json",
105-
language: "json",
106-
docsUrl: "https://docs.waveterm.dev/presets#background-configurations",
107-
validator: validateBgJson,
108-
hasJsonView: true,
109-
},
110-
{
111-
name: "Secrets",
112-
path: "secrets",
113-
isSecrets: true,
114-
hasJsonView: false,
115-
visualComponent: SecretsContent,
116-
},
117-
];
68+
function makeConfigFiles(isWindows: boolean): ConfigFile[] {
69+
return [
70+
{
71+
name: "General",
72+
path: "settings.json",
73+
language: "json",
74+
docsUrl: "https://docs.waveterm.dev/config",
75+
hasJsonView: true,
76+
},
77+
{
78+
name: "Connections",
79+
path: "connections.json",
80+
language: "json",
81+
docsUrl: "https://docs.waveterm.dev/connections",
82+
description: isWindows ? "SSH hosts and WSL distros" : "SSH hosts",
83+
hasJsonView: true,
84+
},
85+
{
86+
name: "Sidebar Widgets",
87+
path: "widgets.json",
88+
language: "json",
89+
docsUrl: "https://docs.waveterm.dev/customwidgets",
90+
hasJsonView: true,
91+
},
92+
{
93+
name: "Wave AI Modes",
94+
path: "waveai.json",
95+
language: "json",
96+
description: "Local models and BYOK",
97+
docsUrl: "https://docs.waveterm.dev/waveai-modes",
98+
validator: validateWaveAiJson,
99+
hasJsonView: true,
100+
// visualComponent: WaveAIVisualContent,
101+
},
102+
{
103+
name: "Tab Backgrounds",
104+
path: "presets/bg.json",
105+
language: "json",
106+
docsUrl: "https://docs.waveterm.dev/presets#background-configurations",
107+
validator: validateBgJson,
108+
hasJsonView: true,
109+
},
110+
{
111+
name: "Secrets",
112+
path: "secrets",
113+
isSecrets: true,
114+
hasJsonView: false,
115+
visualComponent: SecretsContent,
116+
},
117+
];
118+
}
118119

119120
const deprecatedConfigFiles: ConfigFile[] = [
120121
{
@@ -144,6 +145,7 @@ export class WaveConfigViewModel implements ViewModel {
144145
noPadding = atom(true);
145146
nodeModel: BlockNodeModel;
146147
tabModel: TabModel;
148+
env: WaveConfigEnv;
147149

148150
selectedFileAtom: PrimitiveAtom<ConfigFile>;
149151
fileContentAtom: PrimitiveAtom<string>;
@@ -171,12 +173,13 @@ export class WaveConfigViewModel implements ViewModel {
171173
storageBackendErrorAtom: PrimitiveAtom<string | null>;
172174
secretValueRef: HTMLTextAreaElement | null = null;
173175

174-
constructor({ blockId, nodeModel, tabModel }: ViewModelInitType) {
176+
constructor({ blockId, nodeModel, tabModel, waveEnv }: ViewModelInitType) {
175177
this.blockId = blockId;
176178
this.nodeModel = nodeModel;
177179
this.tabModel = tabModel;
178-
this.configDir = getApi().getConfigDir();
179-
const platform = getApi().getPlatform();
180+
this.env = waveEnv as WaveConfigEnv;
181+
this.configDir = this.env.electron.getConfigDir();
182+
const platform = this.env.electron.getPlatform();
180183
this.saveShortcut = platform === "darwin" ? "Cmd+S" : "Alt+S";
181184

182185
this.selectedFileAtom = atom(null) as PrimitiveAtom<ConfigFile>;
@@ -191,7 +194,7 @@ export class WaveConfigViewModel implements ViewModel {
191194
this.presetsJsonExistsAtom = atom(false);
192195
this.activeTabAtom = atom<"visual" | "json">("visual");
193196
this.configErrorFilesAtom = atom((get) => {
194-
const fullConfig = get(atoms.fullConfigAtom);
197+
const fullConfig = get(this.env.atoms.fullConfigAtom);
195198
const errorSet = new Set<string>();
196199
for (const cerr of fullConfig?.configerrors ?? []) {
197200
errorSet.add(cerr.file);
@@ -216,7 +219,7 @@ export class WaveConfigViewModel implements ViewModel {
216219
async checkPresetsJsonExists() {
217220
try {
218221
const fullPath = `${this.configDir}/presets.json`;
219-
const fileInfo = await RpcApi.FileInfoCommand(TabRpcClient, {
222+
const fileInfo = await this.env.rpc.FileInfoCommand(TabRpcClient, {
220223
info: { path: fullPath },
221224
});
222225
if (!fileInfo.notfound) {
@@ -230,8 +233,10 @@ export class WaveConfigViewModel implements ViewModel {
230233
initialize() {
231234
const selectedFile = globalStore.get(this.selectedFileAtom);
232235
if (!selectedFile) {
233-
const metaFileAtom = getBlockMetaKeyAtom(this.blockId, "file");
236+
const metaFileAtom = this.env.getBlockMetaKeyAtom(this.blockId, "file");
234237
const savedFilePath = globalStore.get(metaFileAtom);
238+
const configFiles = this.getConfigFiles();
239+
const deprecatedConfigFiles = this.getDeprecatedConfigFiles();
235240

236241
let fileToLoad: ConfigFile | null = null;
237242
if (savedFilePath) {
@@ -252,7 +257,7 @@ export class WaveConfigViewModel implements ViewModel {
252257
}
253258

254259
getConfigFiles(): ConfigFile[] {
255-
return configFiles;
260+
return makeConfigFiles(this.env.isWindows());
256261
}
257262

258263
getDeprecatedConfigFiles(): ConfigFile[] {
@@ -280,8 +285,8 @@ export class WaveConfigViewModel implements ViewModel {
280285

281286
if (file.isSecrets) {
282287
globalStore.set(this.selectedFileAtom, file);
283-
RpcApi.SetMetaCommand(TabRpcClient, {
284-
oref: WOS.makeORef("block", this.blockId),
288+
this.env.rpc.SetMetaCommand(TabRpcClient, {
289+
oref: makeORef("block", this.blockId),
285290
meta: { file: file.path },
286291
});
287292
globalStore.set(this.isLoadingAtom, false);
@@ -292,7 +297,7 @@ export class WaveConfigViewModel implements ViewModel {
292297

293298
try {
294299
const fullPath = `${this.configDir}/${file.path}`;
295-
const fileData = await RpcApi.FileReadCommand(TabRpcClient, {
300+
const fileData = await this.env.rpc.FileReadCommand(TabRpcClient, {
296301
info: { path: fullPath },
297302
});
298303
const content = fileData?.data64 ? base64ToString(fileData.data64) : "";
@@ -303,8 +308,8 @@ export class WaveConfigViewModel implements ViewModel {
303308
globalStore.set(this.fileContentAtom, content);
304309
}
305310
globalStore.set(this.selectedFileAtom, file);
306-
RpcApi.SetMetaCommand(TabRpcClient, {
307-
oref: WOS.makeORef("block", this.blockId),
311+
this.env.rpc.SetMetaCommand(TabRpcClient, {
312+
oref: makeORef("block", this.blockId),
308313
meta: { file: file.path },
309314
});
310315
} catch (err) {
@@ -329,7 +334,7 @@ export class WaveConfigViewModel implements ViewModel {
329334

330335
try {
331336
const fullPath = `${this.configDir}/${selectedFile.path}`;
332-
await RpcApi.FileWriteCommand(TabRpcClient, {
337+
await this.env.rpc.FileWriteCommand(TabRpcClient, {
333338
info: { path: fullPath },
334339
data64: stringToBase64(""),
335340
});
@@ -371,7 +376,7 @@ export class WaveConfigViewModel implements ViewModel {
371376

372377
try {
373378
const fullPath = `${this.configDir}/${selectedFile.path}`;
374-
await RpcApi.FileWriteCommand(TabRpcClient, {
379+
await this.env.rpc.FileWriteCommand(TabRpcClient, {
375380
info: { path: fullPath },
376381
data64: stringToBase64(formatted),
377382
});
@@ -401,7 +406,7 @@ export class WaveConfigViewModel implements ViewModel {
401406

402407
async checkStorageBackend() {
403408
try {
404-
const backend = await RpcApi.GetSecretsLinuxStorageBackendCommand(TabRpcClient);
409+
const backend = await this.env.rpc.GetSecretsLinuxStorageBackendCommand(TabRpcClient);
405410
if (backend === "basic_text" || backend === "unknown") {
406411
globalStore.set(
407412
this.storageBackendErrorAtom,
@@ -420,7 +425,7 @@ export class WaveConfigViewModel implements ViewModel {
420425
globalStore.set(this.errorMessageAtom, null);
421426

422427
try {
423-
const names = await RpcApi.GetSecretsNamesCommand(TabRpcClient);
428+
const names = await this.env.rpc.GetSecretsNamesCommand(TabRpcClient);
424429
globalStore.set(this.secretNamesAtom, names || []);
425430
} catch (error) {
426431
globalStore.set(this.errorMessageAtom, `Failed to load secrets: ${error.message}`);
@@ -452,7 +457,7 @@ export class WaveConfigViewModel implements ViewModel {
452457
globalStore.set(this.errorMessageAtom, null);
453458

454459
try {
455-
const secrets = await RpcApi.GetSecretsCommand(TabRpcClient, [selectedSecret]);
460+
const secrets = await this.env.rpc.GetSecretsCommand(TabRpcClient, [selectedSecret]);
456461
const value = secrets[selectedSecret];
457462
if (value !== undefined) {
458463
globalStore.set(this.secretValueAtom, value);
@@ -479,8 +484,8 @@ export class WaveConfigViewModel implements ViewModel {
479484
globalStore.set(this.errorMessageAtom, null);
480485

481486
try {
482-
await RpcApi.SetSecretsCommand(TabRpcClient, { [selectedSecret]: secretValue });
483-
RpcApi.RecordTEventCommand(
487+
await this.env.rpc.SetSecretsCommand(TabRpcClient, { [selectedSecret]: secretValue });
488+
this.env.rpc.RecordTEventCommand(
484489
TabRpcClient,
485490
{
486491
event: "action:other",
@@ -509,7 +514,7 @@ export class WaveConfigViewModel implements ViewModel {
509514
globalStore.set(this.errorMessageAtom, null);
510515

511516
try {
512-
await RpcApi.SetSecretsCommand(TabRpcClient, { [selectedSecret]: null });
517+
await this.env.rpc.SetSecretsCommand(TabRpcClient, { [selectedSecret]: null });
513518
this.closeSecretView();
514519
await this.refreshSecrets();
515520
} catch (error) {
@@ -560,8 +565,8 @@ export class WaveConfigViewModel implements ViewModel {
560565
globalStore.set(this.errorMessageAtom, null);
561566

562567
try {
563-
await RpcApi.SetSecretsCommand(TabRpcClient, { [name]: value });
564-
RpcApi.RecordTEventCommand(
568+
await this.env.rpc.SetSecretsCommand(TabRpcClient, { [name]: value });
569+
this.env.rpc.RecordTEventCommand(
565570
TabRpcClient,
566571
{
567572
event: "action:other",

frontend/app/view/waveconfig/waveconfig.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { Tooltip } from "@/app/element/tooltip";
5-
import { atoms } from "@/app/store/global";
65
import { globalStore } from "@/app/store/jotaiStore";
76
import { tryReinjectKey } from "@/app/store/keymodel";
87
import { CodeEditor } from "@/app/view/codeeditor/codeeditor";
98
import type { ConfigFile, WaveConfigViewModel } from "@/app/view/waveconfig/waveconfig-model";
9+
import type { WaveConfigEnv } from "@/app/view/waveconfig/waveconfigenv";
10+
import { useWaveEnv } from "@/app/waveenv/waveenv";
1011
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil";
1112
import { cn } from "@/util/util";
1213
import { useAtom, useAtomValue } from "jotai";
@@ -97,6 +98,7 @@ const ConfigSidebar = memo(({ model }: ConfigSidebarProps) => {
9798
ConfigSidebar.displayName = "ConfigSidebar";
9899

99100
const WaveConfigView = memo(({ blockId, model }: ViewComponentProps<WaveConfigViewModel>) => {
101+
const env = useWaveEnv<WaveConfigEnv>();
100102
const selectedFile = useAtomValue(model.selectedFileAtom);
101103
const [fileContent, setFileContent] = useAtom(model.fileContentAtom);
102104
const isLoading = useAtomValue(model.isLoadingAtom);
@@ -106,7 +108,7 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps<WaveConfigVi
106108
const [isMenuOpen, setIsMenuOpen] = useAtom(model.isMenuOpenAtom);
107109
const hasChanges = useAtomValue(model.hasEditedAtom);
108110
const [activeTab, setActiveTab] = useAtom(model.activeTabAtom);
109-
const fullConfig = useAtomValue(atoms.fullConfigAtom);
111+
const fullConfig = useAtomValue(env.atoms.fullConfigAtom);
110112
const configErrors = fullConfig?.configerrors;
111113

112114
const handleContentChange = useCallback(
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2026, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import type { BlockMetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
5+
6+
export type WaveConfigEnv = WaveEnvSubset<{
7+
electron: {
8+
getConfigDir: WaveEnv["electron"]["getConfigDir"];
9+
getPlatform: WaveEnv["electron"]["getPlatform"];
10+
};
11+
rpc: {
12+
FileInfoCommand: WaveEnv["rpc"]["FileInfoCommand"];
13+
FileReadCommand: WaveEnv["rpc"]["FileReadCommand"];
14+
FileWriteCommand: WaveEnv["rpc"]["FileWriteCommand"];
15+
SetMetaCommand: WaveEnv["rpc"]["SetMetaCommand"];
16+
GetSecretsLinuxStorageBackendCommand: WaveEnv["rpc"]["GetSecretsLinuxStorageBackendCommand"];
17+
GetSecretsNamesCommand: WaveEnv["rpc"]["GetSecretsNamesCommand"];
18+
GetSecretsCommand: WaveEnv["rpc"]["GetSecretsCommand"];
19+
SetSecretsCommand: WaveEnv["rpc"]["SetSecretsCommand"];
20+
RecordTEventCommand: WaveEnv["rpc"]["RecordTEventCommand"];
21+
};
22+
atoms: {
23+
fullConfigAtom: WaveEnv["atoms"]["fullConfigAtom"];
24+
};
25+
getBlockMetaKeyAtom: BlockMetaKeyAtomFnType<"file">;
26+
isWindows: WaveEnv["isWindows"];
27+
}>;

0 commit comments

Comments
 (0)