@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.
Files changed (2) hide show
  1. package/package.json +2 -1
  2. 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.2",
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 DetailPlaceholder = ({ content, paneWidth }: { content: DetailPlaceholderContent; paneWidth: number }) => {
854
- const innerWidth = Math.max(1, paneWidth - 2)
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
- <box flexDirection="column" paddingLeft={1} paddingRight={1}>
873
- <PlainLine text={`${offset}┌${"─".repeat(cardInnerWidth)}┐`} fg={colors.separator} />
874
- {contentLine(content.title, colors.count, true)}
875
- {contentLine(content.hint, colors.muted)}
876
- <PlainLine text={`${offset}└${"─".repeat(cardInnerWidth)}┘`} fg={colors.separator} />
877
- </box>
878
- <box height={1}><Divider width={paneWidth} /></box>
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: DetailPlaceholderContent = pullRequestStatus === "loading"
1176
- ? {
1177
- title: `${loadingIndicator} Loading pull requests`,
1178
- hint: retryProgress ? `Retry ${retryProgress.attempt}/${retryProgress.max}` : "Fetching latest open PRs",
1179
- }
1180
- : pullRequestStatus === "error"
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
- {isWideLayout && detailFullView ? (
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} />