@kitlangton/ghui 0.1.2 → 0.1.3
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 +2 -1
- package/src/App.tsx +89 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitlangton/ghui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Terminal UI for GitHub pull requests",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public",
|
|
31
|
+
"provenance": true,
|
|
31
32
|
"registry": "https://registry.npmjs.org/"
|
|
32
33
|
},
|
|
33
34
|
"bin": {
|
package/src/App.tsx
CHANGED
|
@@ -66,6 +66,14 @@ interface RetryProgress {
|
|
|
66
66
|
readonly max: number
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
interface DetailPlaceholderInput {
|
|
70
|
+
readonly status: LoadStatus
|
|
71
|
+
readonly retryProgress: RetryProgress | null
|
|
72
|
+
readonly loadingIndicator: string
|
|
73
|
+
readonly visibleCount: number
|
|
74
|
+
readonly filterText: string
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
const pullRequestReferencePattern = /(#[0-9]+)/g
|
|
70
78
|
const PR_FETCH_RETRIES = 6
|
|
71
79
|
const DETAIL_PLACEHOLDER_ROWS = 4
|
|
@@ -308,6 +316,47 @@ const labelTextColor = (color: string) => {
|
|
|
308
316
|
return "#f8fafc"
|
|
309
317
|
}
|
|
310
318
|
|
|
319
|
+
const getDetailPlaceholderContent = ({
|
|
320
|
+
status,
|
|
321
|
+
retryProgress,
|
|
322
|
+
loadingIndicator,
|
|
323
|
+
visibleCount,
|
|
324
|
+
filterText,
|
|
325
|
+
}: DetailPlaceholderInput): DetailPlaceholderContent => {
|
|
326
|
+
if (status === "loading") {
|
|
327
|
+
return {
|
|
328
|
+
title: `${loadingIndicator} Loading pull requests`,
|
|
329
|
+
hint: retryProgress ? `Retry ${retryProgress.attempt}/${retryProgress.max}` : "Fetching latest open PRs",
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (status === "error") {
|
|
334
|
+
return {
|
|
335
|
+
title: "Could not load pull requests",
|
|
336
|
+
hint: "Press r to retry",
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (visibleCount === 0 && filterText.length > 0) {
|
|
341
|
+
return {
|
|
342
|
+
title: "No matching pull requests",
|
|
343
|
+
hint: "Press esc to clear the filter",
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (visibleCount === 0) {
|
|
348
|
+
return {
|
|
349
|
+
title: "No open pull requests",
|
|
350
|
+
hint: "Press r to refresh",
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
title: "Select a pull request",
|
|
356
|
+
hint: "Use up/down to move",
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
311
360
|
const bodyPreview = (body: string, width: number, limit = DETAIL_BODY_LINES): Array<PreviewLine> => {
|
|
312
361
|
const sourceLines = body.replace(/\r/g, "").split("\n")
|
|
313
362
|
const preview: Array<PreviewLine> = []
|
|
@@ -850,8 +899,8 @@ const DetailBody = ({
|
|
|
850
899
|
)
|
|
851
900
|
}
|
|
852
901
|
|
|
853
|
-
const
|
|
854
|
-
const innerWidth = Math.max(1,
|
|
902
|
+
const StatusCard = ({ content, width }: { content: DetailPlaceholderContent; width: number }) => {
|
|
903
|
+
const innerWidth = Math.max(1, width - 2)
|
|
855
904
|
const cardWidth = Math.min(innerWidth, Math.max(28, content.title.length + 4, content.hint.length + 4))
|
|
856
905
|
const offset = " ".repeat(Math.max(0, Math.floor((innerWidth - cardWidth) / 2)))
|
|
857
906
|
const cardInnerWidth = Math.max(1, cardWidth - 2)
|
|
@@ -868,14 +917,31 @@ const DetailPlaceholder = ({ content, paneWidth }: { content: DetailPlaceholderC
|
|
|
868
917
|
)
|
|
869
918
|
|
|
870
919
|
return (
|
|
871
|
-
<box flexDirection="column">
|
|
872
|
-
<
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
920
|
+
<box flexDirection="column" paddingLeft={1} paddingRight={1}>
|
|
921
|
+
<PlainLine text={`${offset}┌${"─".repeat(cardInnerWidth)}┐`} fg={colors.separator} />
|
|
922
|
+
{contentLine(content.title, colors.count, true)}
|
|
923
|
+
{contentLine(content.hint, colors.muted)}
|
|
924
|
+
<PlainLine text={`${offset}└${"─".repeat(cardInnerWidth)}┘`} fg={colors.separator} />
|
|
925
|
+
</box>
|
|
926
|
+
)
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const DetailPlaceholder = ({ content, paneWidth }: { content: DetailPlaceholderContent; paneWidth: number }) => (
|
|
930
|
+
<box flexDirection="column">
|
|
931
|
+
<StatusCard content={content} width={paneWidth} />
|
|
932
|
+
<box height={1}><Divider width={paneWidth} /></box>
|
|
933
|
+
</box>
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
const LoadingPane = ({ content, width, height }: { content: DetailPlaceholderContent; width: number; height: number }) => {
|
|
937
|
+
const topRows = Math.max(0, Math.floor((height - DETAIL_PLACEHOLDER_ROWS) / 2))
|
|
938
|
+
const bottomRows = Math.max(0, height - topRows - DETAIL_PLACEHOLDER_ROWS)
|
|
939
|
+
|
|
940
|
+
return (
|
|
941
|
+
<box height={height} flexDirection="column">
|
|
942
|
+
{Array.from({ length: topRows }, (_, index) => <BlankRow key={`top-${index}`} />)}
|
|
943
|
+
<StatusCard content={content} width={width} />
|
|
944
|
+
{Array.from({ length: bottomRows }, (_, index) => <BlankRow key={`bottom-${index}`} />)}
|
|
879
945
|
</box>
|
|
880
946
|
)
|
|
881
947
|
}
|
|
@@ -1100,6 +1166,7 @@ export const App = () => {
|
|
|
1100
1166
|
: AsyncResult.isFailure(pullRequestResult)
|
|
1101
1167
|
? "error"
|
|
1102
1168
|
: "ready"
|
|
1169
|
+
const isInitialLoading = pullRequestStatus === "loading" && pullRequests.length === 0
|
|
1103
1170
|
const pullRequestError = AsyncResult.isFailure(pullRequestResult) ? errorMessage(Cause.squash(pullRequestResult.cause)) : null
|
|
1104
1171
|
const username = AsyncResult.isSuccess(usernameResult) ? usernameResult.value : null
|
|
1105
1172
|
|
|
@@ -1172,30 +1239,13 @@ export const App = () => {
|
|
|
1172
1239
|
|
|
1173
1240
|
const selectedPullRequest = visiblePullRequests[selectedIndex] ?? null
|
|
1174
1241
|
const loadingIndicator = LOADING_FRAMES[loadingFrame % LOADING_FRAMES.length]!
|
|
1175
|
-
const detailPlaceholderContent
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
:
|
|
1181
|
-
|
|
1182
|
-
title: "Could not load pull requests",
|
|
1183
|
-
hint: "Press r to retry",
|
|
1184
|
-
}
|
|
1185
|
-
: visiblePullRequests.length === 0 && visibleFilterText.length > 0
|
|
1186
|
-
? {
|
|
1187
|
-
title: "No matching pull requests",
|
|
1188
|
-
hint: "Press esc to clear the filter",
|
|
1189
|
-
}
|
|
1190
|
-
: visiblePullRequests.length === 0
|
|
1191
|
-
? {
|
|
1192
|
-
title: "No open pull requests",
|
|
1193
|
-
hint: "Press r to refresh",
|
|
1194
|
-
}
|
|
1195
|
-
: {
|
|
1196
|
-
title: "Select a pull request",
|
|
1197
|
-
hint: "Use up/down to move",
|
|
1198
|
-
}
|
|
1242
|
+
const detailPlaceholderContent = getDetailPlaceholderContent({
|
|
1243
|
+
status: pullRequestStatus,
|
|
1244
|
+
retryProgress,
|
|
1245
|
+
loadingIndicator,
|
|
1246
|
+
visibleCount: visiblePullRequests.length,
|
|
1247
|
+
filterText: visibleFilterText,
|
|
1248
|
+
})
|
|
1199
1249
|
const titleWrapWidth = Math.max(1, rightPaneWidth - 2) // account for paddingLeft/paddingRight in detail pane
|
|
1200
1250
|
const titleLines = selectedPullRequest ? wrapText(selectedPullRequest.title, titleWrapWidth).length : 1
|
|
1201
1251
|
const detailDividerRow = 1 + titleLines + 1 // info row + title lines + labels row
|
|
@@ -1564,12 +1614,14 @@ export const App = () => {
|
|
|
1564
1614
|
<box paddingLeft={1} paddingRight={1} flexDirection="column">
|
|
1565
1615
|
<PlainLine text={headerLine} fg={colors.muted} bold />
|
|
1566
1616
|
</box>
|
|
1567
|
-
{isWideLayout && !detailFullView ? (
|
|
1617
|
+
{isWideLayout && !detailFullView && !isInitialLoading ? (
|
|
1568
1618
|
<Divider width={contentWidth} junctionAt={dividerJunctionAt} junctionChar="┬" />
|
|
1569
1619
|
) : (
|
|
1570
1620
|
<Divider width={contentWidth} />
|
|
1571
1621
|
)}
|
|
1572
|
-
{
|
|
1622
|
+
{isInitialLoading ? (
|
|
1623
|
+
<LoadingPane content={detailPlaceholderContent} width={contentWidth} height={wideBodyHeight} />
|
|
1624
|
+
) : isWideLayout && detailFullView ? (
|
|
1573
1625
|
<box flexGrow={1} flexDirection="column">
|
|
1574
1626
|
<scrollbox flexGrow={1}>
|
|
1575
1627
|
<DetailsPane
|
|
@@ -1629,7 +1681,7 @@ export const App = () => {
|
|
|
1629
1681
|
</>
|
|
1630
1682
|
)}
|
|
1631
1683
|
|
|
1632
|
-
{isWideLayout && !detailFullView ? (
|
|
1684
|
+
{isWideLayout && !detailFullView && !isInitialLoading ? (
|
|
1633
1685
|
<Divider width={contentWidth} junctionAt={dividerJunctionAt} junctionChar="┴" />
|
|
1634
1686
|
) : (
|
|
1635
1687
|
<Divider width={contentWidth} />
|