Fix Unexpected Keyword in MediaManager and Add Build Linting

- Fix corrupted line in src/MediaManager.html causing syntax error.
- Add ESLint integration to build process to prevent future syntax errors.
- Create .eslintrc.js with TypeScript and HTML support.
- Relax strict lint rules to accommodate existing codebase.
This commit is contained in:
Ben Miller
2025-12-31 07:02:16 -07:00
parent 3abc57f45a
commit d34f9a1417
5 changed files with 1523 additions and 108 deletions

View File

@ -164,25 +164,24 @@
/* Processing State */
.media-item.processing-card {
background-color: #334155 !important;
position: relative; /* Ensure absolute children are contained */
/* Removed flex centering to let image stretch */
position: relative;
}
.media-item.processing-card .media-content {
display: block !important;
opacity: 0.8; /* Lighter overlay (was 0.4) */
filter: grayscale(30%); /* Less grey (was 80%) */
opacity: 0.8;
filter: grayscale(30%);
width: 100%;
height: 100%;
object-fit: contain; /* Ensure it fills */
object-fit: contain;
}
.processing-icon {
position: absolute;
bottom: 6px;
right: 6px;
font-size: 20px; /* Smaller */
z-index: 20; /* Above badges */
font-size: 20px;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
@ -383,60 +382,67 @@
/* Log Card Styles */
.log-card {
background: var(--surface);
color: var(--text);
border-radius: 8px;
margin-top: 16px;
font-family: monospace;
font-size: 11px;
overflow: hidden;
transition: all 0.2s ease;
border: 1px solid var(--border);
box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
background: var(--surface);
color: var(--text);
border-radius: 8px;
margin-top: 16px;
font-family: monospace;
font-size: 11px;
overflow: hidden;
transition: all 0.2s ease;
border: 1px solid var(--border);
box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
}
.log-header {
padding: 8px 12px;
background: #f8fafc; /* Slightly darker than surface */
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
user-select: none;
color: var(--text-secondary);
font-weight: 500;
padding: 8px 12px;
background: #f8fafc;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
user-select: none;
color: var(--text-secondary);
font-weight: 500;
}
.log-content {
padding: 12px;
max-height: 16px; /* ~1 line */
overflow-y: auto;
transition: max-height 0.3s ease;
display: flex;
flex-direction: column;
gap: 4px;
background: var(--surface);
padding: 12px;
/* ~1 line */
max-height: 16px;
overflow-y: auto;
transition: max-height 0.3s ease;
display: flex;
flex-direction: column;
gap: 4px;
background: var(--surface);
}
.log-card.expanded .log-content {
max-height: 300px; /* ~20 lines */
/* ~20 lines */
max-height: 300px;
}
.log-entry {
line-height: 1.4;
border-bottom: 1px solid #f1f5f9;
padding-bottom: 2px;
line-height: 1.4;
border-bottom: 1px solid #f1f5f9;
padding-bottom: 2px;
}
.log-entry:last-child {
border-bottom: none;
}
.log-entry:last-child { border-bottom: none; }
/* Scrollbar for log */
.log-content::-webkit-scrollbar {
width: 6px;
}
.log-content::-webkit-scrollbar-track {
background: transparent;
}
.log-content::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
@ -460,42 +466,7 @@
</div>
</div>
<!-- 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"
style="display:none; margin-top:12px; padding:12px; background:#f0f9ff; border-radius:8px; border:1px solid #bae6fd;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
<span style="font-weight:600; font-size:12px; color:#0369a1;">Photo Picker Session</span>
<button onclick="ui.closePhotoSession()"
style="background:none; border:none; color:#0369a1; cursor:pointer; font-size:16px;">×</button>
</div>
<a id="photos-session-link" href="#" target="_blank" class="btn"
style="background:#0ea5e9; text-decoration:none; margin-bottom:8px;">
Open Google Photos ↗
</a>
<div id="photos-session-status" style="font-size:11px; color:#64748b; text-align:center;">Initializing...</div>
</div>
@ -538,16 +509,54 @@
</button>
</div>
</div>
<!-- Permanent Log Card -->
<div id="log-card" class="log-card">
<div class="log-header" onclick="ui.toggleLogExpand()">
<span style="font-weight:600;">Activity Log</span>
<span id="log-toggle-icon"></span>
</div>
<div id="status-log-container" class="log-content">
<div class="log-entry" style="color: #94a3b8;">Ready.</div>
</div>
</div>
<div id="upload-section" style="display:none;">
<!-- 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 id="btn-upload-drive" onclick="controller.openPicker()" class="btn btn-secondary"
style="flex: 1; font-size: 13px; white-space: nowrap;">
Google Drive
</button>
<button id="btn-upload-photos" onclick="controller.startPhotoSession()" class="btn btn-secondary"
style="flex: 1; font-size: 13px; white-space: nowrap;">
Google Photos
</button>
<button id="btn-upload-computer" 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"
style="display:none; margin-top:12px; padding:12px; background:#f0f9ff; border-radius:8px; border:1px solid #bae6fd;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
<span style="font-weight:600; font-size:12px; color:#0369a1;">Photo Picker Session</span>
<button onclick="ui.closePhotoSession()"
style="background:none; border:none; color:#0369a1; cursor:pointer; font-size:16px;">×</button>
</div>
<a id="photos-session-link" href="#" target="_blank" class="btn"
style="background:#0ea5e9; text-decoration:none; margin-bottom:8px;">
Open Google Photos ↗
</a>
<div id="photos-session-status" style="font-size:11px; color:#64748b; text-align:center;">Initializing...</div>
</div>
</div>
<!-- Permanent Log Card -->
<div id="log-card" class="log-card">
<div class="log-header" onclick="ui.toggleLogExpand()">
<span style="font-weight:600;">Activity Log</span>
<span id="log-toggle-icon"></span>
</div>
<div id="status-log-container" class="log-content">
<div class="log-entry" style="color: #94a3b8;">Ready.</div>
</div>
</div>
</div>
@ -793,10 +802,10 @@
if (this.shopifyUrl) this.linksContainer.innerHTML += '<a href="' + this.shopifyUrl + '" target="_blank" style="color:var(--primary); text-decoration:none; margin-left:8px;">Shopify ↗</a>';
};
UI.prototype.toggleLogExpand = function () {
this.logCard.classList.toggle('expanded');
var icon = this.logCard.querySelector('#log-toggle-icon');
icon.innerText = this.logCard.classList.contains('expanded') ? '▼' : '▲';
UI.prototype.toggleLogExpand = function () {
this.logCard.classList.toggle('expanded');
var icon = this.logCard.querySelector('#log-toggle-icon');
icon.innerText = this.logCard.classList.contains('expanded') ? '▼' : '▲';
};
UI.prototype.updateSku = function (sku, title) {
@ -1326,6 +1335,8 @@
},
handleFiles(fileList) {
if (!fileList || fileList.length === 0) return;
this.setPickerState(true);
Array.from(fileList).forEach(file => {
const reader = new FileReader();
reader.onload = (e) => {
@ -1334,6 +1345,12 @@
google.script.run
.withSuccessHandler(() => {
this.loadMedia();
this.setPickerState(false);
})
.withFailureHandler(err => {
console.error(err);
alert("Upload failed: " + err.message);
this.setPickerState(false);
})
.saveFileToDrive(state.sku, file.name, file.type, data);
};
@ -1341,27 +1358,57 @@
});
},
// --- Picker ---
// --- Picker ---
setPickerState(isActive) {
const btnDrive = document.getElementById('btn-upload-drive');
const btnPhotos = document.getElementById('btn-upload-photos');
const btnComp = document.getElementById('btn-upload-computer');
[btnDrive, btnPhotos, btnComp].forEach(btn => {
if (btn) {
btn.disabled = isActive;
btn.style.opacity = isActive ? '0.5' : '1';
btn.style.cursor = isActive ? 'not-allowed' : 'pointer';
}
});
},
openPicker() {
if (!pickerApiLoaded) return alert("API Loading...");
google.script.run.withSuccessHandler(c => createPicker(c)).getPickerConfig();
this.setPickerState(true);
google.script.run
.withSuccessHandler(c => createPicker(c))
.withFailureHandler(e => {
alert("Failed to load picker: " + e.message);
this.setPickerState(false);
})
.getPickerConfig();
},
importFromPicker(fileId, mime, name, url) {
google.script.run
.withSuccessHandler(() => this.loadMedia())
.withSuccessHandler(() => {
this.loadMedia();
this.setPickerState(false);
})
.withFailureHandler(e => {
alert("Import failed: " + e.message);
this.setPickerState(false);
})
.importFromPicker(state.sku, fileId, mime, name, url);
},
// --- Photos (Popup Flow) ---
startPhotoSession() {
this.setPickerState(true);
ui.updatePhotoStatus("Starting session...");
google.script.run
.withSuccessHandler(session => {
ui.showPhotoSession(session.pickerUri);
this.pollPhotoSession(session.id);
})
.withFailureHandler(e => {
ui.updatePhotoStatus("Failed: " + e.message);
this.setPickerState(false);
})
.createPhotoSession();
},
@ -1374,13 +1421,18 @@
if (res.status === 'complete') {
processing = true;
ui.updatePhotoStatus("Importing photos...");
controller.processPhotoItems(res.mediaItems);
this.processPhotoItems(res.mediaItems);
} else if (res.status === 'error') {
ui.updatePhotoStatus("Error: " + res.message);
this.setPickerState(false);
} else {
setTimeout(check, 2000);
}
})
.withFailureHandler(e => {
ui.updatePhotoStatus("Error polling: " + e.message);
this.setPickerState(false);
})
.checkPhotoSession(sessionId);
};
check();
@ -1412,9 +1464,18 @@
if (done === items.length) {
ui.updatePhotoStatus("Done!");
controller.loadMedia();
this.setPickerState(false);
setTimeout(() => ui.closePhotoSession(), 2000);
}
})
.withFailureHandler(e => {
console.error("Import failed", e);
// If last one
done++;
if (done === items.length) {
this.setPickerState(false);
}
})
.importFromPicker(state.sku, null, mimeType, filename, url);
});
},
@ -1523,11 +1584,11 @@
document.getElementById('btn-match-confirm').innerText = "Linking...";
document.getElementById('btn-match-skip').disabled = true;
// ui.logStatus('link', 'Linking ' + match.drive.filename + '...', 'info');
ui.logStatus('link', 'Linking ' + match.drive.filename + '...', 'info');
google.script.run
.withSuccessHandler(function () {
// ui.logStatus('link', 'Linked ' + match.drive.filename, 'success');
ui.logStatus('link', 'Linked ' + match.drive.filename, 'success');
_this.nextMatch();
})
.withFailureHandler(function (e) {
@ -1579,6 +1640,7 @@
showGallery() {
document.getElementById('loading-ui').style.display = 'none';
document.getElementById('main-ui').style.display = 'block';
document.getElementById('upload-section').style.display = 'block';
ui.logStatus('done', 'Finished loading.', 'success');
// setTimeout(function () { ui.toggleLog(false); }, 1000); // Removed auto-hide
@ -1692,6 +1754,9 @@
// Drive File (Always, since we removed Photos view)
controller.importFromPicker(doc.id, doc.mimeType, doc.name, null);
} else if (data.action == google.picker.Action.CANCEL) {
console.log("Picker cancelled");
controller.setPickerState(false);
}
})
.build()