openvibers/openvibe.tips Commits

Use shared upload kit for paid sounds (#263)

OP OpenVibers committed 2 days ago · 0d1834c991 · 5 files, +95 −2

Changes

ddiff --git a/public/js/sound-upload.js b/public/js/sound-upload.js
nnew file mode 100644
iindex 0000000..a16125e
---- /dev/null
++++ b/public/js/sound-upload.js
@@@ -0,0 +1,85 @@
++/* Shared picker for the dashboard's existing multipart paid-sound form. */
++(function () {
++ 'use strict';
++ var form = document.querySelector('form.sound-upload');
++ var input = form && form.querySelector('[data-sound-file]');
++ var root = form && form.querySelector('[data-sound-picker]');
++ var error = form && form.querySelector('[data-sound-error]');
++ if (!form || !input || !root || !error || !window.OVUpload || !window.DataTransfer) return;
++
++ var file = null;
++ var row = null;
++ var meter = null;
++ var sending = false;
++ function fail(message) { error.textContent = message; error.hidden = false; }
++ function clear() {
++ file = null; row = null; input.value = '';
++ if (meter) meter.destroy();
++ meter = null;
++ }
++ var pick = OVUpload.picker(root, {
++ label: 'Choose an MP3, OGG or WAV sound',
++ accept: input.accept, multiple: false, onRemove: clear,
++ onFile: function (next, nextRow) {
++ if (row) { row.dispatchEvent(new Event('remove')); row.remove(); }
++ error.hidden = true;
++ if (!['audio/mpeg', 'audio/ogg', 'audio/wav', 'audio/x-wav'].includes(next.type) &&
++ !/\.(mp3|ogg|wav)$/i.test(next.name)) {
++ nextRow.dispatchEvent(new Event('remove')); nextRow.remove();
++ return fail('Choose an MP3, OGG or WAV sound.');
++ }
++ if (next.size > Number(root.dataset.maxBytes)) {
++ nextRow.dispatchEvent(new Event('remove')); nextRow.remove();
++ return fail('A sound can be at most 512 KB.');
++ }
++ var transfer = new DataTransfer();
++ transfer.items.add(next);
++ input.files = transfer.files;
++ file = next; row = nextRow;
++ var meterRoot = document.createElement('div');
++ row.append(meterRoot);
++ meter = OVUpload.progress(meterRoot, { compact: true, onRetry: send });
++ meter.update({ state: 'waiting', total: file.size });
++ }
++ });
++ input.required = false;
++ input.hidden = true;
++
++ function send() {
++ if (sending || !file) return;
++ sending = true;
++ error.hidden = true;
++ var button = form.querySelector('[type="submit"]');
++ var remove = row.querySelector('.ov-upload__remove');
++ button.disabled = true; remove.disabled = true;
++ var size = file.size;
++ var xhr = new XMLHttpRequest();
++ xhr.open('POST', form.action);
++ xhr.upload.onprogress = function (event) {
++ if (event.lengthComputable) meter.update({ state: 'sending', done: Math.min(event.loaded, size), total: event.total });
++ };
++ xhr.upload.onload = function () { meter.update({ state: 'processing', done: size, total: size }); };
++ xhr.onload = function () {
++ if (xhr.status >= 200 && xhr.status < 400 && xhr.responseURL && xhr.responseURL !== form.action) {
++ meter.update({ state: 'done', done: size, total: size });
++ window.location.assign(xhr.responseURL);
++ return;
++ }
++ sending = false; button.disabled = false; remove.disabled = false;
++ var page = new DOMParser().parseFromString(xhr.responseText || '', 'text/html');
++ var reason = page.querySelector('.notice.error[role="alert"]');
++ meter.update({ state: 'failed', message: reason ? reason.textContent : 'The sound could not be added. Try again.' });
++ };
++ xhr.onerror = function () {
++ sending = false; button.disabled = false; remove.disabled = false;
++ meter.update({ state: 'failed', message: 'The connection stopped. Try again.' });
++ };
++ meter.update({ state: 'sending', done: 0, total: size });
++ xhr.send(new FormData(form));
++ }
++ form.addEventListener('submit', function (event) {
++ event.preventDefault();
++ if (!file) { fail('Choose a sound first.'); pick.input.focus(); return; }
++ send();
++ });
++})();
ddiff --git a/server/web/layout.js b/server/web/layout.js
iindex 160a1d3..a196950 100644
---- a/server/web/layout.js
++++ b/server/web/layout.js
@@@ -91,6 +91,9 @@ function createLayout({ config, release }) {
`<link rel="stylesheet" href="${ovServe.url('kit.css')}">`,
// openvibe-shared stylesheets a page asks for by name (the home's showcase.css).
...(o.styles || []).map((name) => `<link rel="stylesheet" href="${ovServe.url(name)}">`),
++ o.uploadKit ? `<link rel="stylesheet" href="${ovServe.url('ov-upload.css')}">` : '',
++ o.uploadKit ? `<script src="${ovServe.url('ov-upload.js')}" defer></script>` : '',
++ o.uploadKit ? `<script src="${asset('js/sound-upload.js')}" defer></script>` : '',
`<meta name="ov-boost" content="tips@${esc(RELEASE)}">`,
`<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>`,
`<script src="${asset('js/tips.js')}" defer></script>`,
ddiff --git a/server/web/pages.js b/server/web/pages.js
iindex 56186d1..362f611 100644
---- a/server/web/pages.js
++++ b/server/web/pages.js
@@@ -401,7 +401,8 @@ function soundsCard({ profile: p, sounds, csrf, idem }) {
<form method="post" action="/dashboard/sounds/${esc(x.id)}/delete">${hidden({ csrf, idem: `${idem}-sd-${x.id}` })}<button class="btn btn-small" type="submit">Delete</button></form></li>`).join('')}</ul>` : '<p class="muted">No sounds yet.</p>'}
${full ? '<p class="muted">You have 12 sounds: delete one to add another.</p>' : `<form method="post" action="/dashboard/sounds" enctype="multipart/form-data" class="sound-upload">${hidden({ csrf, idem: `${idem}-su` })}
<label>Name <input type="text" name="name" maxlength="40" placeholder="Airhorn"></label>
-- <label>File <input type="file" name="file" accept="audio/mpeg,audio/ogg,audio/wav,.mp3,.ogg,.wav" required></label>
++ <label>File <input type="file" name="file" accept="audio/mpeg,audio/ogg,audio/wav,.mp3,.ogg,.wav" required data-sound-file></label>
++ <div data-sound-picker data-max-bytes="524288"></div><p role="alert" data-sound-error hidden></p>
<button class="btn" type="submit">Add sound</button>
</form>`}
</section>`;
ddiff --git a/server/web/routes.js b/server/web/routes.js
iindex ff59255..180df1c 100644
---- a/server/web/routes.js
++++ b/server/web/routes.js
@@@ -256,7 +256,7 @@ function createWebRoutes({ domain, config, layout, userAuth, liveNow = { list: a
domain.vip.tiers(s),
]);
html(res, pageFor(req, {
-- title: 'Dashboard', active: 'dashboard', robots: 'noindex,nofollow', canonicalPath: '/dashboard',
++ title: 'Dashboard', active: 'dashboard', robots: 'noindex,nofollow', canonicalPath: '/dashboard', uploadKit: true,
body: pages.dashboard({
profile: p, goals: goalRows, tokens: tokens.map(overlays.presentToken),
configs: configs.map(overlays.presentConfig), totals, recent: recentRows,
ddiff --git a/test/sounds.test.js b/test/sounds.test.js
iindex 6a2f1e0..498b706 100644
---- a/test/sounds.test.js
++++ b/test/sounds.test.js
@@@ -51,6 +51,10 @@ const WAV = Buffer.concat([Buffer.from('RIFF'), Buffer.from([0x24, 0, 0, 0]), Bu
const page = await get('/dashboard', alex);
assert.match(page.text, /id="sounds"/);
assert.match(page.text, /enctype="multipart\/form-data"/);
++ assert.match(page.text, /ov-upload\.css/);
++ assert.match(page.text, /ov-upload\.js/);
++ assert.match(page.text, /data-sound-picker data-max-bytes="524288"/);
++ assert.match(page.text, /data-sound-error hidden/);
const ok = await upload(alex, { name: 'Airhorn', file: MP3 });
assert.strictEqual(ok.status, 303, ok.text.slice(0, 300));
assert.match(ok.location, /done=Sound%20added/);