Load layouts and optional features from staged asset caches
This commit is contained in:
77
frontend/feature-bootstrap.js
Normal file
77
frontend/feature-bootstrap.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/* Thin eager entries and configuration for optional, stateful feature modules. */
|
||||
(function(root) {
|
||||
'use strict';
|
||||
const Lazy = root.Lazy, doc = root.document;
|
||||
if (!Lazy) return;
|
||||
const specs = [
|
||||
['Piano','piano','configure','piano','Piano transcription','Live & worship','Off · load model on request'],
|
||||
['MidiPedals','midi','configure','midi','MIDI pedalboard','Live & worship','Off'],
|
||||
['PartyDJ','party-dj','register','party-dj','Watch party','Live & worship','Shared suggestions · host approval'],
|
||||
['LyricsWindow','lyrics-window','configure','floating-lyrics','Floating lyrics','Live & worship','Floating lyrics window'],
|
||||
['LowerThird','lower-third','register','broadcast','Presenter & broadcast','Live & worship','Projector · OBS lower third'],
|
||||
['EqCore','eq-core','register','equalizer','Equalizer & stereo','Listening','Flat · Original stereo'],
|
||||
['VocalReducer','vocal-reducer','register','vocal-reducer','Vocal reducer','Listening','100% · centre-panned vocals'],
|
||||
['SetlistImport','setlist-import','configure','setlist-import','Setlist import','Library & storage','Planning Center · SongSelect · ChordPro'],
|
||||
['CarMode','car-mode','configure','voice-car','Voice & Car mode','Listening','Voice search · Car mode'],
|
||||
['DirectMedia','direct','configure','direct-transfer','Direct device transfer','Library & storage','Paired devices only'],
|
||||
];
|
||||
function button(id, text, anchor, name, action, className='np-btn') {
|
||||
if (!anchor || doc.getElementById(id)) return;
|
||||
const b=doc.createElement('button'); b.id=id; b.type='button'; b.className=className; b.textContent=text; b.setAttribute('aria-label',text);
|
||||
if(id==='carModeBtn')b.innerHTML='<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="m5 8 2-5h10l2 5M3 8h18v10H3zM5 18v3m14-3v3M6 12h2m8 0h2"/></svg>';
|
||||
if(id==='voiceSearchBtn')b.innerHTML='<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 10v2a7 7 0 0 0 14 0v-2M12 19v3m-4 0h8"/></svg>';
|
||||
if(id.startsWith('floating-')){b.title='Open floating lyrics';b.setAttribute('aria-label','Open floating lyrics window');}
|
||||
b.title ||= text;
|
||||
b.onclick=()=>Lazy.load(name).then(action).catch(error=>root.dispatchEvent(new CustomEvent('ytp-lazy-error',{detail:{error}})));
|
||||
anchor.after(b); Lazy.intent(b,name);
|
||||
}
|
||||
for (const [globalName, suffix, setup, id, title, cluster, summary] of specs) {
|
||||
const group='feature:'+suffix;
|
||||
const facade=Lazy.proxy(group,['open','close','obtain','publish','attach','handle','offer','paint','fade'],globalName);
|
||||
let configured=false;
|
||||
facade[setup]=options=>{
|
||||
if (configured) return; configured=true;
|
||||
Lazy.onLoad(group,()=>root[globalName][setup](options));
|
||||
root.SettingsSections.register({id,title,cluster,lazyGroup:group,summary:()=>{
|
||||
const s=options?.settings?.() || {};
|
||||
if(globalName==='Piano')return s.pianoEnabled?'On · browser or optional worker':'Off · load model on request';
|
||||
if(globalName==='MidiPedals')return s.midiEnabled?'On · pedal mappings':'Off';
|
||||
if(globalName==='EqCore')return `${root.EqParameters.presets[s.eq?.preset]?.name || 'Custom'} · ${root.EqParameters.width(s.stereoWidth)?'Widened':'Original stereo'}`;
|
||||
if(globalName==='VocalReducer')return `${root.EqParameters.amount(s.vocalLevel ?? 100)}% · centre-panned vocals`;
|
||||
if(globalName==='DirectMedia')return facade.enabled()?'On · paired devices only':'Off or unsupported';
|
||||
if(globalName==='CarMode')return root.SpeechRecognition||root.webkitSpeechRecognition?'Voice search available · Car mode':'Car mode · voice unavailable';
|
||||
if(globalName==='LyricsWindow')return root.documentPictureInPicture?.requestWindow?'Interactive window available':root.HTMLCanvasElement?.prototype.captureStream&&doc.pictureInPictureEnabled?'Lyric video PiP available':'Not supported on this browser';
|
||||
return summary;
|
||||
},
|
||||
render(container,context) {
|
||||
let disposed=false, cleanup;
|
||||
container.textContent='Loading…';
|
||||
Lazy.load(group).then(()=>{
|
||||
if (disposed || !container.isConnected) return;
|
||||
container.replaceChildren(); cleanup=root.SettingsSections.get(id).render(container,context);
|
||||
}).catch(error=>{if(!disposed)container.textContent=error.message;});
|
||||
return ()=>{disposed=true; if(typeof cleanup==='function')cleanup();};
|
||||
},
|
||||
});
|
||||
if (globalName==='CarMode') {
|
||||
button('carModeBtn','Car mode',doc.getElementById('searchForm'),group,()=>root.CarMode.open(),'icon-btn');
|
||||
if(root.SpeechRecognition||root.webkitSpeechRecognition)button('voiceSearchBtn','Voice search',options.input,group,()=>doc.getElementById('voiceSearchBtn').click(),'icon-btn voice-search');
|
||||
}
|
||||
if (globalName==='LyricsWindow' && (root.documentPictureInPicture?.requestWindow || (root.HTMLCanvasElement?.prototype.captureStream && doc.pictureInPictureEnabled))) {
|
||||
for(const anchor of ['stageLyricsAuto','serviceAutoscrollBtn','pipBtn'])button('floating-'+anchor,'⧉ Lyrics',doc.getElementById(anchor),group,()=>doc.getElementById('floating-'+anchor).click());
|
||||
}
|
||||
};
|
||||
if(globalName==='LowerThird') {
|
||||
facade.active=()=>new URLSearchParams(root.location.search).get('overlay')==='lowerthird';
|
||||
facade.url=(origin,code)=>{const u=new URL('/',origin);u.searchParams.set('overlay','lowerthird');u.searchParams.set('code',String(code));return u.href;};
|
||||
}
|
||||
if(globalName==='DirectMedia') {
|
||||
let settings=()=>({}); const configure=facade.configure;
|
||||
facade.configure=options=>{settings=options.settings;configure(options);};
|
||||
facade.enabled=()=>settings().directTransfer!==false && !!root.RTCPeerConnection;
|
||||
facade.hasSource=()=>false;
|
||||
}
|
||||
if(globalName==='LyricsWindow')facade.element=()=>null;
|
||||
root[globalName]=facade;
|
||||
}
|
||||
})(typeof window !== 'undefined' ? window : globalThis);
|
||||
Reference in New Issue
Block a user