flora is a fast and secure runtime that lets you write discord bots for your servers, with a rich TypeScript SDK, without worrying about running infrastructure. [mirror]
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(frontend): lazy-load file differ, tweaks

+82 -71
+82 -71
apps/frontend/src/components/features/DeploymentHistory.tsx
··· 11 11 } from '@/components/ui/table' 12 12 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' 13 13 import { cn } from '@/lib/utils' 14 - import { MultiFileDiff } from '@pierre/diffs/react' 15 14 import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' 16 15 import { formatDistanceToNow } from 'date-fns' 17 16 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 + }) 19 23 20 24 type DeploymentChangeSummary = { 21 25 added_files: number ··· 76 80 return actor.user_id ?? actor.actor_type 77 81 } 78 82 79 - function buildSummaryLabel(summary?: DeploymentChangeSummary | null) { 80 - if (!summary) return '—' 81 - return `+${summary.added_files} ~${summary.modified_files} -${summary.removed_files}` 82 - } 83 - 84 83 function isDiffableFile(path: string) { 85 84 const lowerPath = path.toLowerCase() 86 85 const fileName = lowerPath.split('/').at(-1) ?? lowerPath ··· 107 106 } 108 107 109 108 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' 109 + } 110 + 111 + function formatStatusLabel(status: string) { 112 + return status 113 + .split('_') 114 + .map((word) => (word ? word[0]?.toUpperCase() + word.slice(1) : word)) 115 + .join(' ') 110 116 } 111 117 112 118 async function requestJson<T>(path: string, init?: RequestInit) { ··· 154 160 } 155 161 }, [historyQuery.data, selectedRevisionId]) 156 162 163 + const shouldLoadDiffs = diffOpen && !!selectedRevisionId 164 + 157 165 const selectedRevisionQuery = useQuery({ 158 166 queryKey: ['deployment-revision', guildId, selectedRevisionId], 159 - enabled: !!selectedRevisionId, 167 + enabled: shouldLoadDiffs, 160 168 queryFn: () => 161 169 requestJson<DeploymentRevision>( 162 170 `/deployments/${encodeURIComponent(guildId)}/revisions/${selectedRevisionId}` 163 171 ) 164 172 }) 165 173 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 167 178 const baseRevisionQuery = useQuery({ 168 179 queryKey: ['deployment-revision', guildId, baseRevisionId], 169 - enabled: !!baseRevisionId, 180 + enabled: diffOpen && !!baseRevisionId, 170 181 queryFn: () => 171 182 requestJson<DeploymentRevision>( 172 183 `/deployments/${encodeURIComponent(guildId)}/revisions/${baseRevisionId}` ··· 205 216 .sort((a, b) => a.path.localeCompare(b.path)) 206 217 }, [baseRevisionQuery.data?.files, selectedRevisionQuery.data?.files]) 207 218 208 - const selectedRevision = selectedRevisionQuery.data ?? selectedSummary ?? null 209 - 210 219 return ( 211 220 <div className='flex h-full min-h-0 flex-col gap-4'> 212 221 <div className='rounded-lg border bg-card p-4'> ··· 216 225 Select a revision to inspect metadata and source diffs. 217 226 </div> 218 227 ) 219 - : selectedRevisionQuery.isLoading 228 + : shouldLoadDiffs && selectedRevisionQuery.isLoading 220 229 ? ( 221 230 <div className='flex items-center gap-2 text-sm text-muted-foreground'> 222 231 <Loader2 className='size-4 animate-spin' /> ··· 233 242 <div className='space-y-3'> 234 243 <div className='flex items-center justify-between gap-2'> 235 244 <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> 237 254 <div className='text-xs text-muted-foreground'> 238 255 {formatDateTime(selectedRevision.deployed_at)} 239 256 </div> 240 257 </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} 263 284 </div> 264 285 265 286 {rollbackMutation.isError ··· 270 291 ) 271 292 : null} 272 293 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'> 283 295 <div className='rounded-md border p-2'> 284 296 <div className='text-[11px] text-muted-foreground'>Source</div> 285 297 <div className='mt-1 text-sm font-medium'>{selectedRevision.deploy_source}</div> ··· 290 302 {formatActor(selectedRevision.actor)} 291 303 </div> 292 304 </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> 299 305 </div> 300 306 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'> 306 308 <div className='rounded-md border p-2'> 307 309 <div className='text-muted-foreground'>Entry</div> 308 310 <div className='font-mono'>{selectedRevision.entry}</div> ··· 341 343 <ChevronDown className={cn('size-4 transition-transform', diffOpen && 'rotate-180')} /> 342 344 </CollapsibleTrigger> 343 345 <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 345 349 ? <div className='text-sm text-muted-foreground'>Loading base revision…</div> 346 350 : baseRevisionQuery.isError 347 351 ? ( ··· 353 357 ? <div className='text-sm text-muted-foreground'>No base revision for this entry.</div> 354 358 : !diffFiles.length 355 359 ? <div className='text-sm text-muted-foreground'>No diffable source-file changes.</div> 360 + : !diffOpen 361 + ? null 356 362 : ( 357 363 <div className='max-h-[42dvh] space-y-3 overflow-auto'> 358 364 {diffFiles.map((file) => ( 359 365 <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> 369 384 </div> 370 385 ))} 371 386 </div> ··· 400 415 <TableRow className='hover:bg-transparent'> 401 416 <TableHead>Id</TableHead> 402 417 <TableHead>Actor</TableHead> 403 - <TableHead>Changes</TableHead> 404 418 <TableHead>Deployed</TableHead> 405 419 <TableHead>Source</TableHead> 406 420 <TableHead>Status</TableHead> ··· 424 438 > 425 439 <TableCell className='font-mono text-xs'>{shortId(row.id)}</TableCell> 426 440 <TableCell className='text-xs'>{formatActor(row.actor)}</TableCell> 427 - <TableCell className='font-mono text-xs'> 428 - {buildSummaryLabel(row.change_summary)} 429 - </TableCell> 430 441 <TableCell className='text-xs whitespace-nowrap'> 431 442 {formatTimeAgo(row.deployed_at)} 432 443 </TableCell>