From 6aea2bf6305e6d266f7ec7d54bd1966b050e7f79 Mon Sep 17 00:00:00 2001 From: kj_sh604 Date: Mon, 1 Jun 2026 13:13:13 -0400 Subject: refactor: revert back to image upload just discovered that localStorage has limits --- src/index.html | 126 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 61 insertions(+), 65 deletions(-) (limited to 'src/index.html') diff --git a/src/index.html b/src/index.html index cbcc7cd..7f5a476 100644 --- a/src/index.html +++ b/src/index.html @@ -116,7 +116,7 @@ questions? -
F5 to present · Esc to exit · ← → h l j k space enter to navigate · images are local to this browser
+F5 to present · Esc to exit · ← → h l j k space enter to navigate · images upload to server and preload before presenting
@@ -179,9 +179,7 @@ questions? }, init() { - this.restoreImageState(); this.restoreState(); - this.ensureBundledImageInLocalStore(); this.loadFonts(); this.bindEvents(); this.updateColors(); @@ -448,7 +446,27 @@ questions? return this.defaultBundledImage.url; } - return canonicalName; + return 'uploads/' + encodeURIComponent(canonicalName); + }, + + listSlideImageUrls(slides) { + const urls = new Set(); + for (const slide of slides) { + if (!slide || !slide.img) continue; + const src = this.resolveSlideImageSrc(slide.img); + if (src) urls.add(src); + } + return [...urls]; + }, + + async preloadSlideImages(slides) { + const urls = this.listSlideImageUrls(slides); + await Promise.all(urls.map(src => new Promise((resolve) => { + const img = new Image(); + img.onload = () => resolve(); + img.onerror = () => resolve(); + img.src = src; + }))); }, buildSentFontStack(primaryFamily) { @@ -548,9 +566,6 @@ questions? input.addEventListener('input', () => { localStorage.setItem('sent-web-content', input.value); }); - input.addEventListener('blur', () => { - this.pruneUnusedLocalImages(input.value); - }); input.addEventListener('keydown', e => this.handleEditorKeydown(e)); document.addEventListener('keydown', e => this.handleKeydown(e)); @@ -695,9 +710,8 @@ questions? }, // presentation controls - startPresentation() { + async startPresentation() { const text = document.getElementById('input').value; - this.pruneUnusedLocalImages(text); this.slides = this.parseSent(text); if (this.slides.length === 0) { @@ -705,6 +719,22 @@ questions? return; } + const btn = document.getElementById('btn-present'); + const originalBtnText = btn ? btn.textContent : 'present'; + if (btn) { + btn.disabled = true; + btn.textContent = 'preloading images...'; + } + + try { + await this.preloadSlideImages(this.slides); + } finally { + if (btn) { + btn.disabled = false; + btn.textContent = originalBtnText; + } + } + this.idx = 0; this.presenting = true; document.getElementById('presentation').classList.add('active'); @@ -898,68 +928,48 @@ questions? return; } - const rateCheck = this.canStoreUpload(); - if (!rateCheck.ok) { - status.textContent = `error: ${rateCheck.error}`; - done(); - return; - } - - status.textContent = 'storing locally...'; + status.textContent = 'uploading...'; try { - const ta = document.getElementById('input'); - this.pruneUnusedLocalImages(ta.value); - - if (Object.keys(this.imageStore).length >= this.uploadPolicy.maxImages) { - status.textContent = `error: local image limit reached (${this.uploadPolicy.maxImages})`; - return; - } + const fd = new FormData(); + fd.append('image', file); - const filename = this.makeLocalImageName(file, ext); - const dataUrl = await this.fileToDataUrl(file); + const res = await fetch('/upload', { + method: 'POST', + body: fd, + }); - const nextStore = { - ...this.imageStore, - [filename]: { - dataUrl, - mime: file.type, - bytes: file.size, - createdAt: Date.now(), - }, - }; + let data = {}; + try { + data = await res.json(); + } catch (_) {} - const nextTotalBytes = this.imageStoreBytes(nextStore); - if (nextTotalBytes > this.uploadPolicy.maxTotalBytes) { - status.textContent = `error: local quota exceeded (max ${this.formatBytes(this.uploadPolicy.maxTotalBytes)} total)`; + if (!res.ok || data.error) { + const msg = data.error || `upload failed (${res.status})`; + status.textContent = `error: ${msg}`; return; } - this.imageStore = nextStore; - this.saveImageStore(); - - this.uploadTimestamps.push(Date.now()); - this.pruneUploadTimestamps(); - this.saveUploadRateState(); + if (!data.filename) { + status.textContent = 'error: invalid upload response'; + return; + } + const ta = document.getElementById('input'); const pos = ta.selectionStart; const txt = ta.value; - const ins = `\n@${filename}\n`; + const ins = `\n@${data.filename}\n`; ta.value = txt.substring(0, pos) + ins + txt.substring(pos); ta.selectionStart = ta.selectionEnd = pos + ins.length; ta.focus(); localStorage.setItem('sent-web-content', ta.value); - status.textContent = `stored: ${filename}`; + status.textContent = `photo inserted`; setTimeout(() => { status.textContent = ''; }, 3000); } catch (err) { - if (err && err.name === 'QuotaExceededError') { - status.textContent = 'error: browser storage quota reached'; - } else { - status.textContent = 'error: failed to store image locally'; - } + status.textContent = 'upload failed'; console.error(err); } finally { done(); @@ -969,20 +979,6 @@ questions? // download .sent file for local usage (base64-encoded to preserve unicode and avoid filename issues) downloadSent() { const text = document.getElementById('input').value; - let localRefs = 0; - for (const name of this.referencedImageNames(text)) { - if (this.imageStore[name]) { - localRefs += 1; - } - } - - if (localRefs > 0) { - const ok = window.confirm( - `this deck references ${localRefs} locally stored image(s). the .sent file will not include image binaries. continue?` - ); - if (!ok) return; - } - const blob = new Blob([text], { type: 'text/plain' }); -- cgit v1.2.3