Skip to content

Commit fc5bdf5

Browse files
committed
feat: implement global text search widget and resolve block view suspense loading and directory tree preview issues
1 parent 285a77f commit fc5bdf5

25 files changed

Lines changed: 1382 additions & 727 deletions

File tree

electron.vite.config.ts

Lines changed: 29 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,70 +4,24 @@
44
import tailwindcss from "@tailwindcss/vite";
55
import react from "@vitejs/plugin-react-swc";
66
import { defineConfig } from "electron-vite";
7+
import path from "path";
78
import { ViteImageOptimizer } from "vite-plugin-image-optimizer";
89
import svgr from "vite-plugin-svgr";
910
import tsconfigPaths from "vite-tsconfig-paths";
1011

1112
// from our electron build
1213
const CHROME = "chrome140";
1314
const NODE = "node22";
15+
const MERMAID_CORE = path.resolve(__dirname, "node_modules/mermaid/dist/mermaid.core.mjs");
1416

15-
// for debugging
16-
// target is like -- path.resolve(__dirname, "frontend/app/workspace/workspace-layout-model.ts");
17-
function whoImportsTarget(target: string) {
17+
function streamdownMermaidFix() {
1818
return {
19-
name: "who-imports-target",
20-
buildEnd() {
21-
// Build reverse graph: child -> [importers...]
22-
const parents = new Map<string, string[]>();
23-
for (const id of (this as any).getModuleIds()) {
24-
const info = (this as any).getModuleInfo(id);
25-
if (!info) continue;
26-
for (const child of [...info.importedIds, ...info.dynamicallyImportedIds]) {
27-
const arr = parents.get(child) ?? [];
28-
arr.push(id);
29-
parents.set(child, arr);
30-
}
31-
}
32-
33-
// Walk upward from TARGET and print paths to entries
34-
const entries = [...parents.keys()].filter((id) => {
35-
const m = (this as any).getModuleInfo(id);
36-
return m?.isEntry;
37-
});
38-
39-
const seen = new Set<string>();
40-
const stack: string[] = [];
41-
const dfs = (node: string) => {
42-
if (seen.has(node)) return;
43-
seen.add(node);
44-
stack.push(node);
45-
const ps = parents.get(node) || [];
46-
if (ps.length === 0) {
47-
// hit a root (likely main entry or plugin virtual)
48-
console.log("\nImporter chain:");
49-
stack
50-
.slice()
51-
.reverse()
52-
.forEach((s) => console.log(" ↳", s));
53-
} else {
54-
for (const p of ps) dfs(p);
55-
}
56-
stack.pop();
57-
};
58-
59-
if (!parents.has(target)) {
60-
console.log(`[who-imports] TARGET not in MAIN graph: ${target}`);
61-
} else {
62-
dfs(target);
63-
}
64-
},
65-
async resolveId(id: any, importer: any) {
66-
const r = await (this as any).resolve(id, importer, { skipSelf: true });
67-
if (r?.id === target) {
68-
console.log(`[resolve] ${importer} -> ${id} -> ${r.id}`);
69-
}
70-
return null;
19+
name: "streamdown-mermaid-fix",
20+
enforce: "pre" as const,
21+
transform(code: string, id: string) {
22+
if (!id.includes("node_modules/streamdown")) return null;
23+
if (!code.includes("import('mermaid')")) return null;
24+
return code.replaceAll("import('mermaid')", `import('/@fs${MERMAID_CORE}')`);
7125
},
7226
};
7327
}
@@ -89,6 +43,7 @@ export default defineConfig({
8943
resolve: {
9044
alias: {
9145
"@": "frontend",
46+
mermaid: path.resolve(__dirname, "node_modules/mermaid/dist/mermaid.js"),
9247
},
9348
},
9449
server: {
@@ -147,8 +102,25 @@ export default defineConfig({
147102
},
148103
},
149104
},
105+
resolve: {
106+
alias: {
107+
"style-to-js$": path.resolve(__dirname, "frontend/style-to-js-compat.ts"),
108+
"extend$": path.resolve(__dirname, "frontend/extend-compat.ts"),
109+
},
110+
},
150111
optimizeDeps: {
151-
include: ["monaco-yaml/yaml.worker.js"],
112+
include: [
113+
"monaco-yaml/yaml.worker.js",
114+
"style-to-js",
115+
"style-to-object",
116+
"react-markdown",
117+
"rehype-raw",
118+
"rehype-sanitize",
119+
"hast-util-to-estree",
120+
"hast-util-to-jsx-runtime",
121+
"extend"
122+
],
123+
exclude: ["mermaid", "streamdown"],
152124
},
153125
server: {
154126
open: false,
@@ -176,6 +148,7 @@ export default defineConfig({
176148
},
177149
plugins: [
178150
tsconfigPaths(),
151+
streamdownMermaidFix(),
179152
{ ...ViteImageOptimizer(), apply: "build" },
180153
svgr({
181154
svgrOptions: { exportType: "default", ref: true, svgo: false, titleProp: true },

frontend/app/block/block.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -273,17 +273,22 @@ const BlockInner = memo((props: BlockProps & { viewType: string }) => {
273273
const waveEnv = useWaveEnv();
274274
const bcm = getBlockComponentModel(props.nodeModel.blockId);
275275
let viewModel = bcm?.viewModel;
276-
if (viewModel == null) {
277-
// viewModel gets the full waveEnv
276+
if (viewModel == null || viewModel.viewType !== props.viewType) {
277+
if (viewModel != null) {
278+
viewModel.dispose?.();
279+
}
278280
viewModel = makeViewModel(props.nodeModel.blockId, props.viewType, props.nodeModel, tabModel, waveEnv);
279281
registerBlockComponentModel(props.nodeModel.blockId, { viewModel });
280282
}
281283
useEffect(() => {
282284
return () => {
283-
unregisterBlockComponentModel(props.nodeModel.blockId);
285+
const currentBcm = getBlockComponentModel(props.nodeModel.blockId);
286+
if (currentBcm?.viewModel === viewModel) {
287+
unregisterBlockComponentModel(props.nodeModel.blockId);
288+
}
284289
viewModel?.dispose?.();
285290
};
286-
}, []);
291+
}, [viewModel]);
287292
if (props.preview) {
288293
return <BlockPreview {...props} viewModel={viewModel} />;
289294
}
@@ -308,17 +313,22 @@ const SubBlockInner = memo((props: SubBlockProps & { viewType: string }) => {
308313
const waveEnv = useWaveEnv();
309314
const bcm = getBlockComponentModel(props.nodeModel.blockId);
310315
let viewModel = bcm?.viewModel;
311-
if (viewModel == null) {
312-
// viewModel gets the full waveEnv
316+
if (viewModel == null || viewModel.viewType !== props.viewType) {
317+
if (viewModel != null) {
318+
viewModel.dispose?.();
319+
}
313320
viewModel = makeViewModel(props.nodeModel.blockId, props.viewType, props.nodeModel, tabModel, waveEnv);
314321
registerBlockComponentModel(props.nodeModel.blockId, { viewModel });
315322
}
316323
useEffect(() => {
317324
return () => {
318-
unregisterBlockComponentModel(props.nodeModel.blockId);
325+
const currentBcm = getBlockComponentModel(props.nodeModel.blockId);
326+
if (currentBcm?.viewModel === viewModel) {
327+
unregisterBlockComponentModel(props.nodeModel.blockId);
328+
}
319329
viewModel?.dispose?.();
320330
};
321-
}, []);
331+
}, [viewModel]);
322332
return <BlockSubBlock {...props} viewModel={viewModel} />;
323333
});
324334
SubBlockInner.displayName = "SubBlockInner";

frontend/app/block/blockregistry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AiFileDiffViewModel } from "@/app/view/aifilediff/aifilediff";
77
import { LauncherViewModel } from "@/app/view/launcher/launcher";
88
import { PreviewModel } from "@/app/view/preview/preview-model";
99
import { ProcessViewerViewModel } from "@/app/view/processviewer/processviewer";
10+
import { SearchViewModel } from "@/app/view/search/search";
1011
import { SysinfoViewModel } from "@/app/view/sysinfo/sysinfo";
1112
import { TsunamiViewModel } from "@/app/view/tsunami/tsunami";
1213
import { VDomModel } from "@/app/view/vdom/vdom-model";
@@ -35,6 +36,7 @@ BlockRegistry.set("tsunami", TsunamiViewModel);
3536
BlockRegistry.set("aifilediff", AiFileDiffViewModel);
3637
BlockRegistry.set("waveconfig", WaveConfigViewModel);
3738
BlockRegistry.set("processviewer", ProcessViewerViewModel);
39+
BlockRegistry.set("search", SearchViewModel);
3840

3941
function makeDefaultViewModel(viewType: string): ViewModel {
4042
const viewModel: ViewModel = {

frontend/app/block/blockutil.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ export function blockViewToIcon(view: string): string {
4545
if (view == "processviewer") {
4646
return "microchip";
4747
}
48+
if (view == "search") {
49+
return "search";
50+
}
4851
return "square";
4952
}
5053

@@ -73,6 +76,9 @@ export function blockViewToName(view: string): string {
7376
if (view == "processviewer") {
7477
return "Processes";
7578
}
79+
if (view == "search") {
80+
return "Search";
81+
}
7682
return view;
7783
}
7884

frontend/app/element/markdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
transformBlocks,
1111
} from "@/app/element/markdown-util";
1212
import remarkMermaidToTag from "@/app/element/remark-mermaid-to-tag";
13-
import { boundNumber, useAtomValueSafe, cn } from "@/util/util";
13+
import { boundNumber, cn, useAtomValueSafe } from "@/util/util";
1414
import clsx from "clsx";
1515
import { Atom } from "jotai";
1616
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
@@ -31,7 +31,7 @@ let mermaidInstance: any = null;
3131

3232
const initializeMermaid = async () => {
3333
if (!mermaidInitialized) {
34-
const mermaid = await import("mermaid");
34+
const mermaid = await import("mermaid/dist/mermaid.js");
3535
mermaidInstance = mermaid.default;
3636
mermaidInstance.initialize({ startOnLoad: false, theme: "dark", securityLevel: "strict" });
3737
mermaidInitialized = true;

frontend/app/store/keymodel.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { getActiveTabModel } from "@/app/store/tab-model";
2424
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
2525
import { deleteLayoutModelForTab, getLayoutModelForStaticTab, NavigateDirection } from "@/layout/index";
2626
import * as keyutil from "@/util/keyutil";
27+
import { focusedBlockId } from "@/util/focusutil";
2728
import { isWindows } from "@/util/platformutil";
2829
import { CHORD_TIMEOUT } from "@/util/sharedconst";
2930
import { fireAndForget } from "@/util/util";
@@ -452,7 +453,11 @@ function appHandleKeyDown(waveEvent: WaveKeyboardEvent): boolean {
452453
if (isTabWindow()) {
453454
const layoutModel = getLayoutModelForStaticTab();
454455
const focusedNode = globalStore.get(layoutModel.focusedNode);
455-
const blockId = focusedNode?.data?.blockId;
456+
let blockId = focusedNode?.data?.blockId;
457+
const domFocusedId = focusedBlockId();
458+
if (domFocusedId) {
459+
blockId = domFocusedId;
460+
}
456461
if (blockId != null && shouldDispatchToBlock(waveEvent)) {
457462
const bcm = getBlockComponentModel(blockId);
458463
const viewModel = bcm?.viewModel;

frontend/app/store/wshclientapi.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,12 @@ export class RpcApiType {
372372
return client.wshRpcCall("filerestorebackup", data, opts);
373373
}
374374

375+
// command "filesearch" [call]
376+
FileSearchCommand(client: WshClient, data: CommandFileSearchData, opts?: RpcOpts): Promise<FileSearchResult[]> {
377+
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "filesearch", data, opts);
378+
return client.wshRpcCall("filesearch", data, opts);
379+
}
380+
375381
// command "filestream" [call]
376382
FileStreamCommand(client: WshClient, data: CommandFileStreamData, opts?: RpcOpts): Promise<FileInfo> {
377383
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "filestream", data, opts);
@@ -720,6 +726,12 @@ export class RpcApiType {
720726
return client.wshRpcCall("remotefilemultiinfo", data, opts);
721727
}
722728

729+
// command "remotefilesearch" [call]
730+
RemoteFileSearchCommand(client: WshClient, data: CommandFileSearchData, opts?: RpcOpts): Promise<FileSearchResult[]> {
731+
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "remotefilesearch", data, opts);
732+
return client.wshRpcCall("remotefilesearch", data, opts);
733+
}
734+
723735
// command "remotefilestream" [call]
724736
RemoteFileStreamCommand(client: WshClient, data: CommandRemoteFileStreamData, opts?: RpcOpts): Promise<FileInfo> {
725737
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "remotefilestream", data, opts);

0 commit comments

Comments
 (0)