File size: 3,892 Bytes
1b2da16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589dcd3
 
 
 
 
 
 
 
1b2da16
 
9684bdc
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
const host=document.getElementById('script-input');
const draft=element.querySelector('textarea'), viewport=element.querySelector('.reading-window');
const track=element.querySelector('.reading-track'), edit=element.querySelector('[data-edit]');
const hint=element.querySelector('#script-hint'), pages=element.querySelector('[data-pages]');
let take=null, reading=false, clock=0, page=0, pageCount=1, spans=[], lineHeight=35;
const listeners=new AbortController();
const on=(node,event,fn)=>node.addEventListener(event,fn,{signal:listeners.signal});
draft.value=props.value || '';
function publish() { props.value=draft.value; host.dataset.draft=draft.value; }
function showMode() {
 draft.hidden=reading; viewport.hidden=!reading;
 edit.hidden=!take; edit.textContent=reading?'Edit text':'View take';
 pages.hidden=!reading || take?.alignment==='ready' || pageCount<2;
 hint.textContent=reading?(take.alignment==='ready'?'': 'Word timing unavailable'):'Write your text · '+draft.value.length+'/600';
 if(reading && take.text!==draft.value) hint.textContent='Previous take';
}
function layout() {
 if(!reading || !spans.length) return;
 track.style.transform='none';
 lineHeight=parseFloat(getComputedStyle(viewport).lineHeight);
 spans.forEach(s=>s.line=Math.max(0,Math.round(s.node.offsetTop/lineHeight)));
 pageCount=Math.max(1,Math.ceil((Math.max(...spans.map(s=>s.line))+1)/2));
 renderClock(); showMode();
}
function renderClock() {
 if(!reading || !take) return;
 if(take.alignment==='ready') {
  let index=-1;
  for(let i=0;i<spans.length;i++) {
   const spoken=clock>=spans[i].start;
   spans[i].node.classList.toggle('spoken',spoken);
   if(spoken) index=i;
  }
  page=index<0?0:Math.floor(spans[index].line/2);
 } else spans.forEach(s=>s.node.classList.add('spoken'));
 page=Math.max(0,Math.min(pageCount-1,page));
 track.style.transform=`translateY(${-page*2*lineHeight}px)`;
 element.querySelector('[data-previous]').disabled=page===0;
 element.querySelector('[data-next]').disabled=page===pageCount-1;
}
function receive(data) {
 if(!data?.text) return;
 take=data; clock=0; page=0; track.replaceChildren();
 const words=data.alignment==='ready' && data.words?.length?data.words:
  (data.text.match(/\S+\s*|\s+/gu)||[data.text]).map(text=>({text,start:0,end:0}));
 spans=words.map(word=>{
  const node=document.createElement('span'); node.className='word'; node.textContent=word.text;
  track.append(node); return {...word,node,line:0};
 });
 reading=true; showMode(); requestAnimationFrame(layout);
}
on(draft,'input',()=>{publish();showMode();});
on(edit,'click',()=>{
 reading=!reading;
 if(!reading) document.getElementById('speech-player')?.dispatchEvent(new CustomEvent('zonos:pause'));
 showMode(); if(reading) layout(); else draft.focus();
});
on(element.querySelector('[data-previous]'),'click',()=>{page--;renderClock();});
on(element.querySelector('[data-next]'),'click',()=>{page++;renderClock();});
on(host,'zonos:take',e=>receive(e.detail));
on(host,'zonos:clock',e=>{
 clock=e.detail.time;
 if(e.detail.playing && !reading){reading=true;showMode();layout();}
 renderClock();
});
watch('value',()=>{if(props.value!==draft.value){draft.value=props.value||'';reading=false;showMode();}host.dataset.draft=draft.value;});
// Layout writes can resize Gradio's outer wrapper. Defer them outside the
// observer delivery cycle to avoid a ResizeObserver feedback warning in WebKit.
let resizeFrame=0;
const resize=new ResizeObserver(()=>{
 if(resizeFrame)return;
 resizeFrame=requestAnimationFrame(()=>{resizeFrame=0;if(element.isConnected)layout();});
});resize.observe(viewport);
const observer=new MutationObserver(()=>{if(!element.isConnected){listeners.abort();cancelAnimationFrame(resizeFrame);resize.disconnect();observer.disconnect();}});
observer.observe(document.documentElement,{childList:true,subtree:true});
publish();showMode();

host.dataset.ready='true';