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:
@ -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);
|
||||
},
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user