Admin: enable download, surface Gemini errors in toast, AJAX file delete; style: make toast clickable
This commit is contained in:
parent
3de0d498ce
commit
420ce99046
3 changed files with 99 additions and 24 deletions
103
new.php
103
new.php
|
|
@ -552,9 +552,41 @@ $runningJobs = active_worker_jobs($config);
|
|||
<?php if($tab==='edit' && $editDir && $editRunning): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><div class="err">This input directory is <?=h($editStatus)?>. Editing is disabled until the job completes.</div></section><?php endif; ?>
|
||||
<?php if($tab==='edit' && $editDir && !$editRunning): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><form method="post" enctype="multipart/form-data" id="edit-form"><input type="hidden" name="action" value="update"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><label>Title<input name="title" value="<?=h($editData['title'])?>"></label><label>Date<input name="date" value="<?=h($editData['date'])?>"></label><label>Place<input name="place" value="<?=h($editData['place'])?>"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-edit"><?=h($editData['description'])?></textarea><small id="description-counter-edit" class="counter"></small></label><label>Add images / videos<input type="file" name="media[]" multiple accept="image/*,video/*"></label><label>Add audio<input type="file" name="audio[]" multiple accept="audio/*"></label><h3>Files and captions</h3><div class="file-list"><?php foreach($editFiles as $f): $ext=strtolower(pathinfo($f, PATHINFO_EXTENSION)); $fileUrl='in-dir/'.rawurlencode(basename($editDir)).'/'.rawurlencode($f); ?><div class="caption-row"><div class="preview"><?php if(in_array($ext,['jpg','jpeg','png','webp','gif'])): ?><img src="<?=h($fileUrl)?>" alt=""><?php elseif(in_array($ext,['mp4','mov','m4v','webm'])): ?><video src="<?=h($fileUrl)?>" controls preload="metadata"></video><a class="download-link" href="<?=h($fileUrl)?>" download>Download</a><?php else: ?><span><?=h(strtoupper($ext ?: 'FILE'))?></span><?php endif; ?></div><div class="caption-fields"><strong><?=h($f)?></strong><?php if(in_array($ext,['jpg','jpeg','png','webp','gif','mp4','mov','m4v','webm'])): ?><input type="hidden" name="caption_files[]" value="<?=h($f)?>"><textarea name="captions[]" placeholder="Optional caption for this file"><?=h($editData['captions'][$f] ?? '')?></textarea><?php if(in_array($ext,['mp4','mov','m4v','webm'])): ?><label class="checkbox-label"><input type="checkbox" name="use_audio_files[]" value="<?=h($f)?>" <?=!empty($editData['video_audio'][$f])?'checked':''?>> <span>Use video file audio</span></label><?php endif; ?><?php else: ?><small>No caption for audio files</small><?php endif; ?></div><button type="button" onclick="deleteFile(<?=h(json_encode($f))?>)">Remove</button></div><?php endforeach; ?></div><button type="submit">Save changes</button></form><form method="post" id="delete-file-form" style="display:none"><input type="hidden" name="action" value="delete_file"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="file" id="delete-file-name" value=""></form><form method="post" onsubmit="return confirm(\'Delete this input directory\?\')" id="delete-dir-form""><input type="hidden" name="action" value="delete_dir"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><label class="checkbox-label"><input type="checkbox" name="delete_output" value="1"> <span>Also delete generated video, if any</span></label><button class="danger" type="submit">Delete input dir</button></form></section><?php endif; ?>
|
||||
<?php if($tab==='new'): ?><section><h2>Add new movie input</h2><form method="post" enctype="multipart/form-data" id="new-form"><input type="hidden" name="action" value="create"><label>Title*<input name="title" required></label><label>Date<input name="date" placeholder="2026-05-25"></label><label>Place<input name="place"></label><label>Description<textarea name="description" maxlength="5000" data-counter="description-counter-new"></textarea><small id="description-counter-new" class="counter"></small></label><label>Images / videos*<input type="file" name="media[]" multiple required accept="image/*,video/*"></label><label>Audio (optional)<input type="file" name="audio[]" multiple accept="audio/*"></label><button type="submit">Create input-dir</button></form></section><?php endif; ?>
|
||||
<script>function deleteFile(name){if(confirm('Remove this file?')){document.getElementById('delete-file-name').value=name;document.getElementById('delete-file-form').submit();}}</script>
|
||||
<script>
|
||||
function showToast(message, ok){
|
||||
function deleteFile(name){
|
||||
if(!confirm('Remove this file?')) return;
|
||||
var form = document.getElementById('delete-file-form');
|
||||
if(!form){ showToast('Internal error: delete form not found', false); return; }
|
||||
var dirInput = form.querySelector('input[name=dir]');
|
||||
var dir = dirInput ? dirInput.value : '';
|
||||
|
||||
// find the caption-row for this file name
|
||||
var rows = document.querySelectorAll('.caption-row');
|
||||
var target = null;
|
||||
for(var i=0;i<rows.length;i++){
|
||||
var strong = rows[i].querySelector('.caption-fields strong') || rows[i].querySelector('strong');
|
||||
if(!strong) continue;
|
||||
if(strong.textContent.trim() === name){ target = rows[i]; break; }
|
||||
}
|
||||
|
||||
var body = 'action=delete_file&dir=' + encodeURIComponent(dir) + '&file=' + encodeURIComponent(name) + '&ajax=1';
|
||||
fetch('new.php', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: body
|
||||
}).then(function(r){ return r.json().catch(function(){ return { ok: false, message: 'Invalid server response' }; }); })
|
||||
.then(function(json){
|
||||
if(!json || !json.ok) throw new Error(json && (json.error || json.message) ? (json.error || json.message) : 'Delete failed');
|
||||
if(target) target.remove();
|
||||
showToast(json.message || ('Removed ' + name), true);
|
||||
}).catch(function(err){
|
||||
showToast(err.message || String(err), false);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function showToast(message, ok, details){
|
||||
let wrap=document.getElementById('toast-wrap');
|
||||
if(!wrap){
|
||||
wrap=document.createElement('div');
|
||||
|
|
@ -563,16 +595,62 @@ function showToast(message, ok){
|
|||
document.body.appendChild(wrap);
|
||||
}
|
||||
const toast=document.createElement('div');
|
||||
toast.className='toast '+(ok?'ok-toast':'err-toast');
|
||||
toast.textContent=message;
|
||||
toast.className='toast '+(ok? 'ok-toast' : 'err-toast');
|
||||
toast.style.pointerEvents = 'auto';
|
||||
const text=document.createElement('div');
|
||||
text.style.whiteSpace='normal';
|
||||
text.textContent=message;
|
||||
toast.appendChild(text);
|
||||
|
||||
if(details){
|
||||
const actions=document.createElement('div');
|
||||
actions.style.marginTop='8px';
|
||||
actions.style.textAlign='right';
|
||||
const dbtn=document.createElement('button');
|
||||
dbtn.className='button';
|
||||
dbtn.textContent='Details';
|
||||
dbtn.style.padding='0.35rem 0.6rem';
|
||||
dbtn.style.fontSize='0.85rem';
|
||||
dbtn.addEventListener('click', function(e){ e.stopPropagation(); e.preventDefault(); showLogModal('Generator log', details); });
|
||||
actions.appendChild(dbtn);
|
||||
toast.appendChild(actions);
|
||||
}
|
||||
|
||||
wrap.appendChild(toast);
|
||||
requestAnimationFrame(function(){toast.classList.add('show');});
|
||||
const timeout = details ? 15000 : 4000;
|
||||
setTimeout(function(){
|
||||
toast.classList.remove('show');
|
||||
setTimeout(function(){toast.remove();},250);
|
||||
},3200);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
function showLogModal(title, text){
|
||||
var existing=document.getElementById('mvlog-log-modal');
|
||||
if(existing) existing.remove();
|
||||
var overlay=document.createElement('div');
|
||||
overlay.id='mvlog-log-modal';
|
||||
overlay.style.cssText='position:fixed;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:10001;';
|
||||
var box=document.createElement('div');
|
||||
box.style.cssText='background:white;color:#111;padding:16px;max-width:90%;max-height:80vh;overflow:auto;border-radius:6px;font-family:inherit;';
|
||||
var h=document.createElement('h3');
|
||||
h.textContent=title||'Details';
|
||||
var pre=document.createElement('pre');
|
||||
pre.style.cssText='white-space:pre-wrap;font-family:monospace;margin-top:10px;border:1px solid #eee;padding:12px;background:#f8f8f8;border-radius:4px; max-height:60vh; overflow:auto;';
|
||||
pre.textContent=text||'';
|
||||
var btnBar=document.createElement('div');
|
||||
btnBar.style.cssText='margin-top:12px;text-align:right;';
|
||||
var copyBtn=document.createElement('button');
|
||||
copyBtn.className='button'; copyBtn.textContent='Copy';
|
||||
var closeBtn=document.createElement('button'); closeBtn.className='button'; closeBtn.textContent='Close'; closeBtn.style.marginLeft='8px';
|
||||
btnBar.appendChild(copyBtn); btnBar.appendChild(closeBtn);
|
||||
box.appendChild(h); box.appendChild(pre); box.appendChild(btnBar); overlay.appendChild(box); document.body.appendChild(overlay);
|
||||
|
||||
closeBtn.addEventListener('click', function(){ overlay.remove(); });
|
||||
copyBtn.addEventListener('click', function(){ try{ navigator.clipboard.writeText(text).then(function(){ showToast('Copied to clipboard', true); }, function(){ showToast('Copy failed', false); }); }catch(e){ showToast('Copy failed', false); } });
|
||||
}
|
||||
|
||||
|
||||
const editForm=document.getElementById('edit-form');
|
||||
if(editForm) handleAjaxFormSubmit(editForm, 'edit');
|
||||
|
||||
|
|
@ -846,9 +924,20 @@ function applySwitchPayload(payload){
|
|||
var desc = data.description || (data.raw_json && JSON.stringify(data.raw_json)) || '';
|
||||
showModal(job, desc, data.raw_json || null);
|
||||
} else {
|
||||
showToast('Error: ' + (data && data.message ? data.message : 'No response'), false);
|
||||
var msg = (data && data.message) ? data.message : 'No response';
|
||||
if (data && data.log) {
|
||||
try {
|
||||
var log = String(data.log);
|
||||
var lines = log.split('\n').map(function(l){ return l.trim(); }).filter(function(l){ return l.length>0; });
|
||||
var keywords = ['quota','rate limit','rate-limit','429','503','overloaded','high demand','busy','unavailable','error','failed','retry'];
|
||||
var found = lines.find(function(l){ var ll=l.toLowerCase(); return keywords.some(function(k){ return ll.indexOf(k) !== -1; }); });
|
||||
if (found) msg += ' — ' + found;
|
||||
else if (lines.length) msg += ' — ' + lines[0].slice(0,200);
|
||||
} catch(e){}
|
||||
}
|
||||
showToast('Error: ' + msg, false, (data && data.log) ? (typeof data.log === 'string' ? data.log : JSON.stringify(data.log)) : null);
|
||||
}
|
||||
}).catch(function(err){
|
||||
}).catch(function(err){
|
||||
showToast('Request failed: ' + err, false);
|
||||
}).finally(function(){
|
||||
el.classList.remove('disabled');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue