Skip to content

Commit 2025bc1

Browse files
committed
feat(frontend): lazy-load file differ, tweaks
1 parent 03a8ce7 commit 2025bc1

1 file changed

Lines changed: 82 additions & 71 deletions

File tree

apps/frontend/src/components/features/DeploymentHistory.tsx

Lines changed: 82 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ import {
1111
} from '@/components/ui/table'
1212
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
1313
import { cn } from '@/lib/utils'
14-
import { MultiFileDiff } from '@pierre/diffs/react'
1514
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
1615
import { formatDistanceToNow } from 'date-fns'
1716
import { ChevronDown, Loader2, RotateCcw } from 'lucide-react'
18-
import { useEffect, useMemo, useState } from 'react'
17+
import { lazy, Suspense, useEffect, useMemo, useState } from 'react'
18+
19+
const LazyMultiFileDiff = lazy(async () => {
20+
const mod = await import('@pierre/diffs/react')
21+
return { default: mod.MultiFileDiff }
22+
})
1923

2024
type DeploymentChangeSummary = {
2125
added_files: number
@@ -76,11 +80,6 @@ function formatActor(actor: DeploymentRevision['actor']) {
7680
return actor.user_id ?? actor.actor_type
7781
}
7882

79-
function buildSummaryLabel(summary?: DeploymentChangeSummary | null) {
80-
if (!summary) return '—'
81-
return `+${summary.added_files} ~${summary.modified_files} -${summary.removed_files}`
82-
}
83-
8483
function isDiffableFile(path: string) {
8584
const lowerPath = path.toLowerCase()
8685
const fileName = lowerPath.split('/').at(-1) ?? lowerPath
@@ -109,6 +108,13 @@ function statusBadgeClass(status: string) {
109108
return 'bg-green-500/15 text-green-700 hover:bg-green-500/25 dark:bg-green-500/10 dark:text-green-300 dark:hover:bg-green-500/20 border-0'
110109
}
111110

111+
function formatStatusLabel(status: string) {
112+
return status
113+
.split('_')
114+
.map((word) => (word ? word[0]?.toUpperCase() + word.slice(1) : word))
115+
.join(' ')
116+
}
117+
112118
async function requestJson<T>(path: string, init?: RequestInit) {
113119
const response = await fetch(`${API_BASE}${path}`, { credentials: 'include', ...init })
114120
if (!response.ok) {
@@ -154,19 +160,24 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
154160
}
155161
}, [historyQuery.data, selectedRevisionId])
156162

163+
const shouldLoadDiffs = diffOpen && !!selectedRevisionId
164+
157165
const selectedRevisionQuery = useQuery({
158166
queryKey: ['deployment-revision', guildId, selectedRevisionId],
159-
enabled: !!selectedRevisionId,
167+
enabled: shouldLoadDiffs,
160168
queryFn: () =>
161169
requestJson<DeploymentRevision>(
162170
`/deployments/${encodeURIComponent(guildId)}/revisions/${selectedRevisionId}`
163171
)
164172
})
165173

166-
const baseRevisionId = selectedRevisionQuery.data?.base_revision_id ?? null
174+
const selectedRevision = selectedRevisionQuery.data ?? selectedSummary ?? null
175+
const latestRevisionId = historyQuery.data?.[0]?.id ?? null
176+
const isLatestRevision = selectedRevision?.id === latestRevisionId
177+
const baseRevisionId = selectedRevision?.base_revision_id ?? null
167178
const baseRevisionQuery = useQuery({
168179
queryKey: ['deployment-revision', guildId, baseRevisionId],
169-
enabled: !!baseRevisionId,
180+
enabled: diffOpen && !!baseRevisionId,
170181
queryFn: () =>
171182
requestJson<DeploymentRevision>(
172183
`/deployments/${encodeURIComponent(guildId)}/revisions/${baseRevisionId}`
@@ -205,8 +216,6 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
205216
.sort((a, b) => a.path.localeCompare(b.path))
206217
}, [baseRevisionQuery.data?.files, selectedRevisionQuery.data?.files])
207218

208-
const selectedRevision = selectedRevisionQuery.data ?? selectedSummary ?? null
209-
210219
return (
211220
<div className='flex h-full min-h-0 flex-col gap-4'>
212221
<div className='rounded-lg border bg-card p-4'>
@@ -216,7 +225,7 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
216225
Select a revision to inspect metadata and source diffs.
217226
</div>
218227
)
219-
: selectedRevisionQuery.isLoading
228+
: shouldLoadDiffs && selectedRevisionQuery.isLoading
220229
? (
221230
<div className='flex items-center gap-2 text-sm text-muted-foreground'>
222231
<Loader2 className='size-4 animate-spin' />
@@ -233,33 +242,45 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
233242
<div className='space-y-3'>
234243
<div className='flex items-center justify-between gap-2'>
235244
<div>
236-
<div className='text-sm font-medium'>Revision {selectedRevision.id}</div>
245+
<div className='flex items-center gap-2 text-sm font-medium'>
246+
<Badge
247+
variant='outline'
248+
className={cn('border-0', statusBadgeClass(selectedRevision.status))}
249+
>
250+
{formatStatusLabel(selectedRevision.status)}
251+
</Badge>
252+
<span>Revision {selectedRevision.id}</span>
253+
</div>
237254
<div className='text-xs text-muted-foreground'>
238255
{formatDateTime(selectedRevision.deployed_at)}
239256
</div>
240257
</div>
241-
<Button
242-
size='sm'
243-
variant='outline'
244-
disabled={rollbackMutation.isPending || selectedRevision.status !== 'success'}
245-
onClick={() => {
246-
if (selectedRevision.id) rollbackMutation.mutate(selectedRevision.id)
247-
}}
248-
>
249-
{rollbackMutation.isPending
250-
? (
251-
<>
252-
<Loader2 className='mr-1 size-4 animate-spin' />
253-
Rolling back…
254-
</>
255-
)
256-
: (
257-
<>
258-
<RotateCcw className='mr-1 size-4' />
259-
Rollback to this
260-
</>
261-
)}
262-
</Button>
258+
{!isLatestRevision
259+
? (
260+
<Button
261+
size='sm'
262+
variant='outline'
263+
disabled={rollbackMutation.isPending || selectedRevision.status !== 'success'}
264+
onClick={() => {
265+
if (selectedRevision.id) rollbackMutation.mutate(selectedRevision.id)
266+
}}
267+
>
268+
{rollbackMutation.isPending
269+
? (
270+
<>
271+
<Loader2 className='mr-1 size-4 animate-spin' />
272+
Rolling back…
273+
</>
274+
)
275+
: (
276+
<>
277+
<RotateCcw className='mr-1 size-4' />
278+
Rollback to this
279+
</>
280+
)}
281+
</Button>
282+
)
283+
: null}
263284
</div>
264285

265286
{rollbackMutation.isError
@@ -270,16 +291,7 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
270291
)
271292
: null}
272293

273-
<div className='grid gap-3 md:grid-cols-4'>
274-
<div className='rounded-md border p-2'>
275-
<div className='text-[11px] text-muted-foreground'>Status</div>
276-
<Badge
277-
variant='outline'
278-
className={cn('mt-1 border-0', statusBadgeClass(selectedRevision.status))}
279-
>
280-
{selectedRevision.status}
281-
</Badge>
282-
</div>
294+
<div className='grid gap-3 md:grid-cols-2'>
283295
<div className='rounded-md border p-2'>
284296
<div className='text-[11px] text-muted-foreground'>Source</div>
285297
<div className='mt-1 text-sm font-medium'>{selectedRevision.deploy_source}</div>
@@ -290,19 +302,9 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
290302
{formatActor(selectedRevision.actor)}
291303
</div>
292304
</div>
293-
<div className='rounded-md border p-2'>
294-
<div className='text-[11px] text-muted-foreground'>Changes</div>
295-
<div className='mt-1 font-mono text-sm'>
296-
{buildSummaryLabel(selectedRevision.change_summary)}
297-
</div>
298-
</div>
299305
</div>
300306

301-
<div className='grid grid-cols-2 gap-3 text-xs md:grid-cols-4'>
302-
<div className='rounded-md border p-2'>
303-
<div className='text-muted-foreground'>Guild</div>
304-
<div className='font-mono'>{selectedRevision.guild_id}</div>
305-
</div>
307+
<div className='grid grid-cols-2 gap-3 text-xs md:grid-cols-3'>
306308
<div className='rounded-md border p-2'>
307309
<div className='text-muted-foreground'>Entry</div>
308310
<div className='font-mono'>{selectedRevision.entry}</div>
@@ -341,7 +343,9 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
341343
<ChevronDown className={cn('size-4 transition-transform', diffOpen && 'rotate-180')} />
342344
</CollapsibleTrigger>
343345
<CollapsibleContent className='border-t p-3'>
344-
{baseRevisionId && baseRevisionQuery.isLoading
346+
{shouldLoadDiffs && selectedRevisionQuery.isLoading
347+
? <div className='text-sm text-muted-foreground'>Loading revision files…</div>
348+
: baseRevisionId && baseRevisionQuery.isLoading
345349
? <div className='text-sm text-muted-foreground'>Loading base revision…</div>
346350
: baseRevisionQuery.isError
347351
? (
@@ -353,19 +357,30 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
353357
? <div className='text-sm text-muted-foreground'>No base revision for this entry.</div>
354358
: !diffFiles.length
355359
? <div className='text-sm text-muted-foreground'>No diffable source-file changes.</div>
360+
: !diffOpen
361+
? null
356362
: (
357363
<div className='max-h-[42dvh] space-y-3 overflow-auto'>
358364
{diffFiles.map((file) => (
359365
<div key={file.path} className='overflow-hidden rounded-lg border'>
360-
<MultiFileDiff
361-
oldFile={{ name: file.path, contents: file.oldContents }}
362-
newFile={{ name: file.path, contents: file.newContents }}
363-
options={{
364-
diffStyle: 'split',
365-
overflow: 'wrap',
366-
lineDiffType: 'word'
367-
}}
368-
/>
366+
<Suspense
367+
fallback={
368+
<div className='flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground'>
369+
<Loader2 className='size-3 animate-spin' />
370+
Loading diff renderer…
371+
</div>
372+
}
373+
>
374+
<LazyMultiFileDiff
375+
oldFile={{ name: file.path, contents: file.oldContents }}
376+
newFile={{ name: file.path, contents: file.newContents }}
377+
options={{
378+
diffStyle: 'split',
379+
overflow: 'wrap',
380+
lineDiffType: 'word'
381+
}}
382+
/>
383+
</Suspense>
369384
</div>
370385
))}
371386
</div>
@@ -400,7 +415,6 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
400415
<TableRow className='hover:bg-transparent'>
401416
<TableHead>Id</TableHead>
402417
<TableHead>Actor</TableHead>
403-
<TableHead>Changes</TableHead>
404418
<TableHead>Deployed</TableHead>
405419
<TableHead>Source</TableHead>
406420
<TableHead>Status</TableHead>
@@ -424,9 +438,6 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
424438
>
425439
<TableCell className='font-mono text-xs'>{shortId(row.id)}</TableCell>
426440
<TableCell className='text-xs'>{formatActor(row.actor)}</TableCell>
427-
<TableCell className='font-mono text-xs'>
428-
{buildSummaryLabel(row.change_summary)}
429-
</TableCell>
430441
<TableCell className='text-xs whitespace-nowrap'>
431442
{formatTimeAgo(row.deployed_at)}
432443
</TableCell>

0 commit comments

Comments
 (0)