Add batch picture candidate workflow
This commit is contained in:
+74
-5
@@ -34,6 +34,7 @@ const sidebarOverlay = document.querySelector("#sidebarOverlay");
|
||||
const sourceModal = document.querySelector("#sourceModal");
|
||||
const sourceTitle = document.querySelector("#sourceTitle");
|
||||
const sourceList = document.querySelector("#sourceList");
|
||||
const sourceBatchActionButton = document.querySelector("#sourceBatchActionButton");
|
||||
const sourceReloadButton = document.querySelector("#sourceReloadButton");
|
||||
const sourceCloseButton = document.querySelector("#sourceCloseButton");
|
||||
const settingsButton = document.querySelector("#settingsButton");
|
||||
@@ -205,6 +206,7 @@ loadProductButton.title = "Load existing product attribute info.";
|
||||
loadProductButton.addEventListener("click", loadProductInfoForCurrentProduct);
|
||||
findSourcesButton.addEventListener("click", findSourcesForCurrentProduct);
|
||||
sourceReloadButton.addEventListener("click", () => findSourcesForCurrentProduct(true));
|
||||
sourceBatchActionButton.addEventListener("click", handleSourceBatchAction);
|
||||
getPicturesButton.addEventListener("click", getPicturesForCurrentProduct);
|
||||
secondaryPanelToggle.addEventListener("click", toggleSecondaryPanel);
|
||||
mainWorkspaceToggle.addEventListener("click", toggleMainWorkspace);
|
||||
@@ -425,6 +427,25 @@ function setButtonLabel(button, label) {
|
||||
}
|
||||
}
|
||||
|
||||
function setButtonIcon(button, iconName) {
|
||||
const useElement = button.querySelector(":scope > svg use");
|
||||
if (useElement) useElement.setAttribute("href", `/icons.svg#${iconName}`);
|
||||
}
|
||||
|
||||
function setSourceBatchAction(mode) {
|
||||
sourceBatchActionButton.dataset.mode = mode;
|
||||
sourceBatchActionButton.hidden = false;
|
||||
sourceBatchActionButton.disabled = false;
|
||||
if (mode === "get-pictures") {
|
||||
setButtonIcon(sourceBatchActionButton, "image");
|
||||
setButtonLabel(sourceBatchActionButton, "Get all pictures");
|
||||
return;
|
||||
}
|
||||
|
||||
setButtonIcon(sourceBatchActionButton, "save");
|
||||
setButtonLabel(sourceBatchActionButton, "Save all");
|
||||
}
|
||||
|
||||
async function loadLanguages() {
|
||||
languageSelect.replaceChildren(createOption("", "Select language"));
|
||||
try {
|
||||
@@ -880,6 +901,7 @@ function getFirstSupplierColorGroup() {
|
||||
}
|
||||
|
||||
function renderSourceCandidates(items) {
|
||||
sourceBatchActionButton.hidden = true;
|
||||
if (!items.length) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "source-row";
|
||||
@@ -888,6 +910,7 @@ function renderSourceCandidates(items) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSourceBatchAction("get-pictures");
|
||||
sourceList.replaceChildren(
|
||||
...items.map((item) => {
|
||||
const section = document.createElement("section");
|
||||
@@ -927,12 +950,14 @@ function renderSourceCandidates(items) {
|
||||
}
|
||||
|
||||
function renderPictureCandidates(items) {
|
||||
sourceBatchActionButton.hidden = true;
|
||||
const rows = [];
|
||||
let position = 0;
|
||||
for (const item of items) {
|
||||
for (const candidate of item.candidates || []) {
|
||||
for (const picture of candidate.pictures || []) {
|
||||
position += 1;
|
||||
const sourcePosition = position;
|
||||
const target = buildPictureTargetInfo();
|
||||
const row = document.createElement("div");
|
||||
row.className = "picture-candidate";
|
||||
@@ -954,7 +979,7 @@ function renderPictureCandidates(items) {
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.className = "picture-candidate-title";
|
||||
label.textContent = `${item.source?.name || item.source?.key || "Source"} picture #${position}`;
|
||||
label.textContent = `${item.source?.name || item.source?.key || "Source"} picture #${sourcePosition}`;
|
||||
|
||||
const dimensions = document.createElement("span");
|
||||
dimensions.className = "picture-candidate-dimensions";
|
||||
@@ -982,18 +1007,18 @@ function renderPictureCandidates(items) {
|
||||
action.type = "button";
|
||||
action.className = "source-picture-action picture-candidate-save";
|
||||
action.textContent = "Save to DB";
|
||||
action.addEventListener("click", () =>
|
||||
action.savePictureCandidate = () =>
|
||||
savePictureCandidateToDb({
|
||||
picture,
|
||||
item,
|
||||
candidate,
|
||||
image,
|
||||
position,
|
||||
position: sourcePosition,
|
||||
target,
|
||||
action,
|
||||
duplicateHint,
|
||||
}),
|
||||
);
|
||||
});
|
||||
action.addEventListener("click", () => action.savePictureCandidate());
|
||||
|
||||
row.append(preview, details, action);
|
||||
rows.push(row);
|
||||
@@ -1008,6 +1033,7 @@ function renderPictureCandidates(items) {
|
||||
sourceList.replaceChildren(empty);
|
||||
return;
|
||||
}
|
||||
setSourceBatchAction("save-pictures");
|
||||
sourceList.replaceChildren(...rows);
|
||||
}
|
||||
|
||||
@@ -1089,16 +1115,59 @@ async function savePictureCandidateToDb({
|
||||
if (!response.ok) throw new Error(data.message || "Picture save failed.");
|
||||
|
||||
action.textContent = "Saved";
|
||||
action.dataset.saved = "1";
|
||||
duplicateHint.textContent = data.duplicateStatus === "exact_duplicate"
|
||||
? `DB: duplicate of #${data.duplicateOfCandidateId || "known"}`
|
||||
: `DB: saved #${data.candidateId}`;
|
||||
return true;
|
||||
} catch (error) {
|
||||
action.disabled = false;
|
||||
action.textContent = "Save to DB";
|
||||
duplicateHint.textContent = `DB: ${error.message || "save failed"}`;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAllVisiblePictureCandidates() {
|
||||
const buttons = [...sourceList.querySelectorAll(".picture-candidate-save")]
|
||||
.filter((button) => button.savePictureCandidate && button.dataset.saved !== "1");
|
||||
|
||||
if (!buttons.length) return;
|
||||
|
||||
sourceBatchActionButton.disabled = true;
|
||||
setButtonLabel(sourceBatchActionButton, `Saving 0/${buttons.length}`);
|
||||
let savedCount = 0;
|
||||
for (const button of buttons) {
|
||||
const saved = await button.savePictureCandidate();
|
||||
if (saved) savedCount += 1;
|
||||
setButtonLabel(sourceBatchActionButton, `Saving ${savedCount}/${buttons.length}`);
|
||||
}
|
||||
|
||||
sourceBatchActionButton.disabled = false;
|
||||
setButtonLabel(
|
||||
sourceBatchActionButton,
|
||||
savedCount === buttons.length ? "Saved all" : `Saved ${savedCount}/${buttons.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function handleSourceBatchAction() {
|
||||
if (sourceBatchActionButton.dataset.mode === "get-pictures") {
|
||||
sourceBatchActionButton.disabled = true;
|
||||
setButtonLabel(sourceBatchActionButton, "Loading pictures...");
|
||||
try {
|
||||
await getPicturesForCurrentProduct("");
|
||||
} finally {
|
||||
sourceBatchActionButton.disabled = false;
|
||||
if (sourceBatchActionButton.dataset.mode === "get-pictures") {
|
||||
setButtonLabel(sourceBatchActionButton, "Get all pictures");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await saveAllVisiblePictureCandidates();
|
||||
}
|
||||
|
||||
function collectPictureUrls(items) {
|
||||
return items.flatMap((item) =>
|
||||
(item.candidates || []).flatMap((candidate) => candidate.pictures || []),
|
||||
|
||||
Reference in New Issue
Block a user