Improve guest photo downloads with preview/original variants
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-02-07 14:31:48 +01:00
parent 3ba4d11d92
commit ddbfa38db1
7 changed files with 356 additions and 14 deletions

View File

@@ -25,6 +25,8 @@ type GalleryFilter = 'latest' | 'popular' | 'mine' | 'photobooth';
type GalleryTile = {
id: number;
imageUrl: string;
fullUrl?: string | null;
downloadUrl?: string | null;
likes: number;
createdAt?: string | null;
ingestSource?: string | null;
@@ -42,6 +44,8 @@ type GalleryTile = {
type LightboxPhoto = {
id: number;
imageUrl: string;
fullUrl?: string | null;
downloadUrl?: string | null;
likes: number;
isMine?: boolean;
taskId?: number | null;
@@ -63,6 +67,9 @@ function normalizeImageUrl(src?: string | null) {
}
let cleanPath = src.replace(/^\/+/g, '').replace(/\/+/g, '/');
if (cleanPath.startsWith('api/')) {
return `/${cleanPath}`;
}
if (!cleanPath.startsWith('storage/')) {
cleanPath = `storage/${cleanPath}`;
}
@@ -175,14 +182,23 @@ export default function GalleryScreen() {
const record = photo as Record<string, unknown>;
const id = Number(record.id ?? 0);
const likesCount = typeof record.likes_count === 'number' ? record.likes_count : 0;
const fullUrl = normalizeImageUrl(
(record.full_url as string | null | undefined)
?? (record.file_path as string | null | undefined)
?? (record.url as string | null | undefined)
?? (record.image_url as string | null | undefined)
);
const imageUrl = normalizeImageUrl(
(record.thumbnail_url as string | null | undefined)
?? (record.thumbnail_path as string | null | undefined)
?? (record.file_path as string | null | undefined)
?? (record.full_url as string | null | undefined)
?? fullUrl
?? (record.url as string | null | undefined)
?? (record.image_url as string | null | undefined)
);
const downloadUrl = normalizeImageUrl(
(record.download_url as string | null | undefined)
?? fullUrl
);
const rawTaskId = Number(record.task_id ?? record.taskId ?? 0);
const taskId = Number.isFinite(rawTaskId) && rawTaskId > 0 ? rawTaskId : null;
const taskLabel =
@@ -226,6 +242,8 @@ export default function GalleryScreen() {
return {
id,
imageUrl,
fullUrl: fullUrl || null,
downloadUrl: downloadUrl || null,
likes: likesCount,
createdAt: typeof record.created_at === 'string' ? record.created_at : null,
ingestSource: typeof record.ingest_source === 'string' ? record.ingest_source : null,
@@ -379,14 +397,23 @@ export default function GalleryScreen() {
const record = photo as Record<string, unknown>;
const id = Number(record.id ?? 0);
const likesCount = typeof record.likes_count === 'number' ? record.likes_count : 0;
const fullUrl = normalizeImageUrl(
(record.full_url as string | null | undefined)
?? (record.file_path as string | null | undefined)
?? (record.url as string | null | undefined)
?? (record.image_url as string | null | undefined)
);
const imageUrl = normalizeImageUrl(
(record.thumbnail_url as string | null | undefined)
?? (record.thumbnail_path as string | null | undefined)
?? (record.file_path as string | null | undefined)
?? (record.full_url as string | null | undefined)
?? fullUrl
?? (record.url as string | null | undefined)
?? (record.image_url as string | null | undefined)
);
const downloadUrl = normalizeImageUrl(
(record.download_url as string | null | undefined)
?? fullUrl
);
if (!id || !imageUrl || existing.has(id)) {
return null;
}
@@ -395,6 +422,8 @@ export default function GalleryScreen() {
return {
id,
imageUrl,
fullUrl: fullUrl || null,
downloadUrl: downloadUrl || null,
likes: likesCount,
createdAt: typeof record.created_at === 'string' ? record.created_at : null,
ingestSource: typeof record.ingest_source === 'string' ? record.ingest_source : null,
@@ -447,6 +476,8 @@ export default function GalleryScreen() {
? {
id: lightboxSelected.id,
imageUrl: lightboxSelected.imageUrl,
fullUrl: lightboxSelected.fullUrl ?? null,
downloadUrl: lightboxSelected.downloadUrl ?? null,
likes: lightboxSelected.likes,
isMine: lightboxSelected.isMine,
taskId: lightboxSelected.taskId ?? null,
@@ -764,9 +795,10 @@ export default function GalleryScreen() {
);
const downloadPhoto = React.useCallback((photo?: LightboxPhoto | null) => {
if (!photo?.imageUrl) return;
const url = photo?.downloadUrl ?? photo?.fullUrl ?? photo?.imageUrl ?? null;
if (!url) return;
const link = document.createElement('a');
link.href = photo.imageUrl;
link.href = url;
link.download = `photo-${photo.id}.jpg`;
link.rel = 'noreferrer';
document.body.appendChild(link);
@@ -1498,14 +1530,23 @@ export default function GalleryScreen() {
function mapFullPhoto(photo: Record<string, unknown>): LightboxPhoto | null {
const id = Number(photo.id ?? 0);
if (!id) return null;
const imageUrl = normalizeImageUrl(
const fullUrl = normalizeImageUrl(
(photo.full_url as string | null | undefined)
?? (photo.file_path as string | null | undefined)
?? (photo.url as string | null | undefined)
?? (photo.image_url as string | null | undefined)
);
const imageUrl = normalizeImageUrl(
fullUrl
?? (photo.thumbnail_url as string | null | undefined)
?? (photo.thumbnail_path as string | null | undefined)
?? (photo.url as string | null | undefined)
?? (photo.image_url as string | null | undefined)
);
const downloadUrl = normalizeImageUrl(
(photo.download_url as string | null | undefined)
?? fullUrl
);
if (!imageUrl) return null;
const taskLabel =
typeof photo.task_title === 'string'
@@ -1550,6 +1591,8 @@ function mapFullPhoto(photo: Record<string, unknown>): LightboxPhoto | null {
return {
id,
imageUrl,
fullUrl: fullUrl || null,
downloadUrl: downloadUrl || null,
likes: typeof photo.likes_count === 'number' ? photo.likes_count : 0,
isMine,
taskId,

View File

@@ -22,6 +22,8 @@ import { GUEST_AI_MAGIC_EDITS_ENABLED } from '../lib/featureFlags';
type LightboxPhoto = {
id: number;
imageUrl: string;
fullUrl?: string | null;
downloadUrl?: string | null;
likes: number;
createdAt?: string | null;
ingestSource?: string | null;
@@ -37,6 +39,9 @@ function normalizeImageUrl(src?: string | null) {
}
let cleanPath = src.replace(/^\/+/g, '').replace(/\/+/g, '/');
if (cleanPath.startsWith('api/')) {
return `/${cleanPath}`;
}
if (!cleanPath.startsWith('storage/')) {
cleanPath = `storage/${cleanPath}`;
}
@@ -47,18 +52,29 @@ function normalizeImageUrl(src?: string | null) {
function mapPhoto(photo: Record<string, unknown>): LightboxPhoto | null {
const id = Number(photo.id ?? 0);
if (!id) return null;
const imageUrl = normalizeImageUrl(
const fullUrl = normalizeImageUrl(
(photo.full_url as string | null | undefined)
?? (photo.file_path as string | null | undefined)
?? (photo.url as string | null | undefined)
?? (photo.image_url as string | null | undefined)
);
const imageUrl = normalizeImageUrl(
fullUrl
?? (photo.thumbnail_url as string | null | undefined)
?? (photo.thumbnail_path as string | null | undefined)
?? (photo.url as string | null | undefined)
?? (photo.image_url as string | null | undefined)
);
const downloadUrl = normalizeImageUrl(
(photo.download_url as string | null | undefined)
?? fullUrl
);
if (!imageUrl) return null;
return {
id,
imageUrl,
fullUrl: fullUrl || null,
downloadUrl: downloadUrl || null,
likes: typeof photo.likes_count === 'number' ? photo.likes_count : 0,
createdAt: typeof photo.created_at === 'string' ? photo.created_at : null,
ingestSource: typeof photo.ingest_source === 'string' ? photo.ingest_source : null,
@@ -617,7 +633,7 @@ export default function PhotoLightboxScreen() {
>
<Button
unstyled
onPress={() => downloadPhoto(selected?.imageUrl ?? null, selected?.id ?? null)}
onPress={() => downloadPhoto(selected?.downloadUrl ?? selected?.fullUrl ?? selected?.imageUrl ?? null, selected?.id ?? null)}
paddingHorizontal="$3"
paddingVertical="$2"
>