Add debug Remove entry, Show more auto-picker, and update docs.

DELETE /api/paintings/:id removes works and image files with gallery refresh and catalog navigation; Show more opens the search modal on load; documentation updated for migrate schema and debug workflow.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-22 22:32:23 +03:00
co-authored by Cursor
parent b2cae284ac
commit f542c689c9
31 changed files with 396 additions and 30 deletions
+10 -3
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState, type ChangeEvent } from 'react';
import { useCallback, useEffect, useRef, useState, type ChangeEvent } from 'react';
import type { Artist } from '../types';
import {
api,
@@ -15,6 +15,7 @@ import './ArtistBio.css';
interface Props {
artist: Artist & { movement_name?: string };
debugMode?: boolean;
debugShowMore?: boolean;
portraitRevision?: number;
onBack: () => void;
onEnterGallery: () => void;
@@ -31,6 +32,7 @@ interface Props {
export default function ArtistBio({
artist,
debugMode = false,
debugShowMore = false,
portraitRevision = 0,
onBack,
onEnterGallery,
@@ -127,7 +129,7 @@ export default function ArtistBio({
}
};
const handleOpenMore = async () => {
const handleOpenMore = useCallback(async () => {
setMoreOpen(true);
setMoreLoading(true);
setMoreError(null);
@@ -140,7 +142,12 @@ export default function ArtistBio({
} finally {
setMoreLoading(false);
}
};
}, [artist.id]);
useEffect(() => {
if (!debugMode || !debugShowMore) return;
void handleOpenMore();
}, [debugMode, debugShowMore, artist.id, handleOpenMore]);
const handleSelectMoreResult = async (item: DebugImageSearchResultItem) => {
if (fixing || applyingUrl) return;
+25
View File
@@ -540,6 +540,10 @@
margin-top: 0;
}
.debug-action-buttons-danger {
margin-top: 4px;
}
.debug-clear-btn,
.debug-upload-btn {
flex: 1;
@@ -578,6 +582,27 @@
cursor: wait;
}
.debug-remove-btn {
width: 100%;
padding: 8px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
border: 1px solid rgba(200, 60, 60, 0.65);
background: rgba(200, 60, 60, 0.12);
color: #e05050;
}
.debug-remove-btn:hover:not(:disabled) {
background: rgba(200, 60, 60, 0.22);
}
.debug-remove-btn:disabled {
opacity: 0.6;
cursor: wait;
}
.painting-frame-empty {
min-height: 280px;
cursor: default;
+42 -3
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState, type ChangeEvent, type SyntheticEvent } from 'react';
import { useCallback, useEffect, useRef, useState, type ChangeEvent, type SyntheticEvent } from 'react';
import type { InfluenceLink, Painting, PaintingDetail } from '../types';
import { api, debugImageProxyUrl, imageUrl, paintingImageUrl, type DebugImageSearchResult, type DebugImageSearchResultItem, type FixPaintingImageResult } from '../api/client';
import DebugSearchResultsModal from './DebugSearchResultsModal';
@@ -15,6 +15,7 @@ interface Props {
onArtistBio: () => void;
onInfluenceArtistClick?: (artistId: number) => void;
debugMode?: boolean;
debugShowMore?: boolean;
onPaintingImageFixed?: (
paintingId: number,
fixResult: FixPaintingImageResult
@@ -23,6 +24,7 @@ interface Props {
paintingId: number,
flags: { checked: boolean; fixed: boolean }
) => void | Promise<void>;
onPaintingRemoved?: (paintingId: number, artistId: number) => void | Promise<void>;
}
function influenceKey(inf: InfluenceLink, index: number): string {
@@ -188,8 +190,10 @@ export default function PaintingDetailView({
onArtistBio,
onInfluenceArtistClick,
debugMode = false,
debugShowMore = false,
onPaintingImageFixed,
onPaintingCheckupFlagsUpdated,
onPaintingRemoved,
}: Props) {
const { painting, influencedBy, influenced, annotations = [] } = data;
const [fullscreen, setFullscreen] = useState(false);
@@ -206,6 +210,7 @@ export default function PaintingDetailView({
const [applyingUrl, setApplyingUrl] = useState<string | null>(null);
const [clearing, setClearing] = useState(false);
const [uploading, setUploading] = useState(false);
const [removing, setRemoving] = useState(false);
const [activeAnnotationId, setActiveAnnotationId] = useState<number | null>(null);
const uploadInputRef = useRef<HTMLInputElement>(null);
@@ -230,6 +235,13 @@ export default function PaintingDetailView({
setMoreResults(null);
setMoreError(null);
setActiveAnnotationId(null);
setRemoving(false);
setFixing(false);
setClearing(false);
setUploading(false);
setMarkingChecked(false);
setApplyingUrl(null);
setMoreLoading(false);
}, [painting.id]);
useEffect(() => {
@@ -298,7 +310,7 @@ export default function PaintingDetailView({
}
};
const handleOpenMore = async () => {
const handleOpenMore = useCallback(async () => {
setMoreOpen(true);
setMoreLoading(true);
setMoreError(null);
@@ -311,7 +323,12 @@ export default function PaintingDetailView({
} finally {
setMoreLoading(false);
}
};
}, [painting.id]);
useEffect(() => {
if (!debugMode || !debugShowMore) return;
void handleOpenMore();
}, [debugMode, debugShowMore, painting.id, handleOpenMore]);
const handleSelectMoreResult = async (item: DebugImageSearchResultItem) => {
if (fixing || applyingUrl) return;
@@ -381,6 +398,18 @@ export default function PaintingDetailView({
}
};
const handleRemoveEntry = async () => {
if (removing || fixing || clearing || uploading || !onPaintingRemoved) return;
setRemoving(true);
setDebugError(null);
try {
await onPaintingRemoved(painting.id, painting.artist_id);
} catch (err) {
setDebugError(err instanceof Error ? err.message : 'Could not remove painting.');
setRemoving(false);
}
};
useEffect(() => {
if (fullscreen) return;
@@ -614,6 +643,16 @@ export default function PaintingDetailView({
onChange={handleUploadFile}
/>
</div>
<div className="debug-action-buttons debug-action-buttons-danger">
<button
type="button"
className="debug-remove-btn"
onClick={handleRemoveEntry}
disabled={removing || fixing || clearing || uploading}
>
{removing ? '…' : 'Remove entry'}
</button>
</div>
</aside>
)}