Skip to content

Commit 083d99a

Browse files
committed
Merge remote-tracking branch 'upstream/main' into export-config
2 parents c59ab85 + cb2cd72 commit 083d99a

File tree

16 files changed

+274
-69
lines changed

16 files changed

+274
-69
lines changed

BUILD.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,15 @@ All the methods below will install Node and Go dependencies when they run the fi
112112
Run the following command to build the app and run it via Vite's development server (this enables Hot Module Reloading):
113113

114114
```sh
115-
task electron:dev
115+
task dev
116116
```
117117

118118
### Standalone
119119

120120
Run the following command to build the app and run it standalone, without the development server. This will not reload on change:
121121

122122
```sh
123-
task electron:start
123+
task start
124124
```
125125

126126
### Packaged

ROADMAP.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ Legend: ✅ Done | 🔧 In Progress | 🔷 Planned | 🤞 Stretch Goal
2020
- 🔷 Wave Apps (Go SDK)
2121
- 🔷 Fixes for reducing 2FA requests on connect
2222
- 🔷 Frontend Only Widgets, React + Babel Transpiling in an iframe/webview
23-
- 🔧 WebLinks in the terminal working again
23+
- WebLinks in the terminal working again
2424
- 🔧 Search in Web Views
2525
- 🔷 Search in the Terminal
2626
- 🔷 Custom init files for widgets and terminal blocks
2727
- 🔷 Multi-Input between terminal blocks on the same tab
28-
- 🔧 Gemini AI support
28+
- Gemini AI support
2929
- 🔷 Monaco Theming
3030
- 🤞 Blockcontroller fixes for terminal escape sequences
3131
- 🤞 Explore VSCode Extension Compatibility with standalone Monaco Editor (language servers)

Taskfile.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ tasks:
1919
electron:dev:
2020
desc: Run the Electron application via the Vite dev server (enables hot reloading).
2121
cmd: yarn dev
22+
aliases:
23+
- dev
2224
deps:
2325
- yarn
2426
- docsite:build:embedded
@@ -30,6 +32,8 @@ tasks:
3032
electron:start:
3133
desc: Run the Electron application directly.
3234
cmd: yarn start
35+
aliases:
36+
- start
3337
deps:
3438
- yarn
3539
- docsite:build:embedded
@@ -56,6 +60,8 @@ tasks:
5660
desc: Start the docsite dev server.
5761
cmd: yarn start
5862
dir: docs
63+
aliases:
64+
- docsite
5965
deps:
6066
- yarn
6167

docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ yarn
1313
### Local Development
1414

1515
```sh
16-
yarn start
16+
task docsite
1717
```
1818

1919
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
2020

2121
### Build
2222

2323
```sh
24-
yarn build
24+
task docsite:build:<embedded,public>
2525
```
2626

2727
This command generates static content into the `build` directory and can be served using any static contents hosting service.

emain/emain-window.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { delay, ensureBoundsAreVisible, waveKeyToElectronKey } from "./emain-uti
1818
import { log } from "./log";
1919
import { getElectronAppBasePath, unamePlatform } from "./platform";
2020
import { updater } from "./updater";
21+
2122
export type WindowOpts = {
2223
unamePlatform: string;
2324
};
@@ -303,7 +304,8 @@ export class WaveBrowserWindow extends BaseWindow {
303304
const workspaceList = await WorkspaceService.ListWorkspaces();
304305
if (!workspaceList?.find((wse) => wse.workspaceid === workspaceId)?.windowid) {
305306
const curWorkspace = await WorkspaceService.GetWorkspace(this.workspaceId);
306-
if (isNonEmptyUnsavedWorkspace(curWorkspace)) {
307+
308+
if (curWorkspace && isNonEmptyUnsavedWorkspace(curWorkspace)) {
307309
console.log(
308310
`existing unsaved workspace ${this.workspaceId} has content, opening workspace ${workspaceId} in new window`
309311
);
@@ -693,17 +695,22 @@ ipcMain.on("delete-workspace", (event, workspaceId) => {
693695
type: "question",
694696
buttons: ["Cancel", "Delete Workspace"],
695697
title: "Confirm",
696-
message: `Deleting workspace will also delete its contents.${workspaceHasWindow ? "\nWorkspace is open in a window, which will be closed." : ""}\n\nContinue?`,
698+
message: `Deleting workspace will also delete its contents.\n\nContinue?`,
697699
});
698700
if (choice === 0) {
699701
console.log("user cancelled workspace delete", workspaceId, ww?.waveWindowId);
700702
return;
701703
}
702-
await WorkspaceService.DeleteWorkspace(workspaceId);
704+
705+
const newWorkspaceId = await WorkspaceService.DeleteWorkspace(workspaceId);
703706
console.log("delete-workspace done", workspaceId, ww?.waveWindowId);
704707
if (ww?.workspaceId == workspaceId) {
705-
console.log("delete-workspace closing window", workspaceId, ww?.waveWindowId);
706-
ww.destroy();
708+
if (newWorkspaceId) {
709+
await ww.switchWorkspace(newWorkspaceId);
710+
} else {
711+
console.log("delete-workspace closing window", workspaceId, ww?.waveWindowId);
712+
ww.destroy();
713+
}
707714
}
708715
});
709716
});

frontend/app/element/search.tsx

Lines changed: 92 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { autoUpdate, FloatingPortal, Middleware, offset, useDismiss, useFloating } from "@floating-ui/react";
22
import clsx from "clsx";
3-
import { atom, PrimitiveAtom, useAtom, useAtomValue } from "jotai";
4-
import { memo, useCallback, useRef, useState } from "react";
3+
import { atom, useAtom } from "jotai";
4+
import { memo, useCallback, useEffect, useMemo, useRef } from "react";
55
import { IconButton } from "./iconbutton";
66
import { Input } from "./input";
77
import "./search.scss";
88

9-
type SearchProps = {
10-
searchAtom: PrimitiveAtom<string>;
11-
indexAtom: PrimitiveAtom<number>;
12-
numResultsAtom: PrimitiveAtom<number>;
13-
isOpenAtom: PrimitiveAtom<boolean>;
9+
type SearchProps = SearchAtoms & {
1410
anchorRef?: React.RefObject<HTMLElement>;
1511
offsetX?: number;
1612
offsetY?: number;
13+
onSearch?: (search: string) => void;
14+
onNext?: () => void;
15+
onPrev?: () => void;
1716
};
1817

1918
const SearchComponent = ({
@@ -24,23 +23,54 @@ const SearchComponent = ({
2423
anchorRef,
2524
offsetX = 10,
2625
offsetY = 10,
26+
onSearch,
27+
onNext,
28+
onPrev,
2729
}: SearchProps) => {
28-
const [isOpen, setIsOpen] = useAtom(isOpenAtom);
29-
const [search, setSearch] = useAtom(searchAtom);
30-
const [index, setIndex] = useAtom(indexAtom);
31-
const numResults = useAtomValue(numResultsAtom);
30+
const [isOpen, setIsOpen] = useAtom<boolean>(isOpenAtom);
31+
const [search, setSearch] = useAtom<string>(searchAtom);
32+
const [index, setIndex] = useAtom<number>(indexAtom);
33+
const [numResults, setNumResults] = useAtom<number>(numResultsAtom);
3234

3335
const handleOpenChange = useCallback((open: boolean) => {
3436
setIsOpen(open);
3537
}, []);
3638

39+
useEffect(() => {
40+
setSearch("");
41+
setIndex(0);
42+
setNumResults(0);
43+
}, [isOpen]);
44+
45+
useEffect(() => {
46+
setIndex(0);
47+
setNumResults(0);
48+
onSearch?.(search);
49+
}, [search]);
50+
3751
const middleware: Middleware[] = [];
38-
middleware.push(
39-
offset(({ rects }) => ({
40-
mainAxis: -rects.floating.height - offsetY,
41-
crossAxis: -offsetX,
42-
}))
52+
const offsetCallback = useCallback(
53+
({ rects }) => {
54+
const docRect = document.documentElement.getBoundingClientRect();
55+
let yOffsetCalc = -rects.floating.height - offsetY;
56+
let xOffsetCalc = -offsetX;
57+
const floatingBottom = rects.reference.y + rects.floating.height + offsetY;
58+
const floatingLeft = rects.reference.x + rects.reference.width - (rects.floating.width + offsetX);
59+
if (floatingBottom > docRect.bottom) {
60+
yOffsetCalc -= docRect.bottom - floatingBottom;
61+
}
62+
if (floatingLeft < 5) {
63+
xOffsetCalc += 5 - floatingLeft;
64+
}
65+
console.log("offsetCalc", yOffsetCalc, xOffsetCalc);
66+
return {
67+
mainAxis: yOffsetCalc,
68+
crossAxis: xOffsetCalc,
69+
};
70+
},
71+
[offsetX, offsetY]
4372
);
73+
middleware.push(offset(offsetCallback));
4474

4575
const { refs, floatingStyles, context } = useFloating({
4676
placement: "top-end",
@@ -55,26 +85,47 @@ const SearchComponent = ({
5585

5686
const dismiss = useDismiss(context);
5787

88+
const onPrevWrapper = useCallback(
89+
() => (onPrev ? onPrev() : setIndex((index - 1) % numResults)),
90+
[onPrev, index, numResults]
91+
);
92+
const onNextWrapper = useCallback(
93+
() => (onNext ? onNext() : setIndex((index + 1) % numResults)),
94+
[onNext, index, numResults]
95+
);
96+
97+
const onKeyDown = useCallback(
98+
(e: React.KeyboardEvent) => {
99+
if (e.key === "Enter") {
100+
if (e.shiftKey) {
101+
onPrevWrapper();
102+
} else {
103+
onNextWrapper();
104+
}
105+
e.preventDefault();
106+
}
107+
},
108+
[onPrevWrapper, onNextWrapper, setIsOpen]
109+
);
110+
58111
const prevDecl: IconButtonDecl = {
59112
elemtype: "iconbutton",
60113
icon: "chevron-up",
61-
title: "Previous Result",
62-
disabled: index === 0,
63-
click: () => setIndex(index - 1),
114+
title: "Previous Result (Shift+Enter)",
115+
click: onPrevWrapper,
64116
};
65117

66118
const nextDecl: IconButtonDecl = {
67119
elemtype: "iconbutton",
68120
icon: "chevron-down",
69-
title: "Next Result",
70-
disabled: !numResults || index === numResults - 1,
71-
click: () => setIndex(index + 1),
121+
title: "Next Result (Enter)",
122+
click: onNextWrapper,
72123
};
73124

74125
const closeDecl: IconButtonDecl = {
75126
elemtype: "iconbutton",
76127
icon: "xmark-large",
77-
title: "Close",
128+
title: "Close (Esc)",
78129
click: () => setIsOpen(false),
79130
};
80131

@@ -83,7 +134,13 @@ const SearchComponent = ({
83134
{isOpen && (
84135
<FloatingPortal>
85136
<div className="search-container" style={{ ...floatingStyles }} {...dismiss} ref={refs.setFloating}>
86-
<Input placeholder="Search" value={search} onChange={setSearch} />
137+
<Input
138+
placeholder="Search"
139+
value={search}
140+
onChange={setSearch}
141+
onKeyDown={onKeyDown}
142+
autoFocus
143+
/>
87144
<div
88145
className={clsx("search-results", { hidden: numResults === 0 })}
89146
aria-live="polite"
@@ -105,11 +162,16 @@ const SearchComponent = ({
105162

106163
export const Search = memo(SearchComponent) as typeof SearchComponent;
107164

108-
export function useSearch(anchorRef?: React.RefObject<HTMLElement>): SearchProps {
109-
const [searchAtom] = useState(atom(""));
110-
const [indexAtom] = useState(atom(0));
111-
const [numResultsAtom] = useState(atom(0));
112-
const [isOpenAtom] = useState(atom(false));
165+
export function useSearch(anchorRef?: React.RefObject<HTMLElement>, viewModel?: ViewModel): SearchProps {
166+
const searchAtoms: SearchAtoms = useMemo(
167+
() => ({ searchAtom: atom(""), indexAtom: atom(0), numResultsAtom: atom(0), isOpenAtom: atom(false) }),
168+
[]
169+
);
113170
anchorRef ??= useRef(null);
114-
return { searchAtom, indexAtom, numResultsAtom, isOpenAtom, anchorRef };
171+
useEffect(() => {
172+
if (viewModel) {
173+
viewModel.searchAtoms = searchAtoms;
174+
}
175+
}, [viewModel]);
176+
return { ...searchAtoms, anchorRef };
115177
}

frontend/app/store/keymodel.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,28 @@ function registerGlobalKeys() {
321321
return true;
322322
});
323323
}
324+
function activateSearch(): boolean {
325+
console.log("activateSearch");
326+
const bcm = getBlockComponentModel(getFocusedBlockInStaticTab());
327+
if (bcm.viewModel.searchAtoms) {
328+
console.log("activateSearch2");
329+
globalStore.set(bcm.viewModel.searchAtoms.isOpenAtom, true);
330+
return true;
331+
}
332+
return false;
333+
}
334+
function deactivateSearch(): boolean {
335+
console.log("deactivateSearch");
336+
const bcm = getBlockComponentModel(getFocusedBlockInStaticTab());
337+
if (bcm.viewModel.searchAtoms && globalStore.get(bcm.viewModel.searchAtoms.isOpenAtom)) {
338+
globalStore.set(bcm.viewModel.searchAtoms.isOpenAtom, false);
339+
return true;
340+
}
341+
return false;
342+
}
343+
globalKeyMap.set("Cmd:f", activateSearch);
344+
globalKeyMap.set("Ctrl:f", activateSearch);
345+
globalKeyMap.set("Escape", deactivateSearch);
324346
const allKeys = Array.from(globalKeyMap.keys());
325347
// special case keys, handled by web view
326348
allKeys.push("Cmd:l", "Cmd:r", "Cmd:ArrowRight", "Cmd:ArrowLeft");

frontend/app/store/services.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ class WorkspaceServiceType {
189189
}
190190

191191
// @returns object updates
192-
DeleteWorkspace(workspaceId: string): Promise<void> {
192+
DeleteWorkspace(workspaceId: string): Promise<string> {
193193
return WOS.callBackendService("workspace", "DeleteWorkspace", Array.from(arguments))
194194
}
195195

frontend/app/view/webview/webview.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Copyright 2024, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
.webview {
4+
.webview,
5+
.webview-container {
56
height: 100%;
67
width: 100%;
78
border: none !important;

0 commit comments

Comments
 (0)