@cueloop/client 0.1.0-alpha.92 → 0.1.0-alpha.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/App.deleted-scroll.test.tsx +186 -0
- package/src/App.diff.test.tsx +3 -4
- package/src/App.test.tsx +1 -0
- package/src/App.toast.test.tsx +27 -1
- package/src/App.tsx +21 -12
- package/src/App.workbench.test.tsx +208 -2
- package/src/app-screens.tsx +34 -8
- package/src/components/AppShell.zoom.test.tsx +16 -3
- package/src/components/ChangesColumn.tsx +10 -4
- package/src/components/ChangesFileTree.test.tsx +122 -4
- package/src/components/DiffContentView.scroll.test.tsx +285 -1
- package/src/components/DiffContentView.tsx +214 -56
- package/src/components/EditorGrid.split.test.tsx +118 -0
- package/src/components/EditorGrid.stories.tsx +27 -0
- package/src/components/EditorGrid.tsx +170 -50
- package/src/components/EditorGrid.zoom.test.tsx +65 -1
- package/src/components/GridTabContent.actions.test.tsx +151 -0
- package/src/components/GridTabContent.stories.tsx +2 -2
- package/src/components/GridTabContent.tsx +5 -1
- package/src/components/ProjectTreeView.test.tsx +166 -8
- package/src/components/ProjectTreeView.tsx +53 -27
- package/src/components/ScrollArea.tsx +21 -2
- package/src/components/Toast.tsx +3 -1
- package/src/components/WelcomePlayground.test.tsx +28 -1
- package/src/components/__snapshots__/stories.test.tsx.snap +41 -21
- package/src/components/editor-grid.test.ts +87 -0
- package/src/components/editor-grid.ts +142 -6
- package/src/components/primitives/Tree.tsx +51 -15
- package/src/thread-controller.curate.test.ts +27 -0
- package/src/thread-controller.ts +4 -6
- package/src/use-annotation-surface.tsx +86 -19
- package/src/use-changes-workbench.ts +30 -18
- package/src/use-frame-measure.ts +40 -7
- package/src/use-settings-dialog.tsx +1 -13
- package/src/use-shared-keyboard.ts +55 -0
- package/src/use-terminal-virtualizer.ts +8 -2
|
@@ -7,13 +7,14 @@ import type { ProjectPanelMode } from "./components/AppShell";
|
|
|
7
7
|
import type { LaunchLayout } from "./launch-layout";
|
|
8
8
|
import {
|
|
9
9
|
activateTab,
|
|
10
|
-
addTab,
|
|
11
10
|
changesTab,
|
|
12
11
|
closeTab,
|
|
13
12
|
containsGroup,
|
|
14
|
-
fileTab,
|
|
15
13
|
firstGroupId,
|
|
14
|
+
keepFileTab,
|
|
16
15
|
makeGroup,
|
|
16
|
+
openChangesTab,
|
|
17
|
+
openFileTab,
|
|
17
18
|
splitGroup,
|
|
18
19
|
welcomeTab,
|
|
19
20
|
type EditorNode,
|
|
@@ -73,9 +74,10 @@ export interface ChangesWorkbench {
|
|
|
73
74
|
toggleZoom: () => void;
|
|
74
75
|
focusGroup: (groupId: string) => void;
|
|
75
76
|
activate: (groupId: string, tabId: string) => void;
|
|
77
|
+
keepTab: (groupId: string, tabId: string) => void;
|
|
76
78
|
close: (groupId: string, tabId: string) => void;
|
|
77
79
|
split: (groupId: string, direction: SplitDirection) => void;
|
|
78
|
-
openFile: (path: string, fileView: "diff" | "contents") => void;
|
|
80
|
+
openFile: (path: string, fileView: "diff" | "contents", persistent?: boolean) => void;
|
|
79
81
|
/** Re-open defaults when the session changes: a diff opens the region in changed-files mode. */
|
|
80
82
|
syncSession: (sessionId: string | undefined, isDiff: boolean) => void;
|
|
81
83
|
}
|
|
@@ -121,16 +123,16 @@ export function useChangesWorkbench(options?: ChangesWorkbenchOptions): ChangesW
|
|
|
121
123
|
}
|
|
122
124
|
};
|
|
123
125
|
const toggleChanges = (): void => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
126
|
+
setChangesOpen(true);
|
|
127
|
+
setProjectOpen(true);
|
|
128
|
+
setProjectMode("changes");
|
|
129
|
+
setGrid((tree) => {
|
|
130
|
+
const result = openChangesTab(tree, focusedGroup ?? firstGroupId(tree));
|
|
131
|
+
|
|
132
|
+
setFocusedGroup(result.focusGroupId);
|
|
133
|
+
|
|
134
|
+
return result.tree;
|
|
135
|
+
});
|
|
134
136
|
};
|
|
135
137
|
const toggleProject = (): void => {
|
|
136
138
|
setProjectOpen(true);
|
|
@@ -160,13 +162,22 @@ export function useChangesWorkbench(options?: ChangesWorkbenchOptions): ChangesW
|
|
|
160
162
|
return result.tree;
|
|
161
163
|
});
|
|
162
164
|
};
|
|
163
|
-
const openFile = (path: string, fileView: "diff" | "contents"): void => {
|
|
165
|
+
const openFile = (path: string, fileView: "diff" | "contents", persistent = false): void => {
|
|
164
166
|
setProjectOpen(true);
|
|
165
167
|
setChangesOpen(true);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
setGrid((tree) => {
|
|
169
|
+
const result = openFileTab(
|
|
170
|
+
tree,
|
|
171
|
+
focusedGroup ?? firstGroupId(tree),
|
|
172
|
+
path,
|
|
173
|
+
fileView,
|
|
174
|
+
persistent,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
setFocusedGroup(result.focusGroupId);
|
|
178
|
+
|
|
179
|
+
return result.tree;
|
|
180
|
+
});
|
|
170
181
|
};
|
|
171
182
|
|
|
172
183
|
return {
|
|
@@ -182,6 +193,7 @@ export function useChangesWorkbench(options?: ChangesWorkbenchOptions): ChangesW
|
|
|
182
193
|
toggleZoom: () => setZoomed((value) => !value),
|
|
183
194
|
focusGroup: setFocusedGroup,
|
|
184
195
|
activate: (groupId, tabId) => setGrid((tree) => activateTab(tree, groupId, tabId)),
|
|
196
|
+
keepTab: (groupId, tabId) => setGrid((tree) => keepFileTab(tree, groupId, tabId)),
|
|
185
197
|
close,
|
|
186
198
|
split,
|
|
187
199
|
openFile,
|
package/src/use-frame-measure.ts
CHANGED
|
@@ -8,6 +8,45 @@
|
|
|
8
8
|
import { useEffect, useState } from "react";
|
|
9
9
|
import { useRenderer } from "@opentui/react";
|
|
10
10
|
|
|
11
|
+
type Renderer = NonNullable<ReturnType<typeof useRenderer>>;
|
|
12
|
+
type Measure = () => void;
|
|
13
|
+
|
|
14
|
+
interface FrameSubscriptions {
|
|
15
|
+
measures: Set<Measure>;
|
|
16
|
+
notify: Measure;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const subscribers = new WeakMap<Renderer, FrameSubscriptions>();
|
|
20
|
+
|
|
21
|
+
export function subscribeToFrames(renderer: Renderer, measure: Measure): () => void {
|
|
22
|
+
let subscription = subscribers.get(renderer);
|
|
23
|
+
|
|
24
|
+
if (!subscription) {
|
|
25
|
+
const measures = new Set<Measure>();
|
|
26
|
+
const notify = (): void => {
|
|
27
|
+
for (const subscriber of measures) subscriber();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
subscription = { measures, notify };
|
|
31
|
+
subscribers.set(renderer, subscription);
|
|
32
|
+
renderer.on("frame", notify);
|
|
33
|
+
renderer.on("resize", notify);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { measures, notify } = subscription;
|
|
37
|
+
|
|
38
|
+
measures.add(measure);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
measures.delete(measure);
|
|
42
|
+
if (measures.size > 0) return;
|
|
43
|
+
|
|
44
|
+
renderer.off("frame", notify);
|
|
45
|
+
renderer.off("resize", notify);
|
|
46
|
+
subscribers.delete(renderer);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
11
50
|
export function useFrameMeasure<T>(
|
|
12
51
|
read: () => T,
|
|
13
52
|
isEqual: (left: T, right: T) => boolean,
|
|
@@ -27,13 +66,7 @@ export function useFrameMeasure<T>(
|
|
|
27
66
|
};
|
|
28
67
|
|
|
29
68
|
measure();
|
|
30
|
-
renderer
|
|
31
|
-
renderer?.on("resize", measure);
|
|
32
|
-
|
|
33
|
-
return () => {
|
|
34
|
-
renderer?.off("frame", measure);
|
|
35
|
-
renderer?.off("resize", measure);
|
|
36
|
-
};
|
|
69
|
+
if (renderer) return subscribeToFrames(renderer, measure);
|
|
37
70
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
71
|
}, [renderer, active]);
|
|
39
72
|
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
persistActions,
|
|
5
5
|
persistAutoClose,
|
|
6
6
|
persistDiffView,
|
|
7
|
-
persistReviewSkill,
|
|
8
7
|
persistReviewWorkspace,
|
|
9
8
|
persistTheme,
|
|
10
9
|
type AutoClose,
|
|
@@ -94,8 +93,6 @@ export function useSettingsDialog(params: {
|
|
|
94
93
|
onSyncGithubIdentity: () => void;
|
|
95
94
|
onRenameDisplayName: () => void;
|
|
96
95
|
reviewSkill: string;
|
|
97
|
-
reviewSkillOptions: string[];
|
|
98
|
-
setReviewSkill: Dispatch<SetStateAction<string>>;
|
|
99
96
|
reviewWorkspace: ReviewWorkspaceMode;
|
|
100
97
|
setReviewWorkspace: Dispatch<SetStateAction<ReviewWorkspaceMode>>;
|
|
101
98
|
}): SettingsDialogModel {
|
|
@@ -118,8 +115,6 @@ export function useSettingsDialog(params: {
|
|
|
118
115
|
onSyncGithubIdentity,
|
|
119
116
|
onRenameDisplayName,
|
|
120
117
|
reviewSkill,
|
|
121
|
-
reviewSkillOptions,
|
|
122
|
-
setReviewSkill,
|
|
123
118
|
reviewWorkspace,
|
|
124
119
|
setReviewWorkspace,
|
|
125
120
|
} = params;
|
|
@@ -210,8 +205,7 @@ export function useSettingsDialog(params: {
|
|
|
210
205
|
{
|
|
211
206
|
key: "reviewSkill",
|
|
212
207
|
label: "Skill",
|
|
213
|
-
kind: "
|
|
214
|
-
options: reviewSkillOptions,
|
|
208
|
+
kind: "text",
|
|
215
209
|
},
|
|
216
210
|
{
|
|
217
211
|
key: "reviewWorkspace",
|
|
@@ -281,12 +275,6 @@ export function useSettingsDialog(params: {
|
|
|
281
275
|
onSyncGithubIdentity();
|
|
282
276
|
} else if (rowKey === "displayName") {
|
|
283
277
|
onRenameDisplayName();
|
|
284
|
-
} else if (rowKey === "reviewSkill") {
|
|
285
|
-
const current = Math.max(0, reviewSkillOptions.indexOf(reviewSkill));
|
|
286
|
-
const next = reviewSkillOptions[(current + 1) % reviewSkillOptions.length]!;
|
|
287
|
-
|
|
288
|
-
setReviewSkill(next);
|
|
289
|
-
persistReviewSkill(next);
|
|
290
278
|
} else if (rowKey === "reviewWorkspace") {
|
|
291
279
|
const next: ReviewWorkspaceMode = reviewWorkspace === "worktree" ? "current" : "worktree";
|
|
292
280
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** One keypress subscription per renderer, even when several editor tiles are mounted. */
|
|
2
|
+
|
|
3
|
+
import { useEffect, useEffectEvent } from "react";
|
|
4
|
+
import { useRenderer } from "@opentui/react";
|
|
5
|
+
import type { KeyEvent } from "@opentui/core";
|
|
6
|
+
|
|
7
|
+
type KeyInput = NonNullable<ReturnType<typeof useRenderer>>["keyInput"];
|
|
8
|
+
type KeyHandler = (key: KeyEvent) => void;
|
|
9
|
+
|
|
10
|
+
interface KeySubscriptions {
|
|
11
|
+
handlers: Set<KeyHandler>;
|
|
12
|
+
notify: KeyHandler;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const subscribers = new WeakMap<KeyInput, KeySubscriptions>();
|
|
16
|
+
|
|
17
|
+
function subscribeToKeypress(input: KeyInput, handler: KeyHandler): () => void {
|
|
18
|
+
let subscription = subscribers.get(input);
|
|
19
|
+
|
|
20
|
+
if (!subscription) {
|
|
21
|
+
const handlers = new Set<KeyHandler>();
|
|
22
|
+
const notify = (key: KeyEvent): void => {
|
|
23
|
+
for (const subscriber of handlers) subscriber(key);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
subscription = { handlers, notify };
|
|
27
|
+
subscribers.set(input, subscription);
|
|
28
|
+
input.on("keypress", notify);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { handlers, notify } = subscription;
|
|
32
|
+
|
|
33
|
+
handlers.add(handler);
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
handlers.delete(handler);
|
|
37
|
+
if (handlers.size > 0) return;
|
|
38
|
+
|
|
39
|
+
input.off("keypress", notify);
|
|
40
|
+
subscribers.delete(input);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function useSharedKeyboard(handler: KeyHandler): void {
|
|
45
|
+
const renderer = useRenderer();
|
|
46
|
+
const stableHandler = useEffectEvent(handler);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const input = renderer?.keyInput;
|
|
50
|
+
|
|
51
|
+
if (!input) return;
|
|
52
|
+
|
|
53
|
+
return subscribeToKeypress(input, stableHandler);
|
|
54
|
+
}, [renderer]);
|
|
55
|
+
}
|
|
@@ -12,6 +12,7 @@ import { useEffect, useLayoutEffect, useRef, useState, type RefObject } from "re
|
|
|
12
12
|
import type { BoxRenderable, ScrollBoxRenderable } from "@opentui/core";
|
|
13
13
|
import { useRenderer } from "@opentui/react";
|
|
14
14
|
import { Virtualizer, type VirtualItem } from "@tanstack/virtual-core";
|
|
15
|
+
import { subscribeToFrames } from "./use-frame-measure";
|
|
15
16
|
|
|
16
17
|
export interface TerminalVirtualizerOptions {
|
|
17
18
|
scrollbox: RefObject<ScrollBoxRenderable | null>;
|
|
@@ -29,6 +30,10 @@ export interface TerminalVirtualizer {
|
|
|
29
30
|
measureRef: (index: number) => (renderable: BoxRenderable | null) => void | (() => void);
|
|
30
31
|
/** Scroll the item into view; "auto" only scrolls when it is off screen. */
|
|
31
32
|
scrollToIndex: (index: number, align?: "auto" | "start" | "center" | "end") => void;
|
|
33
|
+
/** Measured or estimated visual-row offset of an item. */
|
|
34
|
+
startOfIndex: (index: number) => number | undefined;
|
|
35
|
+
/** Move the native viewport to an exact visual-row offset. */
|
|
36
|
+
scrollToOffset: (offset: number) => void;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
/** Visible rows and one-row scrolling for a held mouse mark at a viewport edge. */
|
|
@@ -150,9 +155,8 @@ export function useTerminalVirtualizer(options: TerminalVirtualizerOptions): Ter
|
|
|
150
155
|
// render, and the callbacks only fire the core when the value actually changed
|
|
151
156
|
const onFrame = (read: () => void): (() => void) => {
|
|
152
157
|
read();
|
|
153
|
-
renderer?.on("frame", read);
|
|
154
158
|
|
|
155
|
-
return
|
|
159
|
+
return renderer ? subscribeToFrames(renderer, read) : () => {};
|
|
156
160
|
};
|
|
157
161
|
|
|
158
162
|
const coreOptions = () => ({
|
|
@@ -255,5 +259,7 @@ export function useTerminalVirtualizer(options: TerminalVirtualizerOptions): Ter
|
|
|
255
259
|
};
|
|
256
260
|
},
|
|
257
261
|
scrollToIndex: (index, align = "auto") => virtualizer.scrollToIndex(index, { align }),
|
|
262
|
+
startOfIndex: (index) => virtualizer.getOffsetForIndex(index, "start")?.[0],
|
|
263
|
+
scrollToOffset: (offset) => virtualizer.scrollToOffset(offset, { align: "start" }),
|
|
258
264
|
};
|
|
259
265
|
}
|