diff --git a/src/MediaManager.html b/src/MediaManager.html
index d798d3c..f30a94e 100644
--- a/src/MediaManager.html
+++ b/src/MediaManager.html
@@ -1975,10 +1975,48 @@
}
};
+ // Optimistic Sheet Update (Immediate Parallel Execution)
+ const pSheet = new Promise(resolve => {
+ const first = activeItems[0];
+ let url = null;
+ if (first) {
+ // Determine best URL based on item type
+ // Note: MediaHandlers logic prioritizes Shopify URL if present.
+ // We mimic that logic here.
+ const isShopify = (first.source === 'shopify_only' || first.source === 'synced') && first.thumbnail;
+
+ if (isShopify) {
+ url = first.thumbnail;
+ } else if (first.id) {
+ // Drive Item - Construct URL compatible with server logic
+ url = "https://drive.google.com/thumbnail?id=" + first.id + "&sz=w400";
+ }
+ }
+
+ if (!url) {
+ updateStatus('prog-sheet', 'Skipped', '#aaa');
+ resolve();
+ return;
+ }
+
+ updateStatus('prog-sheet', 'Running...', 'blue');
+ google.script.run
+ .withSuccessHandler(() => {
+ updateStatus('prog-sheet', 'Done', 'green');
+ resolve();
+ })
+ .withFailureHandler(e => {
+ console.warn("Sheet update failed", e);
+ updateStatus('prog-sheet', 'Failed (Warn)', 'orange');
+ resolve(); // Don't block completion
+ })
+ .updateSpreadsheetThumbnail(state.sku, url);
+ });
+
// Phase 1: Deletions
if (plan.deletions.length === 0) {
updateStatus('prog-delete', 'Skipped', '#aaa');
- this.startParallelPhases(plan, activeItems, jobId);
+ this.startParallelPhases(plan, activeItems, jobId, pSheet);
return;
}
@@ -1987,7 +2025,7 @@
google.script.run
.withSuccessHandler(() => {
updateStatus('prog-delete', 'Done', 'green');
- this.startParallelPhases(plan, activeItems, jobId);
+ this.startParallelPhases(plan, activeItems, jobId, pSheet);
})
.withFailureHandler(e => {
updateStatus('prog-delete', 'Failed', 'red');
@@ -1998,7 +2036,7 @@
.executeSavePhase(state.sku, 'deletions', plan.deletions, jobId);
},
- startParallelPhases(plan, activeItems, jobId) {
+ startParallelPhases(plan, activeItems, jobId, pSheet) {
// Helper function defined at the top to avoid hoisting issues
const updateStatus = (id, status, color) => {
const el = document.getElementById(id);
@@ -2009,7 +2047,6 @@
};
// Phase 2 & 3: Adoptions & Uploads (Parallel)
-
const pAdoption = new Promise((resolve, reject) => {
if (plan.adoptions.length === 0) {
updateStatus('prog-adopt', 'Skipped', '#aaa');
@@ -2043,58 +2080,44 @@
Promise.all([pAdoption, pUpload])
.then(() => {
// Phase 4: Reorder
- if (plan.reorders.length === 0 && plan.deletions.length === 0 && plan.adoptions.length === 0 && plan.uploads.length === 0) {
- // Nothing to reorder usually means no changes, but if we just changed metadata?
- // Reorder always runs to ensure "0001" suffixes are correct if we deleted something.
- // But if plan.reorders is empty, likely no items left.
+ const pReorder = new Promise((resolve, reject) => {
if (plan.reorders.length === 0) {
updateStatus('prog-reorder', 'Skipped', '#aaa');
- updateStatus('prog-sheet', 'Running...', 'blue');
- // Still update sheet? Yes.
- }
- }
+ resolve();
+ return;
+ }
- if (plan.reorders.length > 0) {
- updateStatus('prog-reorder', 'Running...', 'blue');
- google.script.run
- .withSuccessHandler(() => {
- updateStatus('prog-reorder', 'Done', 'green');
- updateStatus('prog-sheet', 'Running...', 'blue');
- this.runSheetUpdate();
- })
- .withFailureHandler(e => {
- alert("Reorder Phase Failed: " + e.message);
- this.finishSave(false);
- })
- .executeSavePhase(state.sku, 'reorder', plan.reorders, jobId);
- } else {
- updateStatus('prog-sheet', 'Running...', 'blue');
- this.runSheetUpdate();
- }
+ updateStatus('prog-reorder', 'Running...', 'blue');
+ google.script.run
+ .withSuccessHandler(() => {
+ updateStatus('prog-reorder', 'Done', 'green');
+ resolve();
+ })
+ .withFailureHandler(e => {
+ alert("Reorder Phase Failed: " + e.message);
+ reject(e);
+ })
+ .executeSavePhase(state.sku, 'reorder', plan.reorders, jobId);
+ });
+
+ // Wait for BOTH Reorder and Early Sheet Update
+ Promise.all([pReorder, pSheet])
+ .then(() => {
+ this.finishSave(true);
+ })
+ .catch(e => {
+ // If reorder failed, we already alerted.
+ console.error("Save completion failed", e);
+ this.finishSave(false);
+ });
})
.catch(e => {
alert("Parallel Execution Failed: " + e.message);
this.stopLogPolling();
- ui.setSavingState(false);
+ ui.setSavingState(false);
});
},
- runSheetUpdate() {
- google.script.run
- .withSuccessHandler(() => {
- const el = document.getElementById('prog-sheet');
- if (el) { el.style.color = 'green'; el.innerText = el.innerText.split(':')[0] + ': Done'; }
- this.finishSave(true);
- })
- .withFailureHandler(e => {
- console.error("Sheet update failed", e);
- const el = document.getElementById('prog-sheet');
- if (el) { el.style.color = 'orange'; el.innerText = el.innerText.split(':')[0] + ': Done (Sheet Warn)'; }
- this.finishSave(true);
- })
- .updateSpreadsheetThumbnail(state.sku);
- },
-
finishSave(success) {
this.stopLogPolling();
if (success) {
diff --git a/src/mediaHandlers.ts b/src/mediaHandlers.ts
index 1f7aad8..564aacf 100644
--- a/src/mediaHandlers.ts
+++ b/src/mediaHandlers.ts
@@ -119,7 +119,7 @@ export function saveMediaChanges(sku: string, finalState: any[], jobId: string |
return logs
}
-export function updateSpreadsheetThumbnail(sku: string) {
+export function updateSpreadsheetThumbnail(sku: string, forcedThumbnailUrl: string | null = null) {
const config = new Config()
const driveService = new GASDriveService()
const shop = new Shop()
@@ -128,6 +128,31 @@ export function updateSpreadsheetThumbnail(sku: string) {
const mediaService = new MediaService(driveService, shopifyMediaService, networkService, config)
const ss = new GASSpreadsheetService();
+
+ // Optimization: If forced URL provided (optimistic update), skip state calculation
+ if (forcedThumbnailUrl) {
+ try {
+ const row = ss.getRowNumberByColumnValue("product_inventory", "sku", sku);
+ if (row) {
+ const thumbUrl = forcedThumbnailUrl;
+ try {
+ const image = SpreadsheetApp.newCellImage()
+ .setSourceUrl(thumbUrl)
+ .setAltTextTitle(sku)
+ .setAltTextDescription(`Thumbnail for ${sku}`)
+ .build();
+ ss.setCellValueByColumnName("product_inventory", row, "thumbnail", image);
+ } catch (builderErr) {
+ ss.setCellValueByColumnName("product_inventory", row, "thumbnail", `=IMAGE("${thumbUrl}")`);
+ }
+ }
+ return;
+ } catch (e) {
+ console.warn("Failed to update sheet thumbnail (forced)", e);
+ throw new Error("Sheet Update Failed: " + e.message);
+ }
+ }
+
const product = new Product(sku);
// Need Shopify ID for accurate state logic?