Refactor Media Manager UI and Fix Infinite Loop

- **UI Refactor**:
  - Split Media Manager header into two distinct cards: 'Product Info' and 'Upload Options'.
  - 'Product Info' now displays the Product Title and SKU.
  - Renamed upload buttons to 'Google Drive', 'Google Photos', and 'Your Computer' for clarity.
  - Added global drag-and-drop support with overlay.
  - Replaced full-screen 'Connecting' overlay with an inline spinner for better UX and log visibility.

- **Backend**:
  - Renamed getSelectedSku to getSelectedProductInfo in mediaHandlers.ts to fetch and return both SKU and Title.
  - Updated global.ts exports and mediaHandlers.test.ts to support the new signature.

- **Fixes**:
  - Resolved an infinite loop issue in loadMedia caused by incorrect SKU state handling.
This commit is contained in:
Ben Miller
2025-12-28 16:34:02 -07:00
parent d9d884e1fc
commit c738ab3ef7
4 changed files with 102 additions and 49 deletions

View File

@ -302,29 +302,39 @@
<body>
<div id="main-ui" style="display:none">
<!-- Header Card -->
<!-- Product Info Card -->
<div class="card">
<div class="header">
<h2>Media Manager</h2>
<span id="current-sku" class="sku-badge">...</span>
</div>
<div class="upload-zone" id="drop-zone" onclick="document.getElementById('file-input').click()">
<div style="font-size: 32px; margin-bottom: 8px;">☁️</div>
<div style="font-size: 14px; font-weight: 500;">Drop files or click to upload</div>
<div style="font-size: 12px; color: var(--text-secondary); margin-top: 4px;">
Direct to Drive • JPG, PNG, MP4
<div style="display:flex; justify-content:space-between; align-items:flex-start;">
<div>
<div id="current-title" style="font-weight:600; font-size:16px; margin-bottom:4px; color:var(--text);">Loading...
</div>
<span id="current-sku" class="sku-badge">...</span>
</div>
<!-- Optional: Could put metadata here or small status -->
</div>
<input type="file" id="file-input" multiple style="display:none" onchange="controller.handleFiles(this.files)">
</div>
</div>
<div style="display: flex; gap: 8px; margin-top: 12px;">
<button onclick="controller.openPicker()" class="btn btn-secondary" style="flex: 1; font-size: 12px;">
📂 Drive Picker
</button>
<button onclick="controller.startPhotoSession()" class="btn btn-secondary" style="flex: 1; font-size: 12px;">
📸 Google Photos
</button>
<!-- Upload Options Card -->
<div class="card">
<div class="header" style="margin-bottom: 12px;">
<h3 style="margin:0; font-size:14px; color:var(--text);">Add Photos/Videos from...</h3>
</div>
<div style="display: flex; gap: 8px; width: 100%;">
<button onclick="controller.openPicker()" class="btn btn-secondary"
style="flex: 1; font-size: 13px; white-space: nowrap;">
Google Drive
</button>
<button onclick="controller.startPhotoSession()" class="btn btn-secondary"
style="flex: 1; font-size: 13px; white-space: nowrap;">
Google Photos
</button>
<button onclick="document.getElementById('file-input').click()" class="btn btn-secondary"
style="flex: 1; font-size: 13px;">
Your Computer
</button>
</div>
<input type="file" id="file-input" multiple style="display:none" onchange="controller.handleFiles(this.files)">
</div>
<!-- Photos Session UI -->
<div id="photos-session-ui"
@ -411,6 +421,12 @@
</div>
</div>
<div id="drop-overlay"
style="position: fixed; top:0; left:0; right:0; bottom:0; background: rgba(37, 99, 235, 0.9); z-index: 200; display: none; flex-direction: column; align-items: center; justify-content: center; color: white;">
<div style="font-size: 48px; margin-bottom: 16px;">☁️</div>
<div style="font-size: 24px; font-weight: 600;">Drop files to Upload</div>
</div>
<script>
/**
* State Management
@ -422,11 +438,12 @@
this.initialState = []; // For diffing "isDirty"
}
setSku(sku) {
this.sku = sku;
setSku(info) {
this.sku = info ? info.sku : null;
this.title = info ? info.title : "";
this.items = [];
this.initialState = [];
ui.updateSku(sku);
ui.updateSku(this.sku, this.title);
}
setItems(items) {
@ -562,8 +579,9 @@
this.toggleLogBtn.innerText = isVisible ? "View Log" : "Hide Log";
}
updateSku(sku) {
document.getElementById('current-sku').innerText = sku;
updateSku(sku, title) {
document.getElementById('current-sku').innerText = sku || '...';
document.getElementById('current-title').innerText = title || '';
document.getElementById('loading-ui').style.display = 'none';
document.getElementById('main-ui').style.display = 'block';
}
@ -791,23 +809,30 @@
checkSku() {
google.script.run
.withSuccessHandler(sku => {
.withSuccessHandler(info => {
// Info is now { sku, title } or null
const sku = info ? info.sku : null;
if (sku && sku !== state.sku) {
state.setSku(sku);
state.setSku(info); // Pass whole object
this.loadMedia();
}
})
.getSelectedSku();
.getSelectedProductInfo();
},
loadMedia(preserveLogs = false) {
const sku = document.getElementById('current-sku').innerText;
// Ensure Loading UI is visible and Main UI is hidden until ready
document.getElementById('loading-ui').style.display = 'block';
document.getElementById('main-ui').style.display = 'none';
// Resolve SKU/Title - prefer state, fallback to DOM
let sku = state.sku;
let title = state.title;
// Reset State (this calls ui.updateSku which might show main-ui, so we re-toggle below if needed)
state.setSku(sku);
if (!sku) {
const domSku = document.getElementById('current-sku').innerText;
if (domSku && domSku !== '...') sku = domSku;
const domTitle = document.getElementById('current-title').innerText;
if (domTitle && domTitle !== 'Loading...') title = domTitle;
}
// Show Main UI immediately so logs are visible
document.getElementById('loading-ui').style.display = 'none';
@ -816,6 +841,9 @@
// Set Inline Loading State
ui.setLoadingState(true);
// Reset State (this calls ui.updateSku)
state.setSku({ sku, title });
if (!preserveLogs) {
document.getElementById('status-log-container').innerHTML = '';
}
@ -1022,14 +1050,33 @@
// Init
controller.init();
// Drag & Drop Handlers for upload zone (Visual only)
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('dragover'); });
dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); });
dropZone.addEventListener('drop', (e) => {
// Drag & Drop Handlers (Global)
const dropOverlay = document.getElementById('drop-overlay');
let dragCounter = 0;
document.addEventListener('dragenter', (e) => {
e.preventDefault();
dragCounter++;
dropOverlay.style.display = 'flex';
});
document.addEventListener('dragleave', (e) => {
e.preventDefault();
dragCounter--;
if (dragCounter === 0) {
dropOverlay.style.display = 'none';
}
});
document.addEventListener('dragover', (e) => { e.preventDefault(); });
document.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
controller.handleFiles(e.dataTransfer.files);
dragCounter = 0;
dropOverlay.style.display = 'none';
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
controller.handleFiles(e.dataTransfer.files);
}
});
</script>