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:
co-authored by
Cursor
parent
b2cae284ac
commit
f542c689c9
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user