openvibers/openvibe.tools Commits
Use shared local picker for watermark and thumbnail images (#309)
Changes
@@@ -19,6 +19,7 @@</script><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css">++ <link rel="stylesheet" href="/shared/ov-upload.css"><script src="/shared/theme-loader.js" defer></script><style>.thumb-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }@@@ -37,9 +38,6 @@.ctrl-row input[type="color"] { width: 36px; height: 30px; border: 1px solid var(--border); background: var(--bg-secondary); border-radius: 8px; cursor: pointer; padding: 2px; }.ctrl-row input[type="range"] { flex: 1; }.ctrl-row .val { min-width: 32px; text-align: right; font-size: .78rem; color: var(--text-muted); font-family: monospace; }-- .upload-btn { width: 100%; padding: .75rem; border-radius: 12px; border: 2px dashed var(--border); background: var(--bg-tertiary); color: var(--text-secondary); cursor: pointer; text-align: center; transition: all var(--ov-dur-2, .2s); font-size: .9rem; }-- .upload-btn:hover { border-color: var(--accent); color: var(--accent); }-- .upload-btn input { display: none; }.size-pills { display: flex; gap: .4rem; flex-wrap: wrap; }.size-pill { padding: .35rem .7rem; border-radius: 16px; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-secondary); font-size: .75rem; cursor: pointer; transition: all var(--ov-dur-2, .15s); }.size-pill:hover, .size-pill.active { border-color: var(--accent); color: var(--accent); background: rgba(192,150,92,0.1); }@@@ -70,10 +68,9 @@<div class="thumb-controls"><div class="ctrl-group"><h3><i class="fas fa-image"></i> Background</h3>-- <label class="upload-btn" id="uploadBtn">-- <input type="file" id="fileInput" accept="image/*">-- <i class="fas fa-cloud-arrow-up"></i> Upload Background Image-- </label>++ <div id="backgroundPicker"></div>++ <p class="subtitle">PNG, JPG, WebP or another image · up to 20 MB. Your image stays in this browser.</p>++ <p id="backgroundError" role="alert" hidden></p><div class="ctrl-row" style="margin-top: .75rem;"><label>BG Color</label><input type="color" id="bgColor" value="#0b0c12">@@@ -128,6 +125,7 @@<script src="/shared/account-switcher.js"></script><script src="/js/logo-engine.js"></script><script src="/js/shared.js"></script>++ <script src="/shared/ov-upload.js"></script><script>OpenVibeUI.initPage();const E = LogoOpenVibeEngine;@@@ -153,17 +151,28 @@};});-- // File upload-- document.getElementById('fileInput').addEventListener('change', e => {-- const file = e.target.files[0];-- if (!file) return;-- const reader = new FileReader();-- reader.onload = ev => {-- const img = new Image();-- img.onload = () => { bgImg = img; renderThumb(); };-- img.src = ev.target.result;-- };-- reader.readAsDataURL(file);++ // Local background image: the shared picker owns drops, file rows and removal.++ const backgroundError = document.getElementById('backgroundError');++ const backgroundPicker = OVUpload.picker(document.getElementById('backgroundPicker'), {++ label: 'Choose a background image', accept: 'image/*', multiple: false,++ onFile: (file, row) => {++ if (!file.type.startsWith('image/') || !file.size || file.size > 20 * 1048576) {++ row.dispatchEvent(new Event('remove')); row.remove();++ backgroundError.textContent = file.size > 20 * 1048576 ? 'Choose an image under 20 MB.' : 'Choose an image this browser can read.';++ backgroundError.hidden = false; return;++ }++ backgroundError.hidden = true;++ const reader = new FileReader();++ reader.onload = ev => {++ const img = new Image();++ img.onload = () => { backgroundPicker.rows.querySelectorAll('.ov-upload__row').forEach(old => { if (old !== row) { old.dispatchEvent(new Event('remove')); old.remove(); } }); bgImg = img; renderThumb(); };++ img.onerror = () => { row.dispatchEvent(new Event('remove')); row.remove(); backgroundError.textContent = 'That image could not be opened.'; backgroundError.hidden = false; };++ img.src = ev.target.result;++ };++ reader.onerror = () => { row.dispatchEvent(new Event('remove')); row.remove(); backgroundError.textContent = 'That image could not be read.'; backgroundError.hidden = false; };++ reader.readAsDataURL(file);++ },++ onRemove: () => { bgImg = null; renderThumb(); }});async function renderThumb() {@@@ -19,6 +19,7 @@</script><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css">++ <link rel="stylesheet" href="/shared/ov-upload.css"><script src="/shared/theme-loader.js" defer></script><style>.wm-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }@@@ -26,9 +27,6 @@.wm-preview canvas { max-width: 100%; height: auto; border-radius: 8px; }.wm-preview .placeholder { color: var(--text-muted); font-size: 1.1rem; }.wm-preview .placeholder i { font-size: 3rem; display: block; margin-bottom: 1rem; color: var(--border); }-- .drop-zone { border: 2px dashed var(--border); border-radius: var(--radius); padding: 2rem; text-align: center; cursor: pointer; transition: all var(--ov-dur-2, .2s); }-- .drop-zone:hover, .drop-zone.drag-over { border-color: var(--accent); background: rgba(192,150,92,0.05); }-- .drop-zone input { display: none; }.wm-controls { display: flex; flex-direction: column; gap: 1rem; }.ctrl-group { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; }.ctrl-group h3 { margin: 0 0 .75rem; color: var(--accent); font-size: .9rem; }@@@ -53,11 +51,9 @@<div class="wm-layout"><div>-- <div class="drop-zone" id="dropZone" onclick="document.getElementById('fileInput').click()">-- <input type="file" id="fileInput" accept="image/*">-- <i class="fas fa-cloud-arrow-up" style="font-size: 2rem; color: var(--text-muted); margin-bottom: .5rem; display: block;"></i>-- <p style="color: var(--text-secondary); margin: 0;">Click or drag an image here</p>-- </div>++ <div id="imagePicker"></div>++ <p class="subtitle">PNG, JPG, WebP or another image · up to 20 MB. Your image stays in this browser.</p>++ <p id="imageError" role="alert" hidden></p><div class="wm-preview" id="previewArea" style="margin-top: 1.5rem;"><div class="placeholder" id="placeholder"><i class="fas fa-image"></i>@@@ -111,6 +107,7 @@<script src="/shared/account-switcher.js"></script><script src="/js/logo-engine.js"></script><script src="/js/shared.js"></script>++ <script src="/shared/ov-upload.js"></script><script>OpenVibeUI.initPage();const E = LogoOpenVibeEngine;@@@ -132,25 +129,29 @@};});-- // File handling-- const dropZone = document.getElementById('dropZone');-- const fileInput = document.getElementById('fileInput');---- ['dragenter', 'dragover'].forEach(evt => dropZone.addEventListener(evt, e => { e.preventDefault(); dropZone.classList.add('drag-over'); }));-- ['dragleave', 'drop'].forEach(evt => dropZone.addEventListener(evt, e => { e.preventDefault(); dropZone.classList.remove('drag-over'); }));-- dropZone.addEventListener('drop', e => { if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]); });-- fileInput.addEventListener('change', () => { if (fileInput.files.length) handleFile(fileInput.files[0]); });---- function handleFile(file) {-- if (!file.type.startsWith('image/')) return OpenVibeUI.showToast('Please upload an image file', 'error');-- const reader = new FileReader();-- reader.onload = e => {-- const img = new Image();-- img.onload = () => { loadedImg = img; document.getElementById('placeholder').style.display = 'none'; canvas.style.display = 'block'; document.getElementById('dlBtn').disabled = false; renderPreview(); };-- img.src = e.target.result;-- };-- reader.readAsDataURL(file);-- }++ // Local image selection: the shared picker owns drops, file rows and removal.++ const imageError = document.getElementById('imageError');++ const picker = OVUpload.picker(document.getElementById('imagePicker'), {++ label: 'Choose an image to watermark', accept: 'image/*', multiple: false,++ onFile: (file, row) => {++ if (!file.type.startsWith('image/') || !file.size || file.size > 20 * 1048576) {++ row.dispatchEvent(new Event('remove')); row.remove();++ imageError.textContent = file.size > 20 * 1048576 ? 'Choose an image under 20 MB.' : 'Choose an image this browser can read.';++ imageError.hidden = false; return;++ }++ imageError.hidden = true;++ const reader = new FileReader();++ reader.onload = e => {++ const img = new Image();++ img.onload = () => { picker.rows.querySelectorAll('.ov-upload__row').forEach(old => { if (old !== row) { old.dispatchEvent(new Event('remove')); old.remove(); } }); loadedImg = img; document.getElementById('placeholder').style.display = 'none'; canvas.style.display = 'block'; document.getElementById('dlBtn').disabled = false; renderPreview(); };++ img.onerror = () => { row.dispatchEvent(new Event('remove')); row.remove(); imageError.textContent = 'That image could not be opened.'; imageError.hidden = false; };++ img.src = e.target.result;++ };++ reader.onerror = () => { row.dispatchEvent(new Event('remove')); row.remove(); imageError.textContent = 'That image could not be read.'; imageError.hidden = false; };++ reader.readAsDataURL(file);++ },++ onRemove: () => { loadedImg = null; document.getElementById('placeholder').style.display = ''; canvas.style.display = 'none'; document.getElementById('dlBtn').disabled = true; }++ });function hexToRgba(hex, alpha) {const r = parseInt(hex.slice(1, 3), 16);@@@ -42,6 +42,15 @@ function pageOf(host, file) {return frameAround(html, host, { file, hostnameMap: HOSTNAME_MAP });}++// Browser-only image tools use the same accessible picker without sending files to a server.++for (const [host, file] of [['watermark.openvibe.tools', 'watermark.html'], ['thumbnail.openvibe.tools', 'thumbnail.html']]) {++ const html = pageOf(host, file);++ assert.ok(html.includes('/shared/ov-upload.css') && html.includes('/shared/ov-upload.js'), `${host}: shared picker assets`);++ assert.ok(html.includes('OVUpload.picker(') && html.includes('role="alert"'), `${host}: picker and readable errors`);++ assert.ok(html.includes('Your image stays in this browser.'), `${host}: local processing is explained`);++ assert.ok(!html.includes('type="file"'), `${host}: old picker is gone`);++}++// ── Every text tool's page is a framed tool page, in the same order ──{for (const [host, file] of TOOL_HOSTS) {
@@ -19,6 +19,7 @@
@@ -19,6 +19,7 @@
</script>
</script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
+ <link rel="stylesheet" href="/shared/ov-upload.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
.thumb-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
.thumb-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
@@ -37,9 +38,6 @@
@@ -37,9 +38,6 @@
.ctrl-row input[type="color"] { width: 36px; height: 30px; border: 1px solid var(--border); background: var(--bg-secondary); border-radius: 8px; cursor: pointer; padding: 2px; }
.ctrl-row input[type="color"] { width: 36px; height: 30px; border: 1px solid var(--border); background: var(--bg-secondary); border-radius: 8px; cursor: pointer; padding: 2px; }
.ctrl-row input[type="range"] { flex: 1; }
.ctrl-row input[type="range"] { flex: 1; }
.ctrl-row .val { min-width: 32px; text-align: right; font-size: .78rem; color: var(--text-muted); font-family: monospace; }
.ctrl-row .val { min-width: 32px; text-align: right; font-size: .78rem; color: var(--text-muted); font-family: monospace; }
- .upload-btn { width: 100%; padding: .75rem; border-radius: 12px; border: 2px dashed var(--border); background: var(--bg-tertiary); color: var(--text-secondary); cursor: pointer; text-align: center; transition: all var(--ov-dur-2, .2s); font-size: .9rem; }
- .upload-btn:hover { border-color: var(--accent); color: var(--accent); }
- .upload-btn input { display: none; }
.size-pills { display: flex; gap: .4rem; flex-wrap: wrap; }
.size-pills { display: flex; gap: .4rem; flex-wrap: wrap; }
.size-pill { padding: .35rem .7rem; border-radius: 16px; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-secondary); font-size: .75rem; cursor: pointer; transition: all var(--ov-dur-2, .15s); }
.size-pill { padding: .35rem .7rem; border-radius: 16px; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-secondary); font-size: .75rem; cursor: pointer; transition: all var(--ov-dur-2, .15s); }
.size-pill:hover, .size-pill.active { border-color: var(--accent); color: var(--accent); background: rgba(192,150,92,0.1); }
.size-pill:hover, .size-pill.active { border-color: var(--accent); color: var(--accent); background: rgba(192,150,92,0.1); }
@@ -70,10 +68,9 @@
@@ -70,10 +68,9 @@
<div class="thumb-controls">
<div class="thumb-controls">
<div class="ctrl-group">
<div class="ctrl-group">
<h3><i class="fas fa-image"></i> Background</h3>
<h3><i class="fas fa-image"></i> Background</h3>
- <label class="upload-btn" id="uploadBtn">
- <input type="file" id="fileInput" accept="image/*">
- <i class="fas fa-cloud-arrow-up"></i> Upload Background Image
- </label>
+ <div id="backgroundPicker"></div>
+ <p class="subtitle">PNG, JPG, WebP or another image · up to 20 MB. Your image stays in this browser.</p>
+ <p id="backgroundError" role="alert" hidden></p>
<div class="ctrl-row" style="margin-top: .75rem;">
<div class="ctrl-row" style="margin-top: .75rem;">
<label>BG Color</label>
<label>BG Color</label>
<input type="color" id="bgColor" value="#0b0c12">
<input type="color" id="bgColor" value="#0b0c12">
@@ -128,6 +125,7 @@
@@ -128,6 +125,7 @@
<script src="/shared/account-switcher.js"></script>
<script src="/shared/account-switcher.js"></script>
<script src="/js/logo-engine.js"></script>
<script src="/js/logo-engine.js"></script>
<script src="/js/shared.js"></script>
<script src="/js/shared.js"></script>
+ <script src="/shared/ov-upload.js"></script>
<script>
<script>
OpenVibeUI.initPage();
OpenVibeUI.initPage();
const E = LogoOpenVibeEngine;
const E = LogoOpenVibeEngine;
@@ -153,17 +151,28 @@
@@ -153,17 +151,28 @@
};
};
});
});
- // File upload
- document.getElementById('fileInput').addEventListener('change', e => {
- const file = e.target.files[0];
- if (!file) return;
- const reader = new FileReader();
- reader.onload = ev => {
- const img = new Image();
- img.onload = () => { bgImg = img; renderThumb(); };
- img.src = ev.target.result;
- };
- reader.readAsDataURL(file);
+ // Local background image: the shared picker owns drops, file rows and removal.
+ const backgroundError = document.getElementById('backgroundError');
+ const backgroundPicker = OVUpload.picker(document.getElementById('backgroundPicker'), {
+ label: 'Choose a background image', accept: 'image/*', multiple: false,
+ onFile: (file, row) => {
+ if (!file.type.startsWith('image/') || !file.size || file.size > 20 * 1048576) {
+ row.dispatchEvent(new Event('remove')); row.remove();
+ backgroundError.textContent = file.size > 20 * 1048576 ? 'Choose an image under 20 MB.' : 'Choose an image this browser can read.';
+ backgroundError.hidden = false; return;
+ }
+ backgroundError.hidden = true;
+ const reader = new FileReader();
+ reader.onload = ev => {
+ const img = new Image();
+ img.onload = () => { backgroundPicker.rows.querySelectorAll('.ov-upload__row').forEach(old => { if (old !== row) { old.dispatchEvent(new Event('remove')); old.remove(); } }); bgImg = img; renderThumb(); };
+ img.onerror = () => { row.dispatchEvent(new Event('remove')); row.remove(); backgroundError.textContent = 'That image could not be opened.'; backgroundError.hidden = false; };
+ img.src = ev.target.result;
+ };
+ reader.onerror = () => { row.dispatchEvent(new Event('remove')); row.remove(); backgroundError.textContent = 'That image could not be read.'; backgroundError.hidden = false; };
+ reader.readAsDataURL(file);
+ },
+ onRemove: () => { bgImg = null; renderThumb(); }
});
});
async function renderThumb() {
async function renderThumb() {
@@ -19,6 +19,7 @@
@@ -19,6 +19,7 @@
</script>
</script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
+ <link rel="stylesheet" href="/shared/ov-upload.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
.wm-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
.wm-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
@@ -26,9 +27,6 @@
@@ -26,9 +27,6 @@
.wm-preview canvas { max-width: 100%; height: auto; border-radius: 8px; }
.wm-preview canvas { max-width: 100%; height: auto; border-radius: 8px; }
.wm-preview .placeholder { color: var(--text-muted); font-size: 1.1rem; }
.wm-preview .placeholder { color: var(--text-muted); font-size: 1.1rem; }
.wm-preview .placeholder i { font-size: 3rem; display: block; margin-bottom: 1rem; color: var(--border); }
.wm-preview .placeholder i { font-size: 3rem; display: block; margin-bottom: 1rem; color: var(--border); }
- .drop-zone { border: 2px dashed var(--border); border-radius: var(--radius); padding: 2rem; text-align: center; cursor: pointer; transition: all var(--ov-dur-2, .2s); }
- .drop-zone:hover, .drop-zone.drag-over { border-color: var(--accent); background: rgba(192,150,92,0.05); }
- .drop-zone input { display: none; }
.wm-controls { display: flex; flex-direction: column; gap: 1rem; }
.wm-controls { display: flex; flex-direction: column; gap: 1rem; }
.ctrl-group { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; }
.ctrl-group { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; }
.ctrl-group h3 { margin: 0 0 .75rem; color: var(--accent); font-size: .9rem; }
.ctrl-group h3 { margin: 0 0 .75rem; color: var(--accent); font-size: .9rem; }
@@ -53,11 +51,9 @@
@@ -53,11 +51,9 @@
<div class="wm-layout">
<div class="wm-layout">
<div>
<div>
- <div class="drop-zone" id="dropZone" onclick="document.getElementById('fileInput').click()">
- <input type="file" id="fileInput" accept="image/*">
- <i class="fas fa-cloud-arrow-up" style="font-size: 2rem; color: var(--text-muted); margin-bottom: .5rem; display: block;"></i>
- <p style="color: var(--text-secondary); margin: 0;">Click or drag an image here</p>
- </div>
+ <div id="imagePicker"></div>
+ <p class="subtitle">PNG, JPG, WebP or another image · up to 20 MB. Your image stays in this browser.</p>
+ <p id="imageError" role="alert" hidden></p>
<div class="wm-preview" id="previewArea" style="margin-top: 1.5rem;">
<div class="wm-preview" id="previewArea" style="margin-top: 1.5rem;">
<div class="placeholder" id="placeholder">
<div class="placeholder" id="placeholder">
<i class="fas fa-image"></i>
<i class="fas fa-image"></i>
@@ -111,6 +107,7 @@
@@ -111,6 +107,7 @@
<script src="/shared/account-switcher.js"></script>
<script src="/shared/account-switcher.js"></script>
<script src="/js/logo-engine.js"></script>
<script src="/js/logo-engine.js"></script>
<script src="/js/shared.js"></script>
<script src="/js/shared.js"></script>
+ <script src="/shared/ov-upload.js"></script>
<script>
<script>
OpenVibeUI.initPage();
OpenVibeUI.initPage();
const E = LogoOpenVibeEngine;
const E = LogoOpenVibeEngine;
@@ -132,25 +129,29 @@
@@ -132,25 +129,29 @@
};
};
});
});
- // File handling
- const dropZone = document.getElementById('dropZone');
- const fileInput = document.getElementById('fileInput');
-
- ['dragenter', 'dragover'].forEach(evt => dropZone.addEventListener(evt, e => { e.preventDefault(); dropZone.classList.add('drag-over'); }));
- ['dragleave', 'drop'].forEach(evt => dropZone.addEventListener(evt, e => { e.preventDefault(); dropZone.classList.remove('drag-over'); }));
- dropZone.addEventListener('drop', e => { if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]); });
- fileInput.addEventListener('change', () => { if (fileInput.files.length) handleFile(fileInput.files[0]); });
-
- function handleFile(file) {
- if (!file.type.startsWith('image/')) return OpenVibeUI.showToast('Please upload an image file', 'error');
- const reader = new FileReader();
- reader.onload = e => {
- const img = new Image();
- img.onload = () => { loadedImg = img; document.getElementById('placeholder').style.display = 'none'; canvas.style.display = 'block'; document.getElementById('dlBtn').disabled = false; renderPreview(); };
- img.src = e.target.result;
- };
- reader.readAsDataURL(file);
- }
+ // Local image selection: the shared picker owns drops, file rows and removal.
+ const imageError = document.getElementById('imageError');
+ const picker = OVUpload.picker(document.getElementById('imagePicker'), {
+ label: 'Choose an image to watermark', accept: 'image/*', multiple: false,
+ onFile: (file, row) => {
+ if (!file.type.startsWith('image/') || !file.size || file.size > 20 * 1048576) {
+ row.dispatchEvent(new Event('remove')); row.remove();
+ imageError.textContent = file.size > 20 * 1048576 ? 'Choose an image under 20 MB.' : 'Choose an image this browser can read.';
+ imageError.hidden = false; return;
+ }
+ imageError.hidden = true;
+ const reader = new FileReader();
+ reader.onload = e => {
+ const img = new Image();
+ img.onload = () => { picker.rows.querySelectorAll('.ov-upload__row').forEach(old => { if (old !== row) { old.dispatchEvent(new Event('remove')); old.remove(); } }); loadedImg = img; document.getElementById('placeholder').style.display = 'none'; canvas.style.display = 'block'; document.getElementById('dlBtn').disabled = false; renderPreview(); };
+ img.onerror = () => { row.dispatchEvent(new Event('remove')); row.remove(); imageError.textContent = 'That image could not be opened.'; imageError.hidden = false; };
+ img.src = e.target.result;
+ };
+ reader.onerror = () => { row.dispatchEvent(new Event('remove')); row.remove(); imageError.textContent = 'That image could not be read.'; imageError.hidden = false; };
+ reader.readAsDataURL(file);
+ },
+ onRemove: () => { loadedImg = null; document.getElementById('placeholder').style.display = ''; canvas.style.display = 'none'; document.getElementById('dlBtn').disabled = true; }
+ });
function hexToRgba(hex, alpha) {
function hexToRgba(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const r = parseInt(hex.slice(1, 3), 16);
@@ -42,6 +42,15 @@ function pageOf(host, file) {
@@ -42,6 +42,15 @@ function pageOf(host, file) {
return frameAround(html, host, { file, hostnameMap: HOSTNAME_MAP });
return frameAround(html, host, { file, hostnameMap: HOSTNAME_MAP });
}
}
+// Browser-only image tools use the same accessible picker without sending files to a server.
+for (const [host, file] of [['watermark.openvibe.tools', 'watermark.html'], ['thumbnail.openvibe.tools', 'thumbnail.html']]) {
+ const html = pageOf(host, file);
+ assert.ok(html.includes('/shared/ov-upload.css') && html.includes('/shared/ov-upload.js'), `${host}: shared picker assets`);
+ assert.ok(html.includes('OVUpload.picker(') && html.includes('role="alert"'), `${host}: picker and readable errors`);
+ assert.ok(html.includes('Your image stays in this browser.'), `${host}: local processing is explained`);
+ assert.ok(!html.includes('type="file"'), `${host}: old picker is gone`);
+}
+
// ── Every text tool's page is a framed tool page, in the same order ──
// ── Every text tool's page is a framed tool page, in the same order ──
{
{
for (const [host, file] of TOOL_HOSTS) {
for (const [host, file] of TOOL_HOSTS) {