FFstudio / app /static /main.js
MarneMorgan's picture
Upload 8 files
8d0498c verified
const $ = (id)=>document.getElementById(id);
async function api(path, opts){
const res = await fetch(path, opts);
if(!res.ok){
const t = await res.text();
throw new Error(`${res.status} ${res.statusText}: ${t}`);
}
return res;
}
async function refresh(){
const res = await api('/api/templates');
const items = await res.json();
const list = $('list');
list.innerHTML = '';
for(const t of items){
const li = document.createElement('li');
const name = document.createElement('span');
name.textContent = `${t.id}${t.name ?? ''}`;
const loadBtn = document.createElement('button');
loadBtn.textContent = 'Load';
loadBtn.onclick = ()=>loadTemplate(t.id);
li.appendChild(name);
li.appendChild(loadBtn);
list.appendChild(li);
}
}
async function loadTemplate(id){
const res = await api(`/api/templates/${encodeURIComponent(id)}`);
const tpl = await res.json();
$('tplId').value = tpl.id;
$('json').value = JSON.stringify(tpl, null, 2);
}
function newTemplate(){
const id = `tpl_${Math.random().toString(16).slice(2,8)}`;
$('tplId').value = id;
$('json').value = JSON.stringify({
id,
name: "My Template",
width: 1280,
height: 720,
fps: 30,
duration_sec: 5,
bg_color: "#111827",
text: { value: "{name}", x: 80, y: 600, fontsize: 48 }
}, null, 2);
}
async function saveTemplate(){
const id = $('tplId').value.trim();
if(!id) return alert('Template id required');
let body;
try{ body = JSON.parse($('json').value); }
catch(e){ return alert('Invalid JSON'); }
body.id = id;
await api(`/api/templates/${encodeURIComponent(id)}`, {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(body)
});
await refresh();
}
async function render(){
const tplId = $('tplId').value.trim();
if(!tplId) return alert('Load or create a template first');
const name = $('name').value || 'Friend';
$('renderStatus').textContent = 'Queued…';
const res = await api('/api/render', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({ template_id: tplId, variables: { name } })
});
const job = await res.json();
poll(job.job_id);
}
async function poll(jobId){
while(true){
const res = await api(`/api/jobs/${encodeURIComponent(jobId)}`);
const job = await res.json();
$('renderStatus').textContent = `${job.status}${job.detail?` — ${job.detail}`:''}`;
if(job.status === 'done'){
const url = `/api/jobs/${encodeURIComponent(jobId)}/download`;
$('player').src = url;
break;
}
if(job.status === 'error') break;
await new Promise(r=>setTimeout(r, 1200));
}
}
$('refresh').onclick = refresh;
$('new').onclick = ()=>{ newTemplate(); };
$('save').onclick = saveTemplate;
$('render').onclick = render;
refresh().catch(e=>$('renderStatus').textContent = e.message);