@f0rbit/overview 0.1.0 → 0.2.1
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/bin/overview +10 -0
- package/dist/overview.js +10361 -0
- package/package.json +22 -15
- package/bunfig.toml +0 -7
- package/packages/core/__tests__/concurrency.test.ts +0 -111
- package/packages/core/__tests__/helpers.ts +0 -60
- package/packages/core/__tests__/integration/git-status.test.ts +0 -62
- package/packages/core/__tests__/integration/scanner.test.ts +0 -140
- package/packages/core/__tests__/ocn.test.ts +0 -164
- package/packages/core/package.json +0 -13
- package/packages/core/src/cache.ts +0 -31
- package/packages/core/src/concurrency.ts +0 -44
- package/packages/core/src/devpad.ts +0 -61
- package/packages/core/src/git-graph.ts +0 -54
- package/packages/core/src/git-stats.ts +0 -201
- package/packages/core/src/git-status.ts +0 -316
- package/packages/core/src/github.ts +0 -286
- package/packages/core/src/index.ts +0 -58
- package/packages/core/src/ocn.ts +0 -74
- package/packages/core/src/scanner.ts +0 -118
- package/packages/core/src/types.ts +0 -199
- package/packages/core/src/watcher.ts +0 -128
- package/packages/core/src/worktree.ts +0 -80
- package/packages/core/tsconfig.json +0 -5
- package/packages/render/bunfig.toml +0 -8
- package/packages/render/jsx-runtime.d.ts +0 -3
- package/packages/render/package.json +0 -18
- package/packages/render/src/components/__tests__/scrollbox-height.test.tsx +0 -780
- package/packages/render/src/components/__tests__/widget-container.integration.test.tsx +0 -304
- package/packages/render/src/components/git-graph.tsx +0 -127
- package/packages/render/src/components/help-overlay.tsx +0 -108
- package/packages/render/src/components/index.ts +0 -7
- package/packages/render/src/components/repo-list.tsx +0 -127
- package/packages/render/src/components/stats-panel.tsx +0 -116
- package/packages/render/src/components/status-badge.tsx +0 -70
- package/packages/render/src/components/status-bar.tsx +0 -56
- package/packages/render/src/components/widget-container.tsx +0 -286
- package/packages/render/src/components/widgets/__tests__/widget-rendering.test.tsx +0 -326
- package/packages/render/src/components/widgets/branch-list.tsx +0 -93
- package/packages/render/src/components/widgets/commit-activity.tsx +0 -112
- package/packages/render/src/components/widgets/devpad-milestones.tsx +0 -88
- package/packages/render/src/components/widgets/devpad-tasks.tsx +0 -81
- package/packages/render/src/components/widgets/file-changes.tsx +0 -78
- package/packages/render/src/components/widgets/git-status.tsx +0 -125
- package/packages/render/src/components/widgets/github-ci.tsx +0 -98
- package/packages/render/src/components/widgets/github-issues.tsx +0 -101
- package/packages/render/src/components/widgets/github-prs.tsx +0 -119
- package/packages/render/src/components/widgets/github-release.tsx +0 -73
- package/packages/render/src/components/widgets/index.ts +0 -12
- package/packages/render/src/components/widgets/recent-commits.tsx +0 -64
- package/packages/render/src/components/widgets/registry.ts +0 -23
- package/packages/render/src/components/widgets/repo-meta.tsx +0 -80
- package/packages/render/src/config/index.ts +0 -104
- package/packages/render/src/lib/__tests__/fetch-context.test.ts +0 -200
- package/packages/render/src/lib/__tests__/widget-grid.test.ts +0 -665
- package/packages/render/src/lib/actions.ts +0 -68
- package/packages/render/src/lib/fetch-context.ts +0 -102
- package/packages/render/src/lib/filter.ts +0 -94
- package/packages/render/src/lib/format.ts +0 -36
- package/packages/render/src/lib/use-devpad.ts +0 -167
- package/packages/render/src/lib/use-github.ts +0 -75
- package/packages/render/src/lib/widget-grid.ts +0 -204
- package/packages/render/src/lib/widget-state.ts +0 -96
- package/packages/render/src/overview.tsx +0 -16
- package/packages/render/src/screens/index.ts +0 -1
- package/packages/render/src/screens/main-screen.tsx +0 -410
- package/packages/render/src/theme/index.ts +0 -37
- package/packages/render/tsconfig.json +0 -9
- package/tsconfig.json +0 -23
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { For, Show, Switch, Match, createMemo } from "solid-js";
|
|
2
|
-
import type { WidgetRenderProps, RepoStatus, DevpadTask } from "@overview/core";
|
|
3
|
-
import { registerWidget } from "./registry";
|
|
4
|
-
import { theme } from "../../theme";
|
|
5
|
-
import { truncate } from "../../lib/format";
|
|
6
|
-
import { useDevpad } from "../../lib/use-devpad";
|
|
7
|
-
|
|
8
|
-
const size_hint = { span: "full" as const, min_height: 2 };
|
|
9
|
-
const MAX_VISIBLE = 10;
|
|
10
|
-
|
|
11
|
-
const priority_indicator: Record<DevpadTask["priority"], { char: string; color: string }> = {
|
|
12
|
-
HIGH: { char: "!", color: theme.red },
|
|
13
|
-
MEDIUM: { char: "·", color: theme.yellow },
|
|
14
|
-
LOW: { char: "·", color: theme.fg_dim },
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const progress_indicator: Record<DevpadTask["progress"], { char: string; color: string }> = {
|
|
18
|
-
UNSTARTED: { char: "○", color: theme.fg_dim },
|
|
19
|
-
IN_PROGRESS: { char: "◑", color: theme.blue },
|
|
20
|
-
COMPLETED: { char: "●", color: theme.green },
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
function DevpadTasksWidget(props: WidgetRenderProps & { status: RepoStatus | null }) {
|
|
24
|
-
const remote_url = createMemo(() => props.status?.remote_url ?? null);
|
|
25
|
-
const repo_name = createMemo(() => props.status?.name ?? "");
|
|
26
|
-
const devpad = useDevpad(remote_url, repo_name);
|
|
27
|
-
|
|
28
|
-
const tasks = createMemo(() => devpad.data()?.tasks ?? []);
|
|
29
|
-
const visible = () => tasks().slice(0, MAX_VISIBLE);
|
|
30
|
-
const overflow = () => Math.max(0, tasks().length - MAX_VISIBLE);
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<box flexDirection="column">
|
|
34
|
-
<Switch>
|
|
35
|
-
<Match when={devpad.error()}>
|
|
36
|
-
{(err) => <text fg={theme.fg_dim} content={err()} />}
|
|
37
|
-
</Match>
|
|
38
|
-
<Match when={devpad.loading() && !devpad.data()}>
|
|
39
|
-
<text fg={theme.fg_dim} content="loading…" />
|
|
40
|
-
</Match>
|
|
41
|
-
<Match when={!devpad.data()?.project && devpad.data() !== null}>
|
|
42
|
-
<text fg={theme.fg_dim} content="no devpad project" />
|
|
43
|
-
</Match>
|
|
44
|
-
<Match when={true}>
|
|
45
|
-
<text fg={theme.fg_dark} content={`Tasks (${tasks().length})`} />
|
|
46
|
-
<Show
|
|
47
|
-
when={tasks().length > 0}
|
|
48
|
-
fallback={<text fg={theme.fg_dim} content="(no open tasks)" />}
|
|
49
|
-
>
|
|
50
|
-
<For each={visible()}>
|
|
51
|
-
{(task) => {
|
|
52
|
-
const pi = () => priority_indicator[task.priority];
|
|
53
|
-
const si = () => progress_indicator[task.progress];
|
|
54
|
-
const available = () => Math.max(1, props.width - 4);
|
|
55
|
-
return (
|
|
56
|
-
<box flexDirection="row" height={1}>
|
|
57
|
-
<text fg={si().color} content={`${si().char} `} />
|
|
58
|
-
<text fg={pi().color} content={`${pi().char} `} />
|
|
59
|
-
<text content={truncate(task.title, available())} />
|
|
60
|
-
</box>
|
|
61
|
-
);
|
|
62
|
-
}}
|
|
63
|
-
</For>
|
|
64
|
-
<Show when={overflow() > 0}>
|
|
65
|
-
<text fg={theme.fg_dim} content={`+${overflow()} more`} />
|
|
66
|
-
</Show>
|
|
67
|
-
</Show>
|
|
68
|
-
</Match>
|
|
69
|
-
</Switch>
|
|
70
|
-
</box>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
registerWidget({
|
|
75
|
-
id: "devpad-tasks",
|
|
76
|
-
label: "Devpad Tasks",
|
|
77
|
-
size_hint,
|
|
78
|
-
component: DevpadTasksWidget,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
export { DevpadTasksWidget };
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { For, Show } from "solid-js";
|
|
2
|
-
import type { WidgetRenderProps, RepoStatus, GitFileChange } from "@overview/core";
|
|
3
|
-
import { registerWidget } from "./registry";
|
|
4
|
-
import { theme } from "../../theme";
|
|
5
|
-
import { truncate } from "../../lib/format";
|
|
6
|
-
|
|
7
|
-
const size_hint = { span: "half" as const, min_height: 2 };
|
|
8
|
-
|
|
9
|
-
function statusIcon(change: GitFileChange): { icon: string; color: string } {
|
|
10
|
-
const icons: Record<string, { icon: string; color: string }> = {
|
|
11
|
-
modified: { icon: "M", color: theme.status.modified },
|
|
12
|
-
added: { icon: "A", color: theme.green },
|
|
13
|
-
deleted: { icon: "D", color: theme.red },
|
|
14
|
-
renamed: { icon: "R", color: theme.cyan },
|
|
15
|
-
copied: { icon: "C", color: theme.magenta },
|
|
16
|
-
untracked: { icon: "?", color: theme.status.untracked },
|
|
17
|
-
ignored: { icon: "!", color: theme.fg_dim },
|
|
18
|
-
conflicted: { icon: "!", color: theme.status.conflict },
|
|
19
|
-
};
|
|
20
|
-
return icons[change.status] ?? { icon: " ", color: theme.fg };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function basename(path: string): string {
|
|
24
|
-
const parts = path.split("/");
|
|
25
|
-
return parts[parts.length - 1] ?? path;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function FileChangesWidget(props: WidgetRenderProps & { status: RepoStatus | null }) {
|
|
29
|
-
const changes = () => props.status?.changes ?? [];
|
|
30
|
-
const header = () => `File Changes (${changes().length})`;
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<box flexDirection="column">
|
|
34
|
-
<Show
|
|
35
|
-
when={props.status}
|
|
36
|
-
fallback={
|
|
37
|
-
<text fg={theme.fg_dim} content="no repo selected" />
|
|
38
|
-
}
|
|
39
|
-
>
|
|
40
|
-
<box flexDirection="row" height={1}>
|
|
41
|
-
<text fg={theme.fg} content={header()} />
|
|
42
|
-
</box>
|
|
43
|
-
|
|
44
|
-
<Show
|
|
45
|
-
when={changes().length > 0}
|
|
46
|
-
fallback={
|
|
47
|
-
<text fg={theme.fg_dim} content="(no changes)" />
|
|
48
|
-
}
|
|
49
|
-
>
|
|
50
|
-
<For each={changes()}>
|
|
51
|
-
{(change) => {
|
|
52
|
-
const si = () => statusIcon(change);
|
|
53
|
-
const staged_prefix = () => change.staged ? "+" : " ";
|
|
54
|
-
const label = () => truncate(basename(change.path), props.width - 5);
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<box flexDirection="row" height={1}>
|
|
58
|
-
<text fg={change.staged ? theme.green : si().color} content={staged_prefix()} />
|
|
59
|
-
<text fg={si().color} content={si().icon + " "} />
|
|
60
|
-
<text fg={theme.fg} content={label()} />
|
|
61
|
-
</box>
|
|
62
|
-
);
|
|
63
|
-
}}
|
|
64
|
-
</For>
|
|
65
|
-
</Show>
|
|
66
|
-
</Show>
|
|
67
|
-
</box>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
registerWidget({
|
|
72
|
-
id: "file-changes",
|
|
73
|
-
label: "File Changes",
|
|
74
|
-
size_hint,
|
|
75
|
-
component: FileChangesWidget,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
export { FileChangesWidget };
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { Show } from "solid-js";
|
|
2
|
-
import type { WidgetRenderProps, RepoStatus, HealthStatus } from "@overview/core";
|
|
3
|
-
import { registerWidget } from "./registry";
|
|
4
|
-
import { theme } from "../../theme";
|
|
5
|
-
import { formatRelativeTime } from "../../lib/format";
|
|
6
|
-
|
|
7
|
-
const size_hint = { span: "third" as const, min_height: 2 };
|
|
8
|
-
|
|
9
|
-
const health_color: Record<HealthStatus, string> = {
|
|
10
|
-
clean: theme.green,
|
|
11
|
-
dirty: theme.yellow,
|
|
12
|
-
ahead: theme.yellow,
|
|
13
|
-
behind: theme.cyan,
|
|
14
|
-
diverged: theme.orange,
|
|
15
|
-
conflict: theme.red,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
function healthIndicator(health: HealthStatus): string {
|
|
19
|
-
const labels: Record<HealthStatus, string> = {
|
|
20
|
-
clean: "●",
|
|
21
|
-
dirty: "●",
|
|
22
|
-
ahead: "↑",
|
|
23
|
-
behind: "↓",
|
|
24
|
-
diverged: "⇅",
|
|
25
|
-
conflict: "✗",
|
|
26
|
-
};
|
|
27
|
-
return labels[health];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function GitStatusWidget(props: WidgetRenderProps & { status: RepoStatus | null }) {
|
|
31
|
-
const s = () => props.status;
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<box flexDirection="column">
|
|
35
|
-
<Show
|
|
36
|
-
when={s()}
|
|
37
|
-
fallback={
|
|
38
|
-
<text fg={theme.fg_dim} content="no repo selected" />
|
|
39
|
-
}
|
|
40
|
-
>
|
|
41
|
-
{(status) => {
|
|
42
|
-
const color = () => health_color[status().health];
|
|
43
|
-
const has_sync = () => status().ahead > 0 || status().behind > 0;
|
|
44
|
-
const has_wt = () =>
|
|
45
|
-
status().modified_count > 0 ||
|
|
46
|
-
status().staged_count > 0;
|
|
47
|
-
const has_extra = () =>
|
|
48
|
-
status().untracked_count > 0 ||
|
|
49
|
-
status().conflict_count > 0;
|
|
50
|
-
const has_stash = () => status().stash_count > 0;
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<>
|
|
54
|
-
{/* Line 1: branch + health */}
|
|
55
|
-
<box flexDirection="row" height={1} gap={1}>
|
|
56
|
-
<text fg={color()} content={healthIndicator(status().health)} />
|
|
57
|
-
<text fg={theme.cyan} content={status().current_branch} />
|
|
58
|
-
</box>
|
|
59
|
-
|
|
60
|
-
{/* Line 2: ahead/behind or up to date */}
|
|
61
|
-
<Show
|
|
62
|
-
when={has_sync()}
|
|
63
|
-
fallback={
|
|
64
|
-
<box flexDirection="row" height={1}>
|
|
65
|
-
<text fg={theme.fg_dim} content="up to date" />
|
|
66
|
-
</box>
|
|
67
|
-
}
|
|
68
|
-
>
|
|
69
|
-
<box flexDirection="row" height={1} gap={2}>
|
|
70
|
-
<Show when={status().ahead > 0}>
|
|
71
|
-
<text fg={theme.status.ahead} content={`↑${status().ahead} ahead`} />
|
|
72
|
-
</Show>
|
|
73
|
-
<Show when={status().behind > 0}>
|
|
74
|
-
<text fg={theme.status.behind} content={`↓${status().behind} behind`} />
|
|
75
|
-
</Show>
|
|
76
|
-
</box>
|
|
77
|
-
</Show>
|
|
78
|
-
|
|
79
|
-
{/* Line 3: modified + staged */}
|
|
80
|
-
<Show when={has_wt()}>
|
|
81
|
-
<box flexDirection="row" height={1} gap={2}>
|
|
82
|
-
<Show when={status().modified_count > 0}>
|
|
83
|
-
<text fg={theme.status.modified} content={`~${status().modified_count} mod`} />
|
|
84
|
-
</Show>
|
|
85
|
-
<Show when={status().staged_count > 0}>
|
|
86
|
-
<text fg={theme.green} content={`+${status().staged_count} staged`} />
|
|
87
|
-
</Show>
|
|
88
|
-
</box>
|
|
89
|
-
</Show>
|
|
90
|
-
|
|
91
|
-
{/* Line 4: untracked + conflicts */}
|
|
92
|
-
<Show when={has_extra()}>
|
|
93
|
-
<box flexDirection="row" height={1} gap={2}>
|
|
94
|
-
<Show when={status().untracked_count > 0}>
|
|
95
|
-
<text fg={theme.status.untracked} content={`?${status().untracked_count} untracked`} />
|
|
96
|
-
</Show>
|
|
97
|
-
<Show when={status().conflict_count > 0}>
|
|
98
|
-
<text fg={theme.status.conflict} content={`!${status().conflict_count} conflicts`} />
|
|
99
|
-
</Show>
|
|
100
|
-
</box>
|
|
101
|
-
</Show>
|
|
102
|
-
|
|
103
|
-
{/* Line 5: stash + last commit */}
|
|
104
|
-
<box flexDirection="row" height={1} gap={2}>
|
|
105
|
-
<Show when={has_stash()}>
|
|
106
|
-
<text fg={theme.status.stash} content={`✂ ${status().stash_count} stash`} />
|
|
107
|
-
</Show>
|
|
108
|
-
<text fg={theme.fg_dim} content={formatRelativeTime(status().head_time)} />
|
|
109
|
-
</box>
|
|
110
|
-
</>
|
|
111
|
-
);
|
|
112
|
-
}}
|
|
113
|
-
</Show>
|
|
114
|
-
</box>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
registerWidget({
|
|
119
|
-
id: "git-status",
|
|
120
|
-
label: "Git Status",
|
|
121
|
-
size_hint,
|
|
122
|
-
component: GitStatusWidget,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
export { GitStatusWidget };
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { For, Show, createMemo } from "solid-js";
|
|
2
|
-
import type { WidgetRenderProps, RepoStatus, GithubWorkflowRun } from "@overview/core";
|
|
3
|
-
import { registerWidget } from "./registry";
|
|
4
|
-
import { theme } from "../../theme";
|
|
5
|
-
import { truncate } from "../../lib/format";
|
|
6
|
-
import { useGithub } from "../../lib/use-github";
|
|
7
|
-
|
|
8
|
-
const size_hint = { span: "third" as const, min_height: 1 };
|
|
9
|
-
|
|
10
|
-
function statusIcon(run: GithubWorkflowRun): { icon: string; color: string } {
|
|
11
|
-
if (run.conclusion === "success") return { icon: "✓", color: theme.green };
|
|
12
|
-
if (run.conclusion === "failure") return { icon: "✗", color: theme.red };
|
|
13
|
-
if (run.conclusion === "cancelled") return { icon: "⊘", color: theme.fg_dim };
|
|
14
|
-
if (run.status === "in_progress" || run.status === "queued") return { icon: "◌", color: theme.yellow };
|
|
15
|
-
return { icon: "◌", color: theme.fg_dim };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function GithubCIWidget(props: WidgetRenderProps & { status: RepoStatus | null }) {
|
|
19
|
-
const repo_path = createMemo(() => props.status?.path ?? null);
|
|
20
|
-
const remote_url = createMemo(() => props.status?.remote_url ?? null);
|
|
21
|
-
const github = useGithub(repo_path, remote_url);
|
|
22
|
-
|
|
23
|
-
const runs = createMemo(() => github.data()?.ci_runs ?? []);
|
|
24
|
-
|
|
25
|
-
const all_green = createMemo(() => {
|
|
26
|
-
const r = runs();
|
|
27
|
-
return r.length > 0 && r.every((run) => run.conclusion === "success");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const visible_runs = createMemo(() => {
|
|
31
|
-
return runs().slice(0, 6);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<box flexDirection="column">
|
|
36
|
-
{/* gh CLI not available */}
|
|
37
|
-
<Show when={github.error()?.kind === "gh_cli_not_found"}>
|
|
38
|
-
<box flexDirection="row" height={1} gap={1}>
|
|
39
|
-
<text fg={theme.fg_dim} content="gh not available" />
|
|
40
|
-
<text fg={theme.blue} content="https://cli.github.com" />
|
|
41
|
-
</box>
|
|
42
|
-
</Show>
|
|
43
|
-
|
|
44
|
-
{/* Not a GitHub repo */}
|
|
45
|
-
<Show when={github.error()?.kind === "not_github_repo"}>
|
|
46
|
-
<text fg={theme.fg_dim} content="not a GitHub repo" />
|
|
47
|
-
</Show>
|
|
48
|
-
|
|
49
|
-
{/* Loading */}
|
|
50
|
-
<Show when={!github.error() && github.loading()}>
|
|
51
|
-
<text fg={theme.fg_dim} content="loading…" />
|
|
52
|
-
</Show>
|
|
53
|
-
|
|
54
|
-
{/* No error, not loading — show data */}
|
|
55
|
-
<Show when={!github.error() && !github.loading()}>
|
|
56
|
-
<Show when={runs().length === 0}>
|
|
57
|
-
<text fg={theme.fg_dim} content="no CI runs" />
|
|
58
|
-
</Show>
|
|
59
|
-
|
|
60
|
-
{/* All green: collapse to single line */}
|
|
61
|
-
<Show when={all_green()}>
|
|
62
|
-
<text fg={theme.green} content="CI: all green ✓" />
|
|
63
|
-
</Show>
|
|
64
|
-
|
|
65
|
-
{/* Mixed results: show each run */}
|
|
66
|
-
<Show when={!all_green() && runs().length > 0}>
|
|
67
|
-
<For each={visible_runs()}>
|
|
68
|
-
{(run) => {
|
|
69
|
-
const si = () => statusIcon(run);
|
|
70
|
-
const name_budget = () => {
|
|
71
|
-
const branch_str = ` ${run.head_branch}`;
|
|
72
|
-
return Math.max(1, props.width - 2 - branch_str.length);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<box flexDirection="row" height={1}>
|
|
77
|
-
<text fg={si().color} content={si().icon} />
|
|
78
|
-
<text content={" "} />
|
|
79
|
-
<text content={truncate(run.name, name_budget())} />
|
|
80
|
-
<text fg={theme.fg_dim} content={` ${run.head_branch}`} />
|
|
81
|
-
</box>
|
|
82
|
-
);
|
|
83
|
-
}}
|
|
84
|
-
</For>
|
|
85
|
-
</Show>
|
|
86
|
-
</Show>
|
|
87
|
-
</box>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
registerWidget({
|
|
92
|
-
id: "github-ci",
|
|
93
|
-
label: "GitHub CI",
|
|
94
|
-
size_hint,
|
|
95
|
-
component: GithubCIWidget,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
export { GithubCIWidget };
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { For, Show, Switch, Match, createMemo } from "solid-js";
|
|
2
|
-
import type { WidgetRenderProps, RepoStatus, GithubIssue } from "@overview/core";
|
|
3
|
-
import { registerWidget } from "./registry";
|
|
4
|
-
import { theme } from "../../theme";
|
|
5
|
-
import { truncate } from "../../lib/format";
|
|
6
|
-
import { useGithub } from "../../lib/use-github";
|
|
7
|
-
|
|
8
|
-
const size_hint = { span: "half" as const, min_height: 2 };
|
|
9
|
-
const MAX_VISIBLE = 10;
|
|
10
|
-
|
|
11
|
-
const label_colors = [
|
|
12
|
-
theme.cyan,
|
|
13
|
-
theme.magenta,
|
|
14
|
-
theme.yellow,
|
|
15
|
-
theme.green,
|
|
16
|
-
theme.red,
|
|
17
|
-
theme.orange,
|
|
18
|
-
] as const;
|
|
19
|
-
|
|
20
|
-
function labelColor(index: number): string {
|
|
21
|
-
return label_colors[index % label_colors.length]!;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function GithubIssuesWidget(props: WidgetRenderProps & { status: RepoStatus | null }) {
|
|
25
|
-
const repo_path = createMemo(() => props.status?.path ?? null);
|
|
26
|
-
const remote_url = createMemo(() => props.status?.remote_url ?? null);
|
|
27
|
-
const github = useGithub(repo_path, remote_url);
|
|
28
|
-
|
|
29
|
-
const issues = createMemo(() => github.data()?.issues ?? []);
|
|
30
|
-
const visible = () => issues().slice(0, MAX_VISIBLE);
|
|
31
|
-
const overflow = () => Math.max(0, issues().length - MAX_VISIBLE);
|
|
32
|
-
|
|
33
|
-
const formatIssueLine = (issue: GithubIssue) => {
|
|
34
|
-
const number_str = `#${issue.number}`;
|
|
35
|
-
const dots_len = issue.labels.length > 0 ? issue.labels.length * 2 : 0;
|
|
36
|
-
const available = props.width - 2 - number_str.length - 1 - dots_len;
|
|
37
|
-
const title = truncate(issue.title, Math.max(1, available));
|
|
38
|
-
return { number_str, title };
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<box flexDirection="column">
|
|
43
|
-
<Switch>
|
|
44
|
-
<Match when={github.error()?.kind === "gh_cli_not_found"}>
|
|
45
|
-
<text fg={theme.fg_dim} content="gh not available" />
|
|
46
|
-
<text fg={theme.fg_dim} content="install: https://cli.github.com" />
|
|
47
|
-
</Match>
|
|
48
|
-
<Match when={github.error()?.kind === "not_github_repo"}>
|
|
49
|
-
<text fg={theme.fg_dim} content="not a GitHub repo" />
|
|
50
|
-
</Match>
|
|
51
|
-
<Match when={github.error()?.kind === "gh_auth_required"}>
|
|
52
|
-
<text fg={theme.fg_dim} content="gh auth required" />
|
|
53
|
-
</Match>
|
|
54
|
-
<Match when={github.error()}>
|
|
55
|
-
<text fg={theme.fg_dim} content="GitHub error" />
|
|
56
|
-
</Match>
|
|
57
|
-
<Match when={github.loading() && !github.data()}>
|
|
58
|
-
<text fg={theme.fg_dim} content="loading…" />
|
|
59
|
-
</Match>
|
|
60
|
-
<Match when={true}>
|
|
61
|
-
<text fg={theme.fg_dark} content={`Issues (${issues().length})`} />
|
|
62
|
-
<Show
|
|
63
|
-
when={issues().length > 0}
|
|
64
|
-
fallback={
|
|
65
|
-
<text fg={theme.fg_dim} content="(no issues)" />
|
|
66
|
-
}
|
|
67
|
-
>
|
|
68
|
-
<For each={visible()}>
|
|
69
|
-
{(issue) => {
|
|
70
|
-
const line = () => formatIssueLine(issue);
|
|
71
|
-
return (
|
|
72
|
-
<box flexDirection="row" height={1}>
|
|
73
|
-
<text fg={theme.fg_dim} content={`${line().number_str} `} />
|
|
74
|
-
<text fg={theme.fg} content={line().title} />
|
|
75
|
-
<For each={issue.labels}>
|
|
76
|
-
{(_, i) => (
|
|
77
|
-
<text fg={labelColor(i())} content=" ●" />
|
|
78
|
-
)}
|
|
79
|
-
</For>
|
|
80
|
-
</box>
|
|
81
|
-
);
|
|
82
|
-
}}
|
|
83
|
-
</For>
|
|
84
|
-
<Show when={overflow() > 0}>
|
|
85
|
-
<text fg={theme.fg_dim} content={`+${overflow()} more`} />
|
|
86
|
-
</Show>
|
|
87
|
-
</Show>
|
|
88
|
-
</Match>
|
|
89
|
-
</Switch>
|
|
90
|
-
</box>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
registerWidget({
|
|
95
|
-
id: "github-issues",
|
|
96
|
-
label: "GitHub Issues",
|
|
97
|
-
size_hint,
|
|
98
|
-
component: GithubIssuesWidget,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
export { GithubIssuesWidget };
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { For, Show, Switch, Match, createMemo } from "solid-js";
|
|
2
|
-
import type { WidgetRenderProps, RepoStatus, GithubPR } from "@overview/core";
|
|
3
|
-
import { registerWidget } from "./registry";
|
|
4
|
-
import { theme } from "../../theme";
|
|
5
|
-
import { truncate } from "../../lib/format";
|
|
6
|
-
import { useGithub } from "../../lib/use-github";
|
|
7
|
-
|
|
8
|
-
const size_hint = { span: "half" as const, min_height: 2 };
|
|
9
|
-
const MAX_VISIBLE = 10;
|
|
10
|
-
|
|
11
|
-
function statusIcon(pr: GithubPR): { char: string; color: string } {
|
|
12
|
-
if (pr.is_draft) return { char: "●", color: theme.orange };
|
|
13
|
-
if (pr.state === "closed" || pr.state === "CLOSED") return { char: "●", color: theme.fg_dim };
|
|
14
|
-
return { char: "●", color: theme.green };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function ciIndicator(pr: GithubPR): { char: string; color: string } | null {
|
|
18
|
-
if (pr.ci_status === "success") return { char: "✓", color: theme.green };
|
|
19
|
-
if (pr.ci_status === "failure") return { char: "✗", color: theme.red };
|
|
20
|
-
if (pr.ci_status === "pending") return { char: "◌", color: theme.yellow };
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function reviewIndicator(pr: GithubPR): { char: string; color: string } | null {
|
|
25
|
-
const d = pr.review_decision;
|
|
26
|
-
if (d === "APPROVED") return { char: "R", color: theme.green };
|
|
27
|
-
if (d === "CHANGES_REQUESTED") return { char: "R", color: theme.orange };
|
|
28
|
-
if (d === "REVIEW_REQUIRED") return { char: "R", color: theme.fg_dim };
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function GithubPRsWidget(props: WidgetRenderProps & { status: RepoStatus | null }) {
|
|
33
|
-
const repo_path = createMemo(() => props.status?.path ?? null);
|
|
34
|
-
const remote_url = createMemo(() => props.status?.remote_url ?? null);
|
|
35
|
-
const github = useGithub(repo_path, remote_url);
|
|
36
|
-
|
|
37
|
-
const prs = createMemo(() => github.data()?.prs ?? []);
|
|
38
|
-
const visible = () => prs().slice(0, MAX_VISIBLE);
|
|
39
|
-
const overflow = () => Math.max(0, prs().length - MAX_VISIBLE);
|
|
40
|
-
|
|
41
|
-
const formatPrLine = (pr: GithubPR) => {
|
|
42
|
-
const si = statusIcon(pr);
|
|
43
|
-
const ci = ciIndicator(pr);
|
|
44
|
-
const rv = reviewIndicator(pr);
|
|
45
|
-
const number_str = `#${pr.number}`;
|
|
46
|
-
// 2 (icon+space) + number + 1 space + ci(2) + rv(2) = overhead
|
|
47
|
-
const suffix_len = (ci ? 2 : 0) + (rv ? 2 : 0);
|
|
48
|
-
const available = props.width - 2 - number_str.length - 1 - suffix_len;
|
|
49
|
-
const title = truncate(pr.title, Math.max(1, available));
|
|
50
|
-
return { si, ci, rv, number_str, title };
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<box flexDirection="column">
|
|
55
|
-
<Switch>
|
|
56
|
-
<Match when={github.error()?.kind === "gh_cli_not_found"}>
|
|
57
|
-
<text fg={theme.fg_dim} content="gh not available" />
|
|
58
|
-
<text fg={theme.fg_dim} content="install: https://cli.github.com" />
|
|
59
|
-
</Match>
|
|
60
|
-
<Match when={github.error()?.kind === "not_github_repo"}>
|
|
61
|
-
<text fg={theme.fg_dim} content="not a GitHub repo" />
|
|
62
|
-
</Match>
|
|
63
|
-
<Match when={github.error()?.kind === "gh_auth_required"}>
|
|
64
|
-
<text fg={theme.fg_dim} content="gh auth required" />
|
|
65
|
-
</Match>
|
|
66
|
-
<Match when={github.error()}>
|
|
67
|
-
<text fg={theme.fg_dim} content="GitHub error" />
|
|
68
|
-
</Match>
|
|
69
|
-
<Match when={github.loading() && !github.data()}>
|
|
70
|
-
<text fg={theme.fg_dim} content="loading…" />
|
|
71
|
-
</Match>
|
|
72
|
-
<Match when={true}>
|
|
73
|
-
<text fg={theme.fg_dark} content={`GitHub PRs (${prs().length})`} />
|
|
74
|
-
<Show
|
|
75
|
-
when={prs().length > 0}
|
|
76
|
-
fallback={
|
|
77
|
-
<text fg={theme.fg_dim} content="(no open PRs)" />
|
|
78
|
-
}
|
|
79
|
-
>
|
|
80
|
-
<For each={visible()}>
|
|
81
|
-
{(pr) => {
|
|
82
|
-
const line = () => formatPrLine(pr);
|
|
83
|
-
return (
|
|
84
|
-
<box flexDirection="row" height={1}>
|
|
85
|
-
<text fg={line().si.color} content={`${line().si.char} `} />
|
|
86
|
-
<text fg={theme.fg_dim} content={line().number_str} />
|
|
87
|
-
<text content={` ${line().title}`} />
|
|
88
|
-
<Show when={line().ci}>
|
|
89
|
-
{(ci) => (
|
|
90
|
-
<text fg={ci().color} content={` ${ci().char}`} />
|
|
91
|
-
)}
|
|
92
|
-
</Show>
|
|
93
|
-
<Show when={line().rv}>
|
|
94
|
-
{(rv) => (
|
|
95
|
-
<text fg={rv().color} content={` ${rv().char}`} />
|
|
96
|
-
)}
|
|
97
|
-
</Show>
|
|
98
|
-
</box>
|
|
99
|
-
);
|
|
100
|
-
}}
|
|
101
|
-
</For>
|
|
102
|
-
<Show when={overflow() > 0}>
|
|
103
|
-
<text fg={theme.fg_dim} content={`+${overflow()} more`} />
|
|
104
|
-
</Show>
|
|
105
|
-
</Show>
|
|
106
|
-
</Match>
|
|
107
|
-
</Switch>
|
|
108
|
-
</box>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
registerWidget({
|
|
113
|
-
id: "github-prs",
|
|
114
|
-
label: "GitHub PRs",
|
|
115
|
-
size_hint,
|
|
116
|
-
component: GithubPRsWidget,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
export { GithubPRsWidget };
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { Show, createMemo } from "solid-js";
|
|
2
|
-
import type { WidgetRenderProps, RepoStatus } from "@overview/core";
|
|
3
|
-
import { registerWidget } from "./registry";
|
|
4
|
-
import { theme } from "../../theme";
|
|
5
|
-
import { useGithub } from "../../lib/use-github";
|
|
6
|
-
import { formatRelativeTime } from "../../lib/format";
|
|
7
|
-
|
|
8
|
-
const size_hint = { span: "third" as const, min_height: 1 };
|
|
9
|
-
|
|
10
|
-
function GithubReleaseWidget(props: WidgetRenderProps & { status: RepoStatus | null }) {
|
|
11
|
-
const repo_path = createMemo(() => props.status?.path ?? null);
|
|
12
|
-
const remote_url = createMemo(() => props.status?.remote_url ?? null);
|
|
13
|
-
const github = useGithub(repo_path, remote_url);
|
|
14
|
-
|
|
15
|
-
const published_relative = () => {
|
|
16
|
-
const release = github.data()?.latest_release;
|
|
17
|
-
if (!release) return "";
|
|
18
|
-
const ts = Math.floor(new Date(release.published_at).getTime() / 1000);
|
|
19
|
-
return formatRelativeTime(ts);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<box flexDirection="column">
|
|
24
|
-
<Show when={!github.error()}>
|
|
25
|
-
<Show
|
|
26
|
-
when={github.data()?.latest_release}
|
|
27
|
-
fallback={
|
|
28
|
-
<text fg={theme.fg_dim} content="no releases" />
|
|
29
|
-
}
|
|
30
|
-
>
|
|
31
|
-
{(release) => (
|
|
32
|
-
<>
|
|
33
|
-
{/* Row 1: tag + published date */}
|
|
34
|
-
<box flexDirection="row" height={1} gap={1}>
|
|
35
|
-
<text fg={theme.green} content={release().tag_name} />
|
|
36
|
-
<text fg={theme.fg_dim} content={published_relative()} />
|
|
37
|
-
</box>
|
|
38
|
-
|
|
39
|
-
{/* Row 2: commits since release */}
|
|
40
|
-
<box flexDirection="row" height={1} gap={1}>
|
|
41
|
-
<text
|
|
42
|
-
fg={release().commits_since > 0 ? theme.yellow : theme.green}
|
|
43
|
-
content={`${release().commits_since}`}
|
|
44
|
-
/>
|
|
45
|
-
<text fg={theme.fg_dim} content="commits since release" />
|
|
46
|
-
</box>
|
|
47
|
-
</>
|
|
48
|
-
)}
|
|
49
|
-
</Show>
|
|
50
|
-
</Show>
|
|
51
|
-
|
|
52
|
-
{/* Error states */}
|
|
53
|
-
<Show when={github.error()?.kind === "gh_cli_not_found"}>
|
|
54
|
-
<box flexDirection="row" height={1} gap={1}>
|
|
55
|
-
<text fg={theme.fg_dim} content="gh not available" />
|
|
56
|
-
<text fg={theme.cyan} content="https://cli.github.com" />
|
|
57
|
-
</box>
|
|
58
|
-
</Show>
|
|
59
|
-
<Show when={github.error()?.kind === "not_github_repo"}>
|
|
60
|
-
<text fg={theme.fg_dim} content="not a GitHub repo" />
|
|
61
|
-
</Show>
|
|
62
|
-
</box>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
registerWidget({
|
|
67
|
-
id: "github-release",
|
|
68
|
-
label: "Latest Release",
|
|
69
|
-
size_hint,
|
|
70
|
-
component: GithubReleaseWidget,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
export { GithubReleaseWidget };
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Side-effect imports — registers widgets with the registry
|
|
2
|
-
import "./git-status";
|
|
3
|
-
import "./file-changes";
|
|
4
|
-
import "./repo-meta";
|
|
5
|
-
import "./branch-list";
|
|
6
|
-
import "./github-prs";
|
|
7
|
-
import "./github-ci";
|
|
8
|
-
import "./github-release";
|
|
9
|
-
import "./devpad-tasks";
|
|
10
|
-
import "./devpad-milestones";
|
|
11
|
-
import "./github-issues";
|
|
12
|
-
import "./commit-activity";
|