Skip to content

Commit aabb28c

Browse files
committed
feat(ui): consume REST board fields across modals and flash flow
Adapt ImageModal, BoardBadges, FlashProgress, vendor logo loading, and the flash operation hook to the new support_tier / format / companions fields exposed by the REST API. Ensures QDL detection and badge rendering stay in sync with the backend migration.
1 parent 470beb0 commit aabb28c

File tree

6 files changed

+60
-67
lines changed

6 files changed

+60
-67
lines changed

src/App.tsx

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -218,18 +218,21 @@ function AppContent() {
218218

219219
// Create ImageInfo for the cached image (same pattern as handleCustomImage)
220220
const cachedImage: ImageInfo = {
221-
armbian_version: 'Cached',
221+
release: 'Cached',
222222
distro_release: filename,
223223
kernel_branch: '',
224224
kernel_version: '',
225225
image_variant: 'cached',
226226
preinstalled_application: '',
227227
promoted: false,
228228
file_url: '',
229-
file_url_sha: null,
229+
direct_url: '',
230+
sha_url: null,
230231
file_size: size,
231-
download_repository: 'cache',
232-
flash_method: 'block',
232+
stability: 'stable',
233+
format: 'sd',
234+
companions: [],
235+
display_variants: [],
233236
is_custom: true,
234237
custom_path: imagePath,
235238
};
@@ -246,14 +249,10 @@ function AppContent() {
246249
name: boardName || t('custom.customImage'),
247250
vendor: hasCacheMetadata ? 'detected' : 'cached',
248251
vendor_name: hasCacheMetadata ? (boardName || 'Unknown') : 'Cached',
249-
vendor_logo: null,
252+
support_tier: 'community',
250253
image_count: 1,
251-
has_standard_support: false,
252-
has_community_support: false,
253-
has_platinum_support: false,
254-
has_eos_support: false,
255-
has_tvb_support: false,
256-
has_wip_support: false,
254+
has_desktop: false,
255+
promoted: false,
257256
};
258257

259258
setSelectedManufacturer({
@@ -339,18 +338,21 @@ function AppContent() {
339338

340339
// Create a custom ImageInfo object
341340
const customImage: ImageInfo = {
342-
armbian_version: 'Custom',
341+
release: 'Custom',
343342
distro_release: result.name,
344343
kernel_branch: '',
345344
kernel_version: '',
346345
image_variant: 'custom',
347346
preinstalled_application: '',
348347
promoted: false,
349348
file_url: '',
350-
file_url_sha: null,
349+
direct_url: '',
350+
sha_url: null,
351351
file_size: result.size,
352-
download_repository: 'local',
353-
flash_method: flashMethod,
352+
stability: 'stable',
353+
format: flashMethod,
354+
companions: [],
355+
display_variants: [],
354356
is_custom: true,
355357
custom_path: result.path,
356358
};
@@ -364,14 +366,10 @@ function AppContent() {
364366
name: t('custom.customImage'),
365367
vendor: 'custom',
366368
vendor_name: 'Custom',
367-
vendor_logo: null,
369+
support_tier: 'community',
368370
image_count: 1,
369-
has_standard_support: false,
370-
has_community_support: false,
371-
has_platinum_support: false,
372-
has_eos_support: false,
373-
has_tvb_support: false,
374-
has_wip_support: false,
371+
has_desktop: false,
372+
promoted: false,
375373
};
376374

377375
// Set manufacturer for display consistency (same pattern as cached image reuse)
@@ -505,7 +503,7 @@ function AppContent() {
505503
isOpen={activeModal === 'device'}
506504
onClose={() => setActiveModal('none')}
507505
onSelect={handleDeviceSelect}
508-
flashMethod={selectedImage?.flash_method}
506+
flashMethod={selectedImage?.format}
509507
/>
510508

511509
{/* Armbian board detection modal */}

src/components/flash/FlashProgress.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function FlashProgress({
5353
if (image.is_custom) {
5454
return image.distro_release;
5555
}
56-
return `Armbian ${image.armbian_version} ${image.distro_release}`;
56+
return `Armbian ${image.release} ${image.distro_release}`;
5757
}
5858

5959
/** Stages that show an indeterminate (animated) progress bar instead of a percentage */
@@ -140,7 +140,7 @@ export function FlashProgress({
140140

141141
{stage === 'complete' && (
142142
<p className="flash-success-hint">
143-
{image.flash_method === 'qdl'
143+
{image.format === 'qdl'
144144
? t('flash.successHintQdl')
145145
: image.is_custom
146146
? t('flash.successHintCustom')

src/components/modals/ImageModal.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ interface ImageModalProps {
3434
const IMAGE_FILTER_PREDICATES: Record<Exclude<ImageFilterType, 'all'>, (img: ImageInfo) => boolean> = {
3535
// Recommended: promoted images
3636
recommended: (img) => img.promoted === true,
37-
// Stable: from archive repository and not trunk version
38-
stable: (img) => img.download_repository === 'archive' && !img.armbian_version.includes('trunk'),
39-
// Nightly: trunk versions
40-
nightly: (img) => img.armbian_version.includes('trunk'),
37+
// Stable: stability field is "stable"
38+
stable: (img) => img.stability === 'stable',
39+
// Nightly: stability field is "nightly"
40+
nightly: (img) => img.stability === 'nightly',
4141
// Apps: has preinstalled application
4242
apps: (img) => !!(img.preinstalled_application && img.preinstalled_application.length > 0),
4343
// Barebone/Minimal: no desktop environment and no preinstalled apps
@@ -91,7 +91,7 @@ export function ImageModal({ isOpen, onClose, onSelect, board }: ImageModalProps
9191
// Use hook for async data fetching
9292
const { data: allImages, loading, error, reload } = useAsyncDataWhen<ImageInfo[]>(
9393
isOpen && !!board,
94-
() => getImagesForBoard(board!.slug, undefined, undefined, undefined, false),
94+
() => getImagesForBoard(board!.slug),
9595
[isOpen, board?.slug]
9696
);
9797

@@ -117,8 +117,8 @@ export function ImageModal({ isOpen, onClose, onSelect, board }: ImageModalProps
117117
*/
118118
function handleImageClick(image: ImageInfo) {
119119
// Check if warning is needed
120-
const isNightly = image.armbian_version.includes('trunk');
121-
const isCommunityBoard = board?.has_community_support === true;
120+
const isNightly = image.stability === 'nightly';
121+
const isCommunityBoard = board?.support_tier === 'community';
122122

123123
// No warning for custom images or stable images on supported boards
124124
if (!isNightly && !isCommunityBoard) {
@@ -236,7 +236,7 @@ export function ImageModal({ isOpen, onClose, onSelect, board }: ImageModalProps
236236

237237
<div className="list-item-content" style={{ flex: 1 }}>
238238
<div className="list-item-title">
239-
Armbian {image.armbian_version} {image.distro_release}
239+
Armbian {image.release} {image.distro_release}
240240
</div>
241241

242242
{/* Side panel with main info */}
@@ -326,7 +326,7 @@ export function ImageModal({ isOpen, onClose, onSelect, board }: ImageModalProps
326326
isOpen={showUnstableWarning}
327327
title={t('modal.imageStatusTitle')}
328328
message={
329-
board?.has_community_support === true
329+
board?.support_tier === 'community'
330330
? t('modal.communityBoardMessage')
331331
: t('modal.nightlyBuildMessage')
332332
}

src/components/shared/BoardBadges.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,37 @@ interface BoardBadgesProps {
1616
export function BoardBadges({ board, className = '' }: BoardBadgesProps) {
1717
return (
1818
<div className={`board-grid-badges ${className}`}>
19-
{board.has_platinum_support && (
19+
{board.support_tier === 'platinum' && (
2020
<span className="badge-platinum">
2121
<Crown size={10} />
2222
<span>Platinum</span>
2323
</span>
2424
)}
25-
{board.has_standard_support && !board.has_platinum_support && (
25+
{board.support_tier === 'standard' && (
2626
<span className="badge-standard">
2727
<Shield size={10} />
2828
<span>Standard</span>
2929
</span>
3030
)}
31-
{board.has_community_support && (
31+
{board.support_tier === 'community' && (
3232
<span className="badge-community">
3333
<Users size={10} />
3434
<span>Community</span>
3535
</span>
3636
)}
37-
{board.has_eos_support && (
37+
{board.support_tier === 'eos' && (
3838
<span className="badge-eos">
3939
<Clock size={10} />
4040
<span>EOS</span>
4141
</span>
4242
)}
43-
{board.has_tvb_support && (
43+
{board.support_tier === 'tvb' && (
4444
<span className="badge-tvb">
4545
<Tv size={10} />
4646
<span>TV Box</span>
4747
</span>
4848
)}
49-
{board.has_wip_support && (
49+
{board.support_tier === 'wip' && (
5050
<span className="badge-wip">
5151
<Wrench size={10} />
5252
<span>WIP</span>

src/hooks/useFlashOperation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export function useFlashOperation({
130130
}, []);
131131

132132
/** Whether the current image uses QDL (Qualcomm EDL) flashing */
133-
const isQdlMode = image.flash_method === 'qdl';
133+
const isQdlMode = image.format === 'qdl';
134134

135135
/**
136136
* Check device connection and trigger disconnect handler if missing
@@ -275,7 +275,7 @@ export function useFlashOperation({
275275
}, POLLING.DOWNLOAD_PROGRESS);
276276

277277
try {
278-
const path = await downloadImage(image.file_url, image.file_url_sha);
278+
const path = await downloadImage(image.file_url, image.sha_url);
279279
setImagePath(path);
280280
if (intervalRef.current) clearInterval(intervalRef.current);
281281
startFlash(path);

src/hooks/useVendorLogos.ts

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useState, useEffect, useMemo, useCallback } from 'react';
22
import type { BoardInfo } from '../types';
3-
import { preloadImage } from '../utils';
43
import { getCachedVendorLogo } from './useTauri';
54
import { VENDOR } from '../config';
65

@@ -29,18 +28,19 @@ export function useVendorLogos(boards: BoardInfo[] | null, isActive: boolean) {
2928
}
3029
}, [isActive]);
3130

32-
// Preload logos via local cache, falling back to remote
31+
// Preload logos via local cache using vendor slugs
3332
useEffect(() => {
3433
if (!isActive || !boards?.length || state.isLoaded) return;
3534

36-
const vendorLogos = new Map<string, string>();
35+
// Collect unique vendor slugs from boards
36+
const vendorSlugs = new Set<string>();
3737
for (const board of boards) {
38-
if (board.vendor && board.vendor !== VENDOR.FALLBACK_ID && board.vendor_logo) {
39-
vendorLogos.set(board.vendor, board.vendor_logo);
38+
if (board.vendor && board.vendor !== VENDOR.FALLBACK_ID) {
39+
vendorSlugs.add(board.vendor);
4040
}
4141
}
4242

43-
if (vendorLogos.size === 0) {
43+
if (vendorSlugs.size === 0) {
4444
setState({ failedLogos: new Set(), cachedUrls: new Map(), isLoaded: true });
4545
return;
4646
}
@@ -49,24 +49,19 @@ export function useVendorLogos(boards: BoardInfo[] | null, isActive: boolean) {
4949
const failed = new Set<string>();
5050
const cached = new Map<string, string>();
5151

52-
vendorLogos.forEach((logoUrl, vendorId) => {
53-
// Try cache first, fall back to remote preload
54-
getCachedVendorLogo(vendorId, logoUrl).then((dataUri) => {
52+
vendorSlugs.forEach((vendorSlug) => {
53+
// Fetch logo via backend cache (constructs URL from slug)
54+
getCachedVendorLogo(vendorSlug).then((dataUri) => {
5555
if (dataUri) {
56-
cached.set(vendorId, dataUri);
56+
cached.set(vendorSlug, dataUri);
5757
} else {
58-
// No cached path — try remote preload as fallback
59-
return preloadImage(logoUrl).then((success) => {
60-
if (!success) {
61-
failed.add(vendorId);
62-
}
63-
});
58+
failed.add(vendorSlug);
6459
}
6560
}).catch(() => {
66-
failed.add(vendorId);
61+
failed.add(vendorSlug);
6762
}).finally(() => {
6863
loaded++;
69-
if (loaded >= vendorLogos.size) {
64+
if (loaded >= vendorSlugs.size) {
7065
setState({ failedLogos: failed, cachedUrls: cached, isLoaded: true });
7166
}
7267
});
@@ -75,15 +70,15 @@ export function useVendorLogos(boards: BoardInfo[] | null, isActive: boolean) {
7570

7671
// Helper to get effective vendor (considering failed logos)
7772
const getEffectiveVendor = useCallback((board: BoardInfo): string => {
78-
if (!board.vendor_logo || state.failedLogos.has(board.vendor)) {
73+
if (!board.vendor || state.failedLogos.has(board.vendor)) {
7974
return VENDOR.FALLBACK_ID;
8075
}
8176
return board.vendor || VENDOR.FALLBACK_ID;
8277
}, [state.failedLogos]);
8378

8479
// Check if a vendor has a valid logo
8580
const hasValidLogo = useCallback((board: BoardInfo): boolean => {
86-
return !!(board.vendor_logo && !state.failedLogos.has(board.vendor));
81+
return !!(board.vendor && !state.failedLogos.has(board.vendor));
8782
}, [state.failedLogos]);
8883

8984
return {
@@ -127,14 +122,14 @@ export function useManufacturerList(
127122
standardCount: number;
128123
}> = {};
129124

130-
// Build vendor map with board counts, platinum board counts, and standard board counts
125+
// Build vendor map with board counts and support tier counts
131126
for (const board of boards) {
132127
const validLogo = hasValidLogo(board);
133128
const vendorId = validLogo ? (board.vendor || VENDOR.FALLBACK_ID) : VENDOR.FALLBACK_ID;
134129
const vendorName = validLogo ? (board.vendor_name || 'Other') : 'Other';
135-
// Prefer cached local URL over remote URL
130+
// Use cached local data URI for vendor logo
136131
const vendorLogo = validLogo
137-
? (cachedUrls.get(board.vendor) || board.vendor_logo)
132+
? (cachedUrls.get(board.vendor) || null)
138133
: null;
139134

140135
if (!vendorMap[vendorId]) {
@@ -149,12 +144,12 @@ export function useManufacturerList(
149144
vendorMap[vendorId].count++;
150145

151146
// Increment platinum count if this board has platinum support
152-
if (board.has_platinum_support) {
147+
if (board.support_tier === 'platinum') {
153148
vendorMap[vendorId].platinumCount++;
154149
}
155150

156151
// Increment standard count if this board has standard support
157-
if (board.has_standard_support) {
152+
if (board.support_tier === 'standard') {
158153
vendorMap[vendorId].standardCount++;
159154
}
160155
}

0 commit comments

Comments
 (0)