Warn on small picture candidates
This commit is contained in:
@@ -186,6 +186,7 @@ There are three selectable data sources:
|
||||
- Source discovery: use manufacturer mapping from Local scrape DB and show exactly which source produced each candidate.
|
||||
- Browser scraping: use the configured browser engine per source and keep the UI responsive while scraping runs.
|
||||
- Image extraction: collect original image URLs, dimensions, mime type, file size and source page URL.
|
||||
- Image quality: show a clear warning for any picture candidate smaller than `800 x 800`; small thumbnails must remain visible for review but must be marked as not production-ready.
|
||||
- Duplicate detection: calculate `sha256` for exact duplicates and perceptual hash for visually similar images.
|
||||
- Target assignment: propose whether pictures belong to `color`, `size`, `combination` or `manual` target, then learn from user corrections.
|
||||
- Persistence: save picture blobs, candidates, hashes, source metadata and learned rules only into Local scrape DB.
|
||||
|
||||
+12
-3
@@ -61,6 +61,7 @@ const darkContrastSelect = document.querySelector("#darkContrastSelect");
|
||||
const catalogScreen = document.querySelector(".catalog-screen");
|
||||
const secondaryPanelToggle = document.querySelector("#secondaryPanelToggle");
|
||||
const mainWorkspaceToggle = document.querySelector("#mainWorkspaceToggle");
|
||||
const MIN_PRODUCT_PICTURE_SIZE = 800;
|
||||
let sidebarUserClosed = false;
|
||||
let mappingLoadToken = 0;
|
||||
const mappingCacheKey = "catalog-maker:mapping-cache";
|
||||
@@ -906,12 +907,20 @@ function renderPictureCandidates(items) {
|
||||
dimensions.className = "picture-candidate-dimensions";
|
||||
dimensions.textContent = "Size: loading...";
|
||||
image.addEventListener("load", () => {
|
||||
dimensions.textContent = image.naturalWidth && image.naturalHeight
|
||||
? `Size: ${image.naturalWidth} x ${image.naturalHeight}px`
|
||||
: "Size: unknown";
|
||||
if (!image.naturalWidth || !image.naturalHeight) {
|
||||
dimensions.textContent = "Size: unknown";
|
||||
row.classList.remove("is-too-small");
|
||||
return;
|
||||
}
|
||||
const isTooSmall = image.naturalWidth < MIN_PRODUCT_PICTURE_SIZE || image.naturalHeight < MIN_PRODUCT_PICTURE_SIZE;
|
||||
row.classList.toggle("is-too-small", isTooSmall);
|
||||
dimensions.textContent = isTooSmall
|
||||
? `Size: ${image.naturalWidth} x ${image.naturalHeight}px - too small, min ${MIN_PRODUCT_PICTURE_SIZE} x ${MIN_PRODUCT_PICTURE_SIZE}`
|
||||
: `Size: ${image.naturalWidth} x ${image.naturalHeight}px`;
|
||||
});
|
||||
image.addEventListener("error", () => {
|
||||
dimensions.textContent = "Size: image preview failed";
|
||||
row.classList.add("is-too-small");
|
||||
});
|
||||
const assignment = document.createElement("span");
|
||||
assignment.className = "picture-candidate-target";
|
||||
|
||||
+13
-3
@@ -61,6 +61,7 @@ const darkContrastSelect = document.querySelector("#darkContrastSelect");
|
||||
const catalogScreen = document.querySelector(".catalog-screen");
|
||||
const secondaryPanelToggle = document.querySelector("#secondaryPanelToggle");
|
||||
const mainWorkspaceToggle = document.querySelector("#mainWorkspaceToggle");
|
||||
const MIN_PRODUCT_PICTURE_SIZE = 800;
|
||||
let sidebarUserClosed = false;
|
||||
let mappingLoadToken = 0;
|
||||
const mappingCacheKey = "catalog-maker:mapping-cache";
|
||||
@@ -985,12 +986,21 @@ function renderPictureCandidates(items) {
|
||||
dimensions.className = "picture-candidate-dimensions";
|
||||
dimensions.textContent = "Size: loading...";
|
||||
image.addEventListener("load", () => {
|
||||
dimensions.textContent = image.naturalWidth && image.naturalHeight
|
||||
? `Size: ${image.naturalWidth} x ${image.naturalHeight}px`
|
||||
: "Size: unknown";
|
||||
if (!image.naturalWidth || !image.naturalHeight) {
|
||||
dimensions.textContent = "Size: unknown";
|
||||
row.classList.remove("is-too-small");
|
||||
return;
|
||||
}
|
||||
|
||||
const isTooSmall = image.naturalWidth < MIN_PRODUCT_PICTURE_SIZE || image.naturalHeight < MIN_PRODUCT_PICTURE_SIZE;
|
||||
row.classList.toggle("is-too-small", isTooSmall);
|
||||
dimensions.textContent = isTooSmall
|
||||
? `Size: ${image.naturalWidth} x ${image.naturalHeight}px - too small, min ${MIN_PRODUCT_PICTURE_SIZE} x ${MIN_PRODUCT_PICTURE_SIZE}`
|
||||
: `Size: ${image.naturalWidth} x ${image.naturalHeight}px`;
|
||||
});
|
||||
image.addEventListener("error", () => {
|
||||
dimensions.textContent = "Size: image preview failed";
|
||||
row.classList.add("is-too-small");
|
||||
});
|
||||
|
||||
const assignment = document.createElement("span");
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -228,6 +228,7 @@
|
||||
.source-status.is-ok { @apply bg-emerald-100 text-emerald-700; }
|
||||
.source-status.is-muted { @apply bg-slate-200 text-slate-600; }
|
||||
.picture-candidate { @apply grid grid-cols-[72px_minmax(0,1fr)_auto] items-center gap-2 border-b border-slate-200 p-2 text-[10px]; }
|
||||
.picture-candidate.is-too-small { @apply bg-amber-50; }
|
||||
.picture-candidate-preview { @apply flex h-16 w-[72px] items-center justify-center bg-white; }
|
||||
.picture-candidate img { @apply max-h-16 w-[72px] object-contain; }
|
||||
.picture-candidate-meta { @apply grid min-w-0 gap-0.5; }
|
||||
@@ -235,6 +236,7 @@
|
||||
.picture-candidate-dimensions,
|
||||
.picture-candidate-target,
|
||||
.picture-candidate-duplicate { @apply min-w-0 truncate text-slate-500; }
|
||||
.picture-candidate.is-too-small .picture-candidate-dimensions { @apply font-semibold text-amber-700; }
|
||||
.picture-candidate-save { @apply min-w-[76px]; }
|
||||
@media (max-width: 980px) {
|
||||
.catalog-screen { @apply flex flex-col pl-0; }
|
||||
@@ -364,10 +366,12 @@
|
||||
html.dark .source-row { @apply border-slate-700 bg-slate-900 text-slate-100 hover:bg-slate-800; }
|
||||
html.dark .suggested-row,
|
||||
html.dark .picture-candidate { @apply border-slate-700; }
|
||||
html.dark .picture-candidate.is-too-small { @apply bg-amber-950/40; }
|
||||
html.dark .picture-candidate-preview { @apply bg-slate-800; }
|
||||
html.dark .picture-candidate-title { @apply text-slate-100; }
|
||||
html.dark .picture-candidate-dimensions,
|
||||
html.dark .picture-candidate-target,
|
||||
html.dark .picture-candidate-duplicate { @apply text-slate-400; }
|
||||
html.dark .picture-candidate.is-too-small .picture-candidate-dimensions { @apply text-amber-300; }
|
||||
html.dark .suggested-row:nth-child(even) { @apply bg-slate-800; }
|
||||
}
|
||||
|
||||
@@ -229,7 +229,8 @@ test("picture candidates show source, dimensions, target assignment and save act
|
||||
|
||||
await expect(page.locator("#sourceModal")).toBeVisible();
|
||||
await expect(page.locator(".picture-candidate-title").first()).toHaveText("Hudy picture #1");
|
||||
await expect(page.locator(".picture-candidate-dimensions").first()).toContainText("Size:");
|
||||
await expect(page.locator(".picture-candidate-dimensions").first()).toContainText("too small, min 800 x 800");
|
||||
await expect(page.locator(".picture-candidate").first()).toHaveClass(/is-too-small/);
|
||||
await expect(page.locator(".picture-candidate-target").first()).toContainText("Target: color Carbon/Malibu Blue");
|
||||
await expect(page.locator(".picture-candidate-save")).toHaveCount(2);
|
||||
await expect(page.locator("#sourceBatchActionButton")).toBeVisible();
|
||||
|
||||
Reference in New Issue
Block a user