Author SHA1 Message Date
teahubxyzandClaude Opus 5.5 0534c739d5 plugins/webamp: open in view on long pages
Webamp centered on the whole document, so on tall pages it opened below
the fold and the button looked dead. Center it in a fixed viewport-sized
host, pin #webamp above Gitea's layers, and make the button re-center an
open player. Playlist fetch dropped until gandora publishes one. v=015.
Confirmed in the user's browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLEj1MiGohaV4qJE4NE1Xq
2026-10-22 19:01:18 +00:00
teahubxyz fd08195cdc plugins/webamp: robust bundle load and visible errors (#24) 2026-10-22 18:47:35 +00:00
teahubxyz 917f69fbce plugins/webamp: self-host the Webamp bundle (#23) 2026-10-22 18:34:38 +00:00
3 changed files with 58 additions and 12 deletions

No files matched your search

+4 -1
View File
@@ -1,12 +1,15 @@
{ {
"name": "webamp", "name": "webamp",
"title": "Webamp", "title": "Webamp",
"version": "0.1.0", "version": "0.1.1",
"description": "A Winamp-style player on every Teahub page, fed by gandora.", "description": "A Winamp-style player on every Teahub page, fed by gandora.",
"slots": { "slots": {
"footer": "templates/custom/footer.tmpl" "footer": "templates/custom/footer.tmpl"
}, },
"assets": ["public/assets/plugins/webamp/webamp-mod.js"], "assets": ["public/assets/plugins/webamp/webamp-mod.js"],
"vendor": {
"public/assets/plugins/webamp/webamp.bundle.min.js": "https://registry.npmjs.org/webamp/-/webamp-1.5.0.tgz#package/built/webamp.bundle.min.js"
},
"config": { "config": {
"playlist_url": "https://gandora.yolopreneur.lol/playlist.json" "playlist_url": "https://gandora.yolopreneur.lol/playlist.json"
} }
@@ -36,26 +36,69 @@ async function loadTracks() {
let webamp = null; let webamp = null;
// Load the UMD bundle as a classic script. Hide any AMD/CommonJS globals on the page
// while it runs, so the bundle always attaches itself to window.Webamp.
function loadWebamp() {
if (window.Webamp) return Promise.resolve(window.Webamp);
return new Promise((ok, fail) => {
const saved = { define: window.define, module: window.module, exports: window.exports };
window.define = window.module = window.exports = undefined;
const restore = () => Object.assign(window, saved);
const s = document.createElement('script');
s.src = new URL('webamp.bundle.min.js', import.meta.url).href;
s.onload = () => {
restore();
window.Webamp ? ok(window.Webamp) : fail(new Error('Webamp global missing after load'));
};
s.onerror = () => { restore(); fail(new Error('could not load ' + s.src)); };
document.head.appendChild(s);
});
}
async function open() { async function open() {
if (webamp) return; if (webamp) {
const { default: Webamp } = await import('https://unpkg.com/webamp@1.5.0/built/webamp.bundle.min.mjs'); // Already open (maybe off-screen or hidden): close and reopen so it lands centered in view.
if (!Webamp.browserIsSupported()) return;
webamp = new Webamp({ initialTracks: await loadTracks() });
webamp.onClose(() => {
webamp.dispose(); webamp.dispose();
webamp = null; webamp = null;
try { localStorage.removeItem(KEY); } catch {} document.getElementById('teahub-webamp')?.remove();
}); }
await webamp.renderWhenReady(document.body.appendChild(document.createElement('div'))); btn.textContent = '… webamp';
try { localStorage.setItem(KEY, '1'); } catch {} try {
const Webamp = await loadWebamp();
if (!Webamp.browserIsSupported()) throw new Error('browser not supported');
webamp = new Webamp({ initialTracks: await loadTracks() });
webamp.onClose(() => {
webamp.dispose();
webamp = null;
try { localStorage.removeItem(KEY); } catch {}
});
const host = document.createElement('div');
host.id = 'teahub-webamp';
// Webamp centers itself in this node, so make it exactly the visible viewport.
host.style.cssText = 'position:fixed;left:0;top:0;width:100vw;height:100vh;pointer-events:none';
document.body.appendChild(host);
await webamp.renderWhenReady(host);
try { localStorage.setItem(KEY, '1'); } catch {}
btn.textContent = '▶ webamp';
} catch (err) {
console.error('[teahub webamp]', err);
webamp = null;
btn.textContent = '✕ webamp';
btn.title = String(err && err.message || err);
}
} }
// Keep Webamp pinned to the viewport and above Gitea's own layers.
const style = document.createElement('style');
style.textContent = '#webamp{position:fixed!important;z-index:99990!important}';
document.head.appendChild(style);
// Small launcher in the corner: the "sound system" button. // Small launcher in the corner: the "sound system" button.
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.textContent = '▶ webamp'; btn.textContent = '▶ webamp';
btn.title = 'Open Webamp'; btn.title = 'Open Webamp';
btn.style.cssText = [ btn.style.cssText = [
'position:fixed', 'right:12px', 'bottom:12px', 'z-index:9999', 'position:fixed', 'right:12px', 'bottom:12px', 'z-index:99999',
'font:12px/1 monospace', 'padding:6px 10px', 'border-radius:4px', 'font:12px/1 monospace', 'padding:6px 10px', 'border-radius:4px',
'border:1px solid #1d3', 'background:#000', 'color:#1d3', 'cursor:pointer', 'border:1px solid #1d3', 'background:#000', 'color:#1d3', 'cursor:pointer',
].join(';'); ].join(';');
+1 -1
View File
@@ -1,2 +1,2 @@
{{/* teahub plugin: webamp */}} {{/* teahub plugin: webamp */}}
<script type="module" nonce="{{ctx.CspScriptNonce}}" src="{{AssetUrlPrefix}}/plugins/webamp/webamp-mod.js?v=010" data-playlist="https://gandora.yolopreneur.lol/playlist.json"></script> <script type="module" nonce="{{ctx.CspScriptNonce}}" src="{{AssetUrlPrefix}}/plugins/webamp/webamp-mod.js?v=015"></script>