feat(media): Optimize Media Manager loading performance

Significant performance improvements to the 'Loading media...' phase:
- Reduced client-server round trips by consolidating the initial handshake (diagnostics + media fetch) into a single backend call: getMediaManagerInitialState.
- Implemented batched Google Drive metadata retrieval in GASDriveService using the Advanced Drive API, eliminating per-file property fetching calls.
- Switched to HtmlService templates in showMediaManager to pass initial SKU/Title data directly, enabling the UI shell to appear instantly upon opening.
- Updated documentation (ARCHITECTURE.md, MEMORY.md) to clarify Webpack global assignment requirements for GAS functions.
- Verified with comprehensive updates to unit and integration tests.
This commit is contained in:
Ben Miller
2025-12-31 09:46:56 -07:00
parent fc25e877f1
commit e39bc862cc
11 changed files with 314 additions and 150 deletions

View File

@ -848,6 +848,8 @@
};
var state = new MediaState();
state.sku = "<?!= initialSku ?>";
state.title = "<?!= initialTitle ?>";
window.state = state;
// --- ES5 Refactor: UI ---
@ -1276,148 +1278,111 @@
*/
var controller = {
init() {
// Initialize by checking SKU once
// Since this is a modal, the selection cannot change during the session.
this.checkSku();
},
checkSku() {
google.script.run
.withSuccessHandler(info => {
// Info is now { sku, title } or null
const sku = info ? info.sku : null;
if (sku && sku !== state.sku) {
state.setSku(info);
this.loadMedia();
} else if (!sku && !state.sku) {
if (document.getElementById('error-ui').style.display !== 'flex') {
this.loadMedia(); // Likely to trigger error UI
}
}
})
.withFailureHandler(e => {
console.warn("SKU check failed", e);
// If it fails once at startup, we probably should alert or retry once,
// but for now let's just leave it. If it fails, the UI might hang on "Loading..."
// potentially better to trigger error UI?
if (document.getElementById('loading-ui').style.display !== 'none') {
alert("Failed to load product info: " + e.message);
}
})
.getSelectedProductInfo();
if (state.sku) {
// If we already have the SKU from the template, show the UI shell immediately
document.getElementById('loading-ui').style.display = 'none';
document.getElementById('main-ui').style.display = 'block';
ui.updateSku(state.sku, state.title);
ui.setLoadingState(true); // Loading... spinner inside the grid
}
this.loadMedia();
},
loadMedia(preserveLogs = false) {
// ... (Resolving SKU/Title Logic preserved below implicitly or we verify we didn't clip it)
// Actually, let's keep the resolving logic safe.
// We are replacing lines 1120-1191 roughly.
let sku = state.sku;
let title = state.title;
if (!sku) {
const domSku = document.getElementById('current-sku').innerText;
if (domSku && domSku !== '...') sku = domSku;
}
// CHECK FOR MISSING SKU
if (!sku || sku === '...') {
console.warn("No SKU found. Showing error.");
document.getElementById('loading-ui').style.display = 'none';
document.getElementById('main-ui').style.display = 'none';
document.getElementById('error-ui').style.display = 'flex';
return;
}
if (!title) {
const domTitle = document.getElementById('current-title').innerText;
if (domTitle && domTitle !== 'Loading...') title = domTitle;
}
document.getElementById('loading-ui').style.display = 'none';
document.getElementById('main-ui').style.display = 'block';
// Visual optimization: Show loading immediately
ui.setLoadingState(true);
state.setSku({ sku, title });
if (!preserveLogs) {
document.getElementById('status-log-container').innerHTML = ''; // Reset log
ui.logStatus('ready', 'Ready.', 'info');
} else {
// We might want to clear "Ready" if we are preserving logs
}
// ui.toggleLogBtn.style.display = 'inline-block'; // Removed
ui.logStatus('init', 'Initializing access...', 'info');
// 1. Diagnostics (Parallel)
google.script.run
.withSuccessHandler((diagnostics) => {
// Check Resumption
if (diagnostics.activeJobId) {
ui.logStatus('resume', 'Resuming active background job...', 'info');
ui.toggleSave(false);
ui.saveBtn.innerText = "Saving in background...";
controller.startLogPolling(diagnostics.activeJobId);
if (!ui.logCard.classList.contains('expanded')) ui.toggleLogExpand();
.withSuccessHandler(response => {
const { sku: serverSku, title: serverTitle, diagnostics, media, token } = response;
if (!serverSku) {
console.warn("No SKU found. Showing error.");
document.getElementById('loading-ui').style.display = 'none';
document.getElementById('main-ui').style.display = 'none';
document.getElementById('error-ui').style.display = 'flex';
return;
}
// Drive Status
if (diagnostics.drive.status === 'ok') {
ui.logStatus('drive', `Drive Folder: ok (${diagnostics.drive.fileCount} files) <a href="${diagnostics.drive.folderUrl}" target="_blank" style="margin-left:8px;">Open Folder ↗</a>`, 'success');
ui.setDriveLink(diagnostics.drive.folderUrl);
} else {
ui.logStatus('drive', `Drive Check Failed: ${diagnostics.drive.error}`, 'error');
// Update State
state.setSku({ sku: serverSku, title: serverTitle });
state.token = token;
// Update UI
document.getElementById('loading-ui').style.display = 'none';
document.getElementById('main-ui').style.display = 'block';
if (!preserveLogs) {
document.getElementById('status-log-container').innerHTML = ''; // Reset log
ui.logStatus('ready', 'Ready.', 'info');
}
// Capture Token
if (diagnostics.token) state.token = diagnostics.token;
ui.logStatus('init', 'Initializing access...', 'info');
// Shopify Status
if (diagnostics.shopify.status === 'ok') {
ui.logStatus('shopify', `Shopify Product: ok (${diagnostics.shopify.mediaCount} media) (ID: ${diagnostics.shopify.id}) <a href="${diagnostics.shopify.adminUrl}" target="_blank" style="margin-left:8px;">Open Admin ↗</a>`, 'success');
ui.setShopifyLink(diagnostics.shopify.adminUrl);
} else if (diagnostics.shopify.status === 'skipped') {
ui.logStatus('shopify', 'Shopify Product: Not linked/Found', 'info');
} else {
ui.logStatus('shopify', `Shopify Check Failed: ${diagnostics.shopify.error}`, 'error');
// Handle Diagnostics
if (diagnostics) {
// Check Resumption
if (diagnostics.activeJobId) {
ui.logStatus('resume', 'Resuming active background job...', 'info');
ui.toggleSave(false);
ui.saveBtn.innerText = "Saving in background...";
this.startLogPolling(diagnostics.activeJobId);
if (!ui.logCard.classList.contains('expanded')) ui.toggleLogExpand();
}
// Drive Status
if (diagnostics.drive.status === 'ok') {
ui.logStatus('drive', `Drive Folder: ok (${diagnostics.drive.fileCount} files) <a href="${diagnostics.drive.folderUrl}" target="_blank" style="margin-left:8px;">Open Folder ↗</a>`, 'success');
ui.setDriveLink(diagnostics.drive.folderUrl);
} else {
ui.logStatus('drive', `Drive Check Failed: ${diagnostics.drive.error}`, 'error');
}
// Shopify Status
if (diagnostics.shopify.status === 'ok') {
ui.logStatus('shopify', `Shopify Product: ok (${diagnostics.shopify.mediaCount} media) (ID: ${diagnostics.shopify.id}) <a href="${diagnostics.shopify.adminUrl}" target="_blank" style="margin-left:8px;">Open Admin ↗</a>`, 'success');
ui.setShopifyLink(diagnostics.shopify.adminUrl);
} else if (diagnostics.shopify.status === 'skipped') {
ui.logStatus('shopify', 'Shopify Product: Not linked/Found', 'info');
} else {
ui.logStatus('shopify', `Shopify Check Failed: ${diagnostics.shopify.error}`, 'error');
}
}
})
.withFailureHandler(function (err) {
ui.logStatus('fatal', `Diagnostics failed: ${err.message}`, 'error');
})
.getMediaDiagnostics(sku, "");
// 2. Load Full Media (Parallel)
ui.logStatus('fetch', 'Fetching full media state...', 'info');
google.script.run
.withSuccessHandler(function (items) {
// Normalize items
const normalized = items.map(function (i) {
return {
...i,
id: i.id || Math.random().toString(36).substr(2, 9),
status: i.source || 'drive_only',
source: i.source,
_deleted: false
};
});
// Handle Media Items
if (media) {
ui.logStatus('fetch', 'Fetched full media state.', 'success');
const normalized = media.map(function (i) {
return {
...i,
id: i.id || Math.random().toString(36).substr(2, 9),
status: i.source || 'drive_only',
source: i.source,
_deleted: false
};
});
state.setItems(normalized);
state.setItems(normalized);
if (!controller.hasRunMatching) {
controller.hasRunMatching = true;
controller.checkMatches(normalized);
} else {
controller.showGallery();
if (!this.hasRunMatching) {
this.hasRunMatching = true;
this.checkMatches(normalized);
} else {
this.showGallery();
}
}
})
.withFailureHandler(function (err) {
ui.logStatus('fatal', `Failed to load media: ${err.message}`, 'error');
ui.setLoadingState(false);
})
.getMediaForSku(sku);
.withFailureHandler(err => {
console.error("Initial load failed", err);
ui.logStatus('fatal', `Failed to load initialization data: ${err.message}`, 'error');
ui.setLoadingState(false);
})
.getMediaManagerInitialState(sku, title);
},