@cueloop/client 0.1.0-alpha.7 → 0.1.0-alpha.71
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/native/build-pty.sh +51 -0
- package/native/build.sh +82 -0
- package/native/darwin-arm64/libcuelooppty.dylib +0 -0
- package/native/darwin-arm64/libcueloopvt.dylib +0 -0
- package/native/src/pty.zig +193 -0
- package/native/src/shim.zig +114 -0
- package/package.json +39 -32
- package/src/App.appearance.test.tsx +53 -0
- package/src/App.autoclose.test.tsx +76 -50
- package/src/App.diff.test.tsx +156 -42
- package/src/App.export.test.tsx +58 -40
- package/src/App.plan-review.test.tsx +363 -0
- package/src/App.prototype-markdown.test.tsx +92 -0
- package/src/App.prototype.test.tsx +176 -0
- package/src/App.readonly.test.tsx +128 -67
- package/src/App.rendering.test.tsx +61 -35
- package/src/App.submit-confirm.test.tsx +191 -0
- package/src/App.test.tsx +310 -114
- package/src/App.toast.test.tsx +89 -0
- package/src/App.tsx +1043 -798
- package/src/App.walk.test.tsx +298 -0
- package/src/App.workbench.test.tsx +495 -0
- package/src/__snapshots__/view-plan.regression.test.ts.snap +100 -0
- package/src/annotation-palette.ts +61 -0
- package/src/app-key-state.ts +61 -0
- package/src/app-screens.tsx +432 -0
- package/src/app-view-model.ts +159 -0
- package/src/attribution.test.ts +31 -0
- package/src/attribution.ts +42 -0
- package/src/clipboard.test.ts +23 -0
- package/src/clipboard.ts +33 -0
- package/src/components/AnnotatableFileView.stories.tsx +42 -0
- package/src/components/AnnotatableFileView.test.tsx +102 -0
- package/src/components/AnnotatableFileView.tsx +126 -0
- package/src/components/AnnotationCards.stories.tsx +87 -0
- package/src/components/AnnotationCards.tsx +303 -0
- package/src/components/AppHeader.stories.tsx +50 -0
- package/src/components/AppHeader.tsx +83 -0
- package/src/components/AppShell.stories.tsx +89 -0
- package/src/components/AppShell.tsx +290 -0
- package/src/components/AppShell.zoom.test.tsx +135 -0
- package/src/components/BareWorkbenchFileView.stories.tsx +27 -0
- package/src/components/BareWorkbenchFileView.tsx +64 -0
- package/src/components/Breadcrumb.stories.tsx +19 -0
- package/src/components/Breadcrumb.tsx +45 -0
- package/src/components/ChangesColumn.stories.tsx +39 -0
- package/src/components/ChangesColumn.tsx +171 -0
- package/src/components/CodeBlock.stories.tsx +42 -0
- package/src/components/CodeBlock.tsx +84 -0
- package/src/components/CompletionOverlay.stories.tsx +23 -0
- package/src/components/CompletionOverlay.tsx +60 -0
- package/src/components/ConfirmCard.stories.tsx +35 -0
- package/src/components/ConfirmCard.tsx +128 -0
- package/src/components/ConfirmDialog.stories.tsx +21 -0
- package/src/components/ConfirmDialog.tsx +66 -0
- package/src/components/DiffContentView.scroll.test.tsx +121 -0
- package/src/components/DiffContentView.split.test.tsx +140 -0
- package/src/components/DiffContentView.stories.tsx +80 -0
- package/src/components/DiffContentView.syntax.test.tsx +82 -0
- package/src/components/DiffContentView.tsx +859 -0
- package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
- package/src/components/DiscussionMarkerRail.tsx +181 -0
- package/src/components/EditorGrid.stories.tsx +59 -0
- package/src/components/EditorGrid.tabs.test.tsx +118 -0
- package/src/components/EditorGrid.tsx +368 -0
- package/src/components/EditorGrid.zoom.test.tsx +68 -0
- package/src/components/FileContentsView.stories.tsx +16 -0
- package/src/components/FileContentsView.test.tsx +29 -0
- package/src/components/FileContentsView.tsx +98 -0
- package/src/components/KeybindsDialog.stories.tsx +31 -0
- package/src/components/KeybindsDialog.tsx +103 -0
- package/src/components/MenuBar.stories.tsx +28 -0
- package/src/components/MenuBar.tsx +44 -0
- package/src/components/OverlayScrollbar.stories.tsx +33 -0
- package/src/components/OverlayScrollbar.test.tsx +139 -0
- package/src/components/OverlayScrollbar.tsx +129 -0
- package/src/components/PaneGrid.stories.tsx +84 -0
- package/src/components/PaneGrid.tsx +128 -0
- package/src/components/PanelColumn.stories.tsx +25 -0
- package/src/components/PanelColumn.tsx +95 -0
- package/src/components/ProjectTreeView.stories.tsx +25 -0
- package/src/components/ProjectTreeView.test.tsx +32 -0
- package/src/components/ProjectTreeView.tsx +83 -0
- package/src/components/PromptDialog.stories.tsx +36 -0
- package/src/components/PromptDialog.tsx +87 -0
- package/src/components/PrototypeContentView.stories.tsx +19 -0
- package/src/components/PrototypeContentView.tsx +446 -0
- package/src/components/RootOverlay.stories.tsx +44 -0
- package/src/components/RootOverlay.tsx +35 -0
- package/src/components/ScrollArea.stories.tsx +20 -0
- package/src/components/ScrollArea.tsx +44 -0
- package/src/components/SettingsDialog.stories.tsx +117 -0
- package/src/components/SettingsDialog.tsx +195 -0
- package/src/components/SettingsRows.stories.tsx +18 -0
- package/src/components/SettingsRows.tsx +112 -0
- package/src/components/ShellHeader.stories.tsx +50 -0
- package/src/components/ShellHeader.tsx +137 -0
- package/src/components/ThreadFooter.stories.tsx +30 -0
- package/src/components/ThreadFooter.test.tsx +31 -0
- package/src/components/ThreadFooter.tsx +54 -0
- package/src/components/ThreadTree.stories.tsx +46 -0
- package/src/components/ThreadTree.tsx +219 -0
- package/src/components/ThreadView.selection.test.tsx +812 -0
- package/src/components/ThreadView.slash.test.tsx +158 -0
- package/src/components/ThreadView.stories.tsx +26 -0
- package/src/components/ThreadView.tsx +330 -0
- package/src/components/ThreadsSidebar.stories.tsx +36 -0
- package/src/components/ThreadsSidebar.tsx +71 -0
- package/src/components/Toast.stories.tsx +12 -0
- package/src/components/Toast.tsx +45 -0
- package/src/components/Tooltip.stories.tsx +26 -0
- package/src/components/Tooltip.tsx +72 -0
- package/src/components/TreePane.stories.tsx +106 -0
- package/src/components/TreePane.tsx +157 -0
- package/src/components/WalkWizard.stories.tsx +100 -0
- package/src/components/WalkWizard.tsx +152 -0
- package/src/components/WelcomePlayground.stories.tsx +12 -0
- package/src/components/WelcomePlayground.test.tsx +47 -0
- package/src/components/WelcomePlayground.tsx +140 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +1845 -0
- package/src/components/agent-launcher.stories.tsx +26 -0
- package/src/components/agent-launcher.test.tsx +50 -0
- package/src/components/agent-launcher.tsx +149 -0
- package/src/components/diff-content-view-layout.test.ts +198 -0
- package/src/components/diff-content-view-layout.ts +84 -0
- package/src/components/editor-grid.test.ts +86 -0
- package/src/components/editor-grid.ts +138 -0
- package/src/components/fade-title.test.ts +49 -0
- package/src/components/fade-title.ts +61 -0
- package/src/components/file-tree.test.ts +60 -0
- package/src/components/file-tree.ts +112 -0
- package/src/components/pages/Workspace.stories.tsx +181 -0
- package/src/components/primitives/Button.stories.tsx +32 -0
- package/src/components/primitives/Button.tsx +52 -0
- package/src/components/primitives/Card.stories.tsx +25 -0
- package/src/components/primitives/Card.tsx +60 -0
- package/src/components/primitives/Dialog.stories.tsx +24 -0
- package/src/components/primitives/Dialog.tsx +66 -0
- package/src/components/primitives/IconButton.stories.tsx +36 -0
- package/src/components/primitives/IconButton.tsx +62 -0
- package/src/components/primitives/StatusBar.stories.tsx +11 -0
- package/src/components/primitives/StatusBar.tsx +23 -0
- package/src/components/primitives/Tabs.stories.tsx +29 -0
- package/src/components/primitives/Tabs.tsx +102 -0
- package/src/components/primitives/Toolbar.stories.tsx +17 -0
- package/src/components/primitives/Toolbar.tsx +14 -0
- package/src/components/primitives/Tree.stories.tsx +124 -0
- package/src/components/primitives/Tree.tsx +96 -0
- package/src/components/primitives/frame.test.ts +12 -0
- package/src/components/primitives/frame.ts +14 -0
- package/src/components/primitives/icons.ts +61 -0
- package/src/components/primitives/tree-model.test.ts +99 -0
- package/src/components/primitives/tree-model.ts +137 -0
- package/src/components/quick-actions-editor.stories.tsx +54 -0
- package/src/components/quick-actions-editor.test.tsx +49 -0
- package/src/components/quick-actions-editor.tsx +150 -0
- package/src/components/session-tree.test.ts +134 -0
- package/src/components/session-tree.ts +108 -0
- package/src/components/stories-app.tsx +184 -0
- package/src/components/stories.test.tsx +66 -0
- package/src/components/story-fixtures.ts +102 -0
- package/src/components/story-tree.test.ts +100 -0
- package/src/components/story.ts +142 -0
- package/src/components/syntax-highlight.ts +110 -0
- package/src/components/terminal-pane.test.tsx +40 -0
- package/src/components/terminal-pane.ts +199 -0
- package/src/components/theme-context.stories.tsx +46 -0
- package/src/components/theme-context.tsx +37 -0
- package/src/components/truncate-text.ts +7 -0
- package/src/config.test.ts +461 -31
- package/src/config.ts +389 -57
- package/src/diff-fold.test.ts +78 -0
- package/src/diff-fold.ts +126 -0
- package/src/diff-hunk-curate.test.ts +315 -0
- package/src/diff-hunk-curate.ts +153 -0
- package/src/diff-intraline.test.ts +165 -0
- package/src/diff-intraline.ts +146 -0
- package/src/diff-syntax.test.ts +112 -0
- package/src/diff-syntax.ts +179 -0
- package/src/discussions.ts +79 -0
- package/src/editor.test.ts +149 -0
- package/src/editor.ts +139 -9
- package/src/ghostty-terminal.test.ts +85 -0
- package/src/ghostty-terminal.ts +199 -0
- package/src/herdr.test.ts +9 -34
- package/src/herdr.ts +25 -21
- package/src/index.ts +11 -0
- package/src/integrations.test.ts +41 -0
- package/src/integrations.ts +32 -0
- package/src/intent-dispatch.test.ts +698 -0
- package/src/intent-dispatch.ts +700 -0
- package/src/key-bindings.test.ts +156 -0
- package/src/key-bindings.ts +465 -0
- package/src/keymap.test.ts +611 -0
- package/src/keymap.ts +410 -0
- package/src/kitty-image.test.ts +66 -0
- package/src/kitty-image.ts +138 -0
- package/src/mark-runs.ts +120 -0
- package/src/prototype-browser.test.ts +56 -0
- package/src/prototype-browser.ts +307 -0
- package/src/prototype-pixels.tsx +35 -0
- package/src/pty.test.ts +81 -0
- package/src/pty.ts +245 -0
- package/src/ready-signal.test.ts +85 -0
- package/src/ready-signal.ts +52 -0
- package/src/run.ts +30 -9
- package/src/serve.test.ts +24 -12
- package/src/serve.ts +20 -9
- package/src/share.ts +215 -0
- package/src/skills.test.ts +74 -0
- package/src/skills.ts +68 -0
- package/src/slash-palette.test.ts +54 -0
- package/src/slash-palette.ts +104 -0
- package/src/split-diff.test.ts +110 -0
- package/src/split-diff.ts +149 -0
- package/src/test-support.test.ts +264 -0
- package/src/test-support.ts +355 -0
- package/src/theme-presets.test.ts +51 -0
- package/src/theme-presets.ts +214 -0
- package/src/theme.ts +84 -19
- package/src/thread-chords.test.ts +170 -0
- package/src/thread-chords.ts +169 -0
- package/src/thread-controller.curate.test.ts +438 -0
- package/src/thread-controller.live-diff.test.ts +199 -0
- package/src/thread-controller.share.test.ts +472 -0
- package/src/thread-controller.tree.test.ts +255 -0
- package/src/thread-controller.ts +1509 -0
- package/src/thread-selection.test.ts +210 -0
- package/src/thread-selection.ts +243 -0
- package/src/tree-view.test.ts +128 -0
- package/src/tree-view.ts +148 -0
- package/src/use-annotation-surface.tsx +851 -0
- package/src/use-changes-workbench.ts +153 -0
- package/src/use-frame-measure.ts +38 -0
- package/src/use-settings-dialog.tsx +274 -0
- package/src/use-terminal-virtualizer.ts +233 -0
- package/src/version.ts +5 -0
- package/src/view-diff.test.ts +66 -9
- package/src/view-diff.ts +231 -23
- package/src/view-plan.regression.test.ts +60 -0
- package/src/view-plan.test.ts +406 -0
- package/src/view-plan.ts +570 -0
- package/src/walk.test.ts +121 -0
- package/src/walk.ts +74 -0
- package/src/syntax.test.ts +0 -41
- package/src/syntax.ts +0 -88
- package/src/view.test.ts +0 -157
- package/src/view.ts +0 -312
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { PanelColumn, FileTab } from "./PanelColumn";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Surfaces/PanelColumn" };
|
|
7
|
+
|
|
8
|
+
export const WithFileTab: Story = {
|
|
9
|
+
render: () => (
|
|
10
|
+
<box style={{ width: "100%", height: "100%", flexDirection: "row" }}>
|
|
11
|
+
<PanelColumn
|
|
12
|
+
width={40}
|
|
13
|
+
border="left"
|
|
14
|
+
header={<FileTab label="Welcome" active onClose={() => {}} theme={DARK} />}
|
|
15
|
+
theme={DARK}
|
|
16
|
+
>
|
|
17
|
+
<box style={{ paddingLeft: 1, paddingTop: 1 }}>
|
|
18
|
+
<text fg={DARK.textDim}>panel body</text>
|
|
19
|
+
</box>
|
|
20
|
+
</PanelColumn>
|
|
21
|
+
</box>
|
|
22
|
+
),
|
|
23
|
+
expectedColors: [DARK.accent, DARK.border],
|
|
24
|
+
size: { width: 50, height: 8 },
|
|
25
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// A workbench panel: one full-height grid column with a file-tab header cell on
|
|
2
|
+
// top and a body below. The header row carries the tabs on the left and optional
|
|
3
|
+
// controls on the right, underlined by a thin rule in the same divider gray so the
|
|
4
|
+
// header bottoms join the side rules into one grid. The side border runs from the top.
|
|
5
|
+
|
|
6
|
+
import React, { useState } from "react";
|
|
7
|
+
import type { Theme } from "../theme";
|
|
8
|
+
import { NERD, HEADER_UNDERLINE_CHARS } from "./primitives/icons";
|
|
9
|
+
|
|
10
|
+
export interface FileTabProps {
|
|
11
|
+
label: string;
|
|
12
|
+
active?: boolean;
|
|
13
|
+
/** When set, a close control reveals on hover (a disposable tab). */
|
|
14
|
+
onClose?: () => void;
|
|
15
|
+
theme?: Theme;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A file tab: the label in the accent when active, with a hover-revealed close. */
|
|
19
|
+
export function FileTab({ label, active, onClose, theme }: FileTabProps): React.ReactNode {
|
|
20
|
+
const [hovered, setHovered] = useState(false);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<box
|
|
24
|
+
style={{ flexDirection: "row", paddingRight: 1 }}
|
|
25
|
+
onMouseOver={() => setHovered(true)}
|
|
26
|
+
onMouseOut={() => setHovered(false)}
|
|
27
|
+
>
|
|
28
|
+
<text fg={active ? theme?.accent : theme?.textDim}>{label}</text>
|
|
29
|
+
{onClose ? (
|
|
30
|
+
<box onMouseUp={onClose} style={{ paddingLeft: 1 }}>
|
|
31
|
+
<text fg={theme?.textDim}>{hovered ? NERD.close : " "}</text>
|
|
32
|
+
</box>
|
|
33
|
+
) : null}
|
|
34
|
+
</box>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PanelColumnProps {
|
|
39
|
+
/** Fixed column width; omit for the flex-growing center panel. */
|
|
40
|
+
width?: number;
|
|
41
|
+
/** Draw the divider on this side (the sidebar borders right, the rest border left). */
|
|
42
|
+
border?: "left" | "right";
|
|
43
|
+
/** Tabs and controls for the header cell. */
|
|
44
|
+
header: React.ReactNode;
|
|
45
|
+
/** Right-aligned header controls (toggles, search). */
|
|
46
|
+
headerRight?: React.ReactNode;
|
|
47
|
+
children: React.ReactNode;
|
|
48
|
+
theme?: Theme;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function PanelColumn({
|
|
52
|
+
width,
|
|
53
|
+
border,
|
|
54
|
+
header,
|
|
55
|
+
headerRight,
|
|
56
|
+
children,
|
|
57
|
+
theme,
|
|
58
|
+
}: PanelColumnProps): React.ReactNode {
|
|
59
|
+
return (
|
|
60
|
+
<box
|
|
61
|
+
style={{
|
|
62
|
+
width,
|
|
63
|
+
// grow panes split evenly and clip wide content (a long diff line) rather than stretching
|
|
64
|
+
flexGrow: width === undefined ? 1 : undefined,
|
|
65
|
+
flexBasis: width === undefined ? 0 : undefined,
|
|
66
|
+
minWidth: 0,
|
|
67
|
+
flexDirection: "column",
|
|
68
|
+
borderStyle: "single",
|
|
69
|
+
border: border ? [border] : [],
|
|
70
|
+
borderColor: theme?.border,
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<box
|
|
74
|
+
customBorderChars={HEADER_UNDERLINE_CHARS}
|
|
75
|
+
style={{
|
|
76
|
+
flexDirection: "row",
|
|
77
|
+
height: 2,
|
|
78
|
+
paddingLeft: 1,
|
|
79
|
+
// right controls need the same breathing room as the left, or the rightmost icon renders
|
|
80
|
+
// squeezed against the terminal's last column
|
|
81
|
+
paddingRight: 1,
|
|
82
|
+
backgroundColor: theme?.panel,
|
|
83
|
+
borderStyle: "single",
|
|
84
|
+
border: ["bottom"],
|
|
85
|
+
borderColor: theme?.border,
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
{header}
|
|
89
|
+
<box style={{ flexGrow: 1 }} />
|
|
90
|
+
{headerRight}
|
|
91
|
+
</box>
|
|
92
|
+
<box style={{ flexGrow: 1, flexDirection: "column" }}>{children}</box>
|
|
93
|
+
</box>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { ProjectTreeView } from "./ProjectTreeView";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Surfaces/ProjectTreeView" };
|
|
7
|
+
|
|
8
|
+
const SAMPLE = [
|
|
9
|
+
"packages/client/src/App.tsx",
|
|
10
|
+
"packages/client/src/components/AppShell.tsx",
|
|
11
|
+
"packages/daemon/src/api.ts",
|
|
12
|
+
"README.md",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export const Loaded: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<ProjectTreeView
|
|
18
|
+
loadFiles={() => Promise.resolve(SAMPLE)}
|
|
19
|
+
onSelectFile={() => {}}
|
|
20
|
+
theme={DARK}
|
|
21
|
+
/>
|
|
22
|
+
),
|
|
23
|
+
expectedColors: [DARK.textDim],
|
|
24
|
+
size: { width: 40, height: 16 },
|
|
25
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { testRender } from "@opentui/react/test-utils";
|
|
4
|
+
import { ProjectTreeView } from "./ProjectTreeView";
|
|
5
|
+
import { clickText, waitForText } from "../test-support";
|
|
6
|
+
import { DARK } from "../theme";
|
|
7
|
+
|
|
8
|
+
test("renders the loaded files as a tree and opens a file on click", async () => {
|
|
9
|
+
const opened: string[] = [];
|
|
10
|
+
const setup = await testRender(
|
|
11
|
+
<ProjectTreeView
|
|
12
|
+
loadFiles={() => Promise.resolve(["src/App.tsx", "README.md"])}
|
|
13
|
+
onSelectFile={(path) => opened.push(path)}
|
|
14
|
+
theme={DARK}
|
|
15
|
+
/>,
|
|
16
|
+
{ width: 40, height: 16 },
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
await waitForText(setup, "README.md");
|
|
20
|
+
await clickText(setup, "README.md");
|
|
21
|
+
|
|
22
|
+
expect(opened).toContain("README.md");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("shows an empty hint when there are no files", async () => {
|
|
26
|
+
const setup = await testRender(
|
|
27
|
+
<ProjectTreeView loadFiles={() => Promise.resolve([])} onSelectFile={() => {}} theme={DARK} />,
|
|
28
|
+
{ width: 40, height: 8 },
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
await waitForText(setup, "empty");
|
|
32
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// The Project pane in tree mode: the workspace's tracked files as a collapsible directory tree.
|
|
2
|
+
// Folders start collapsed and expand on click; a file click opens it as a tab. The caller keys this
|
|
3
|
+
// on the session id so switching sessions remounts it and never shows the previous repository's paths.
|
|
4
|
+
|
|
5
|
+
import { ScrollArea } from "./ScrollArea";
|
|
6
|
+
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import type { Theme } from "../theme";
|
|
8
|
+
import { useComponentTheme } from "./theme-context";
|
|
9
|
+
import { Tree } from "./primitives/Tree";
|
|
10
|
+
import { buildPathTree } from "./file-tree";
|
|
11
|
+
|
|
12
|
+
export interface ProjectTreeViewProps {
|
|
13
|
+
loadFiles: () => Promise<string[]>;
|
|
14
|
+
onSelectFile: (path: string) => void;
|
|
15
|
+
theme?: Theme;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ProjectTreeView({
|
|
19
|
+
loadFiles,
|
|
20
|
+
onSelectFile,
|
|
21
|
+
theme,
|
|
22
|
+
}: ProjectTreeViewProps): React.ReactNode {
|
|
23
|
+
const tokens = useComponentTheme(theme);
|
|
24
|
+
const [paths, setPaths] = useState<readonly string[] | null>(null);
|
|
25
|
+
const [expandedIds, setExpandedIds] = useState<ReadonlySet<string>>(new Set());
|
|
26
|
+
const nodes = useMemo(() => (paths ? buildPathTree(paths) : []), [paths]);
|
|
27
|
+
const loadRef = useRef(loadFiles);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
loadRef.current = loadFiles;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
let alive = true;
|
|
34
|
+
void loadRef.current().then(
|
|
35
|
+
(files) => {
|
|
36
|
+
if (alive) setPaths(files);
|
|
37
|
+
},
|
|
38
|
+
() => {
|
|
39
|
+
if (alive) setPaths([]);
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
alive = false;
|
|
45
|
+
};
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
if (paths === null) {
|
|
49
|
+
return (
|
|
50
|
+
<box style={{ flexGrow: 1, paddingLeft: 1, paddingTop: 1 }}>
|
|
51
|
+
<text fg={tokens.textDim}>loading...</text>
|
|
52
|
+
</box>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (paths.length === 0) {
|
|
56
|
+
return (
|
|
57
|
+
<box style={{ flexGrow: 1, paddingLeft: 1, paddingTop: 1 }}>
|
|
58
|
+
<text fg={tokens.textDim}>empty</text>
|
|
59
|
+
</box>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<ScrollArea>
|
|
65
|
+
<Tree
|
|
66
|
+
nodes={nodes}
|
|
67
|
+
expandedIds={expandedIds}
|
|
68
|
+
onSelect={onSelectFile}
|
|
69
|
+
onToggle={(id) =>
|
|
70
|
+
setExpandedIds((current) => {
|
|
71
|
+
const next = new Set(current);
|
|
72
|
+
|
|
73
|
+
if (next.has(id)) next.delete(id);
|
|
74
|
+
else next.add(id);
|
|
75
|
+
|
|
76
|
+
return next;
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
theme={theme}
|
|
80
|
+
/>
|
|
81
|
+
</ScrollArea>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { PromptDialog } from "./PromptDialog";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Overlays/PromptDialog" };
|
|
7
|
+
|
|
8
|
+
export const RenameAuthor: Story = {
|
|
9
|
+
render: () => (
|
|
10
|
+
<PromptDialog
|
|
11
|
+
isOpen
|
|
12
|
+
title=" Rename author "
|
|
13
|
+
label="Display name for this collaborator:"
|
|
14
|
+
value="Robin"
|
|
15
|
+
placeholder="their name"
|
|
16
|
+
onInput={() => {}}
|
|
17
|
+
/>
|
|
18
|
+
),
|
|
19
|
+
expectedColors: [DARK.elevated, DARK.accent],
|
|
20
|
+
size: { width: 64, height: 16 },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const NameYourself: Story = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<PromptDialog
|
|
26
|
+
isOpen
|
|
27
|
+
title=" Welcome "
|
|
28
|
+
label="Your name (optional) - it attributes the notes you leave:"
|
|
29
|
+
value=""
|
|
30
|
+
placeholder="your name"
|
|
31
|
+
onInput={() => {}}
|
|
32
|
+
/>
|
|
33
|
+
),
|
|
34
|
+
expectedColors: [DARK.elevated],
|
|
35
|
+
size: { width: 64, height: 16 },
|
|
36
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A centered single-field text prompt on the shared Dialog overlay. The focused
|
|
3
|
+
* input owns typing/cursor natively; the reducer's `prompt` overlay drives ⏎
|
|
4
|
+
* save / esc cancel (like the composer). Used to rename an author and to ask a
|
|
5
|
+
* collaborator for their name.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useEffect, useRef } from "react";
|
|
9
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
10
|
+
import type { KeyBinding, TextareaRenderable } from "@opentui/core";
|
|
11
|
+
import type { Theme } from "../theme";
|
|
12
|
+
import { useComponentTheme } from "./theme-context";
|
|
13
|
+
import { Dialog } from "./primitives/Dialog";
|
|
14
|
+
|
|
15
|
+
// ⏎ submits (suppresses the textarea's default newline); the grammar owns save.
|
|
16
|
+
const PROMPT_KEY_BINDINGS: KeyBinding[] = [{ name: "return", action: "submit" }];
|
|
17
|
+
|
|
18
|
+
export interface PromptDialogProps {
|
|
19
|
+
isOpen: boolean;
|
|
20
|
+
title?: string;
|
|
21
|
+
label: string;
|
|
22
|
+
value: string;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
onInput: (text: string) => void;
|
|
25
|
+
theme?: Theme;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function PromptDialog({
|
|
29
|
+
isOpen,
|
|
30
|
+
title,
|
|
31
|
+
label,
|
|
32
|
+
value,
|
|
33
|
+
placeholder,
|
|
34
|
+
onInput,
|
|
35
|
+
theme,
|
|
36
|
+
}: PromptDialogProps): React.ReactNode {
|
|
37
|
+
const tokens = useComponentTheme(theme);
|
|
38
|
+
const { width: terminalWidth } = useTerminalDimensions();
|
|
39
|
+
const inputRef = useRef<TextareaRenderable | null>(null);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
// open with the caret after the seeded value, like a text field
|
|
43
|
+
if (inputRef.current) inputRef.current.cursorOffset = value.length;
|
|
44
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
45
|
+
}, []);
|
|
46
|
+
if (!isOpen) return null;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Dialog
|
|
50
|
+
isOpen
|
|
51
|
+
title={title}
|
|
52
|
+
width={Math.min(54, terminalWidth - 6)}
|
|
53
|
+
height={7}
|
|
54
|
+
background={tokens.elevated}
|
|
55
|
+
theme={theme}
|
|
56
|
+
>
|
|
57
|
+
<box
|
|
58
|
+
style={{
|
|
59
|
+
flexDirection: "column",
|
|
60
|
+
flexGrow: 1,
|
|
61
|
+
paddingLeft: 1,
|
|
62
|
+
paddingRight: 1,
|
|
63
|
+
paddingTop: 1,
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<text fg={tokens.textDim}>{label}</text>
|
|
67
|
+
<textarea
|
|
68
|
+
ref={inputRef}
|
|
69
|
+
focused
|
|
70
|
+
initialValue={value}
|
|
71
|
+
placeholder={placeholder}
|
|
72
|
+
keyBindings={PROMPT_KEY_BINDINGS}
|
|
73
|
+
onContentChange={() => onInput(inputRef.current?.plainText ?? "")}
|
|
74
|
+
style={{
|
|
75
|
+
height: 1,
|
|
76
|
+
backgroundColor: tokens.elevated,
|
|
77
|
+
focusedBackgroundColor: tokens.elevated,
|
|
78
|
+
textColor: tokens.text,
|
|
79
|
+
focusedTextColor: tokens.text,
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
<box style={{ flexGrow: 1 }} />
|
|
83
|
+
<text fg={tokens.textDim}>enter save · esc cancel</text>
|
|
84
|
+
</box>
|
|
85
|
+
</Dialog>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { PrototypeContentView } from "./PrototypeContentView";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "Surfaces/PrototypeContentView" };
|
|
7
|
+
|
|
8
|
+
// The story renderer reports no kitty graphics, so the view shows its fallback.
|
|
9
|
+
export const Unsupported: Story = {
|
|
10
|
+
render: () => (
|
|
11
|
+
<PrototypeContentView
|
|
12
|
+
prototypePath="/tmp/example.html"
|
|
13
|
+
canComment={true}
|
|
14
|
+
onCommentElement={() => {}}
|
|
15
|
+
/>
|
|
16
|
+
),
|
|
17
|
+
expectedColors: [DARK.textDim],
|
|
18
|
+
size: { width: 80, height: 24 },
|
|
19
|
+
};
|