Replaces the up/down arrow buttons on each track card with a grip handle. Dragging a card shows a red insertion indicator on the target card's top or bottom edge and drops it into the closest slot on release, including gaps between cards or past the last card. The card is only marked draggable while the grip handle is being held, so text selection and button clicks inside the card body behave normally. Existing promote_track_id logic (secondary at position 1 becomes primary on save) is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1161 lines
58 KiB
PHP
1161 lines
58 KiB
PHP
@php use App\Data\Languages; @endphp
|
|
<div class="modal fade" id="editVideoModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable um-dialog">
|
|
|
|
<form id="edit-form" enctype="multipart/form-data" class="modal-content um-content">
|
|
@csrf
|
|
@method('PUT')
|
|
|
|
{{-- Core hidden inputs --}}
|
|
<input type="hidden" name="type" id="edit-type-val" value="generic">
|
|
<input type="hidden" name="visibility" id="edit-vis-val" value="public">
|
|
<input type="hidden" name="download_access" id="edit-dl-val" value="disabled">
|
|
{{-- Optional replacement video/audio file --}}
|
|
<input type="file" name="video" id="edit-video-file"
|
|
accept="video/*,audio/mpeg,audio/mp4,audio/aac,audio/flac,audio/wav,.mp3,.m4a,.aac,.flac,.wav"
|
|
style="display:none;">
|
|
|
|
{{-- ── Discard confirm overlay ── --}}
|
|
<div id="edit-close-confirm" style="display:none;position:absolute;inset:0;z-index:50;background:rgba(0,0,0,.88);border-radius:20px;align-items:center;justify-content:center;flex-direction:column;gap:16px;padding:40px;text-align:center;">
|
|
<div style="width:60px;height:60px;background:rgba(245,158,11,.12);border:1px solid rgba(245,158,11,.3);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:26px;color:#f59e0b;">
|
|
<i class="bi bi-exclamation-triangle-fill"></i>
|
|
</div>
|
|
<div>
|
|
<p style="color:#f1f1f1;font-size:17px;font-weight:700;margin:0 0 6px;">Discard changes?</p>
|
|
<p style="color:#777;font-size:13px;margin:0;line-height:1.5;">Any unsaved changes will be lost.</p>
|
|
</div>
|
|
<div style="display:flex;gap:10px;">
|
|
<button type="button" class="action-btn" onclick="_editHideConfirm()"><span>Keep editing</span></button>
|
|
<button type="button" class="action-btn action-btn-danger" onclick="_editDoClose()"><i class="bi bi-trash"></i><span>Discard</span></button>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ── Header ── --}}
|
|
<div class="um-header">
|
|
<div class="um-header-left">
|
|
<div class="um-header-icon"><i class="bi bi-pencil-fill"></i></div>
|
|
<div>
|
|
<div class="um-header-title">Edit</div>
|
|
<div class="um-header-sub" id="edit-modal-label">Loading…</div>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white" onclick="closeEditVideoModal()" aria-label="Close"></button>
|
|
</div>
|
|
|
|
{{-- ── Body ── --}}
|
|
<div class="um-body">
|
|
|
|
{{-- ── Global Settings ── --}}
|
|
<div class="um-gs-row">
|
|
<div class="um-gs-wrap">
|
|
<span class="um-gs-lbl">Content Type</span>
|
|
<button type="button" class="um-gs-btn" id="edit-gs-type-btn">
|
|
<i class="bi bi-film um-gs-ico" id="edit-gs-type-ico"></i>
|
|
<span class="um-gs-txt" id="edit-gs-type-txt">Generic</span>
|
|
<i class="bi bi-chevron-down um-gs-arr"></i>
|
|
</button>
|
|
<ul class="um-gs-menu" id="edit-gs-type-menu" hidden>
|
|
<li class="um-gs-opt active" data-egs="type" data-value="generic" data-icon="bi-film" data-label="Generic"><i class="bi bi-film"></i><span>Generic</span></li>
|
|
<li class="um-gs-opt" data-egs="type" data-value="music" data-icon="bi-music-note-beamed" data-label="Music"> <i class="bi bi-music-note-beamed"></i><span>Music</span></li>
|
|
<li class="um-gs-opt" data-egs="type" data-value="match" data-icon="bi-trophy" data-label="Match"> <i class="bi bi-trophy"></i><span>Match</span></li>
|
|
</ul>
|
|
</div>
|
|
<div class="um-gs-wrap">
|
|
<span class="um-gs-lbl">Visibility</span>
|
|
<button type="button" class="um-gs-btn" id="edit-gs-vis-btn">
|
|
<i class="bi bi-globe um-gs-ico" id="edit-gs-vis-ico"></i>
|
|
<span class="um-gs-txt" id="edit-gs-vis-txt">Public</span>
|
|
<i class="bi bi-chevron-down um-gs-arr"></i>
|
|
</button>
|
|
<ul class="um-gs-menu" id="edit-gs-vis-menu" hidden>
|
|
<li class="um-gs-opt active" data-egs="vis" data-value="public" data-icon="bi-globe" data-label="Public"> <i class="bi bi-globe"></i><span>Public</span></li>
|
|
<li class="um-gs-opt" data-egs="vis" data-value="unlisted" data-icon="bi-link-45deg" data-label="Unlisted"> <i class="bi bi-link-45deg"></i><span>Unlisted</span></li>
|
|
<li class="um-gs-opt" data-egs="vis" data-value="private" data-icon="bi-lock" data-label="Private"> <i class="bi bi-lock"></i><span>Private</span></li>
|
|
</ul>
|
|
</div>
|
|
<div class="um-gs-wrap">
|
|
<span class="um-gs-lbl">Downloads</span>
|
|
<button type="button" class="um-gs-btn" id="edit-gs-dl-btn">
|
|
<i class="bi bi-slash-circle um-gs-ico" id="edit-gs-dl-ico"></i>
|
|
<span class="um-gs-txt" id="edit-gs-dl-txt">Off</span>
|
|
<i class="bi bi-chevron-down um-gs-arr"></i>
|
|
</button>
|
|
<ul class="um-gs-menu" id="edit-gs-dl-menu" hidden>
|
|
<li class="um-gs-opt active" data-egs="dl" data-value="disabled" data-icon="bi-slash-circle" data-label="Off"> <i class="bi bi-slash-circle"></i><span>Off</span></li>
|
|
<li class="um-gs-opt" data-egs="dl" data-value="everyone" data-icon="bi-globe" data-label="Everyone"> <i class="bi bi-globe"></i><span>Everyone</span></li>
|
|
<li class="um-gs-opt" data-egs="dl" data-value="registered" data-icon="bi-person-check" data-label="Members"> <i class="bi bi-person-check"></i><span>Members</span></li>
|
|
<li class="um-gs-opt" data-egs="dl" data-value="subscribers" data-icon="bi-star" data-label="Subscribers"> <i class="bi bi-star"></i><span>Subscribers</span></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="um-rule"></div>
|
|
|
|
{{-- ── Track Cards Section ── --}}
|
|
<div class="um-tracks-header">
|
|
<div>
|
|
<span class="um-tracks-title" id="edit-tracks-section-label">Language Tracks</span>
|
|
<span class="um-tracks-sub" id="edit-tracks-section-sub">Add audio tracks in different languages</span>
|
|
</div>
|
|
<button type="button" class="action-btn" id="edit-add-track-btn" onclick="editAddExtraTrack()" style="display:none;font-size:12px;">
|
|
<i class="bi bi-plus-circle"></i> <span>Add Language Track</span>
|
|
</button>
|
|
</div>
|
|
|
|
<input type="hidden" name="promote_track_id" id="edit-promote-track-id" value="">
|
|
<div id="edit-tc-list">
|
|
{{-- Track 1 card always visible in edit mode --}}
|
|
<div class="um-track-card" id="edit-tc-t1-card">
|
|
<div class="um-tc-body">
|
|
<div class="um-tc-left">
|
|
<span class="um-tc-drag" title="Drag to reorder"><i class="bi bi-grip-vertical"></i></span>
|
|
<div class="um-tc-num">1</div>
|
|
<span class="fi fi-xx um-tc-flag" id="edit-tc-flag-t1"></span>
|
|
<div class="um-tc-info">
|
|
<span class="um-tc-title" id="edit-tc-title-t1"></span>
|
|
<span class="um-tc-primary" id="edit-tc-t1-primary-badge"><i class="bi bi-star-fill"></i> Primary</span>
|
|
</div>
|
|
</div>
|
|
<div class="um-tc-right">
|
|
<button type="button" class="action-btn" onclick="editOpenTrackPopup('t1')">
|
|
<i class="bi bi-pencil"></i> <span>Edit</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- Secondary track cards appended here by JS --}}
|
|
</div>
|
|
|
|
{{-- Status --}}
|
|
<div id="edit-status-msg" class="status-message-modal"></div>
|
|
|
|
{{-- Submit --}}
|
|
<button type="submit" id="edit-submit-btn" class="action-btn action-btn-primary um-submit">
|
|
<i class="bi bi-check-lg"></i> <span>Save Changes</span>
|
|
</button>
|
|
|
|
</div>{{-- /um-body --}}
|
|
|
|
{{-- ── Track Editor Mini-Popup ── --}}
|
|
<div id="edit-track-popup" class="um-track-popup" style="display:none;">
|
|
<div class="um-track-popup-box">
|
|
<div class="um-tp-header">
|
|
<div class="um-tp-header-left">
|
|
<div class="um-tp-header-icon"><i class="bi bi-translate"></i></div>
|
|
<div class="um-tp-header-title">Track Editor</div>
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white" onclick="editCloseTrackPopup()" aria-label="Close"></button>
|
|
</div>
|
|
<div class="um-tp-body">
|
|
|
|
{{-- Track 1 form (primary) --}}
|
|
<x-track-editor-form
|
|
:is-primary="true"
|
|
prefix="t1"
|
|
language-name="primary_language"
|
|
language-id="edit_primary_language"
|
|
title-name="title"
|
|
title-id="edit-track1-title"
|
|
desc-name="description"
|
|
desc-id="edit-track1-desc"
|
|
video-file-input-id="edit-video-file"
|
|
/>
|
|
|
|
{{-- Extra track forms appended by JS --}}
|
|
<div id="edit-tf-extra"></div>
|
|
|
|
</div>{{-- /um-tp-body --}}
|
|
|
|
<div class="um-tp-footer">
|
|
<button type="button" class="action-btn action-btn-primary" onclick="editCloseTrackPopup()">
|
|
<i class="bi bi-check-lg"></i> <span>Done</span>
|
|
</button>
|
|
</div>
|
|
</div>{{-- /um-track-popup-box --}}
|
|
</div>{{-- /edit-track-popup --}}
|
|
|
|
</form>{{-- /edit-form --}}
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ── Secondary track form template (cloned by JS for each extra track) ──── --}}
|
|
<div id="edit-extra-track-tpl" style="display:none;" aria-hidden="true">
|
|
<x-track-editor-form
|
|
prefix="__TPL__"
|
|
:is-primary="false"
|
|
language-name="__LANGNAME__"
|
|
language-id="csd_v___TPL__"
|
|
title-name="__TITLENAME__"
|
|
title-id="edit-__TPL__-title"
|
|
desc-name="__DESCNAME__"
|
|
desc-id="edit-__TPL__-desc"
|
|
:embed-audio-input="true"
|
|
audio-input-name="__AUDIONAME__"
|
|
video-file-input-id=""
|
|
/>
|
|
</div>
|
|
|
|
<style>
|
|
#editVideoModal .modal-dialog { opacity: 0; transition: opacity .25s ease; }
|
|
#editVideoModal.show .modal-dialog { opacity: 1; }
|
|
|
|
/* ── Track drag-and-drop reorder ─────────────────────────────── */
|
|
#edit-tc-list .um-track-card { position: relative; }
|
|
#edit-tc-list .um-tc-drag {
|
|
display: flex; align-items: center; justify-content: center;
|
|
width: 28px; height: 28px; margin-right: 2px;
|
|
color: #666; cursor: grab; user-select: none;
|
|
border-radius: 6px; transition: color .15s, background .15s;
|
|
flex-shrink: 0;
|
|
}
|
|
#edit-tc-list .um-tc-drag:hover { color: #e5e5e5; background: rgba(255,255,255,.05); }
|
|
#edit-tc-list .um-tc-drag:active { cursor: grabbing; }
|
|
#edit-tc-list .um-tc-drag i { font-size: 14px; line-height: 1; }
|
|
#edit-tc-list .um-track-card.dragging {
|
|
opacity: .4; outline: 2px dashed #e61e1e; outline-offset: -2px;
|
|
}
|
|
#edit-tc-list .um-track-card.drop-before { box-shadow: 0 -3px 0 0 #e61e1e inset; }
|
|
#edit-tc-list .um-track-card.drop-after { box-shadow: 0 3px 0 0 #e61e1e inset; }
|
|
</style>
|
|
|
|
<script>
|
|
// ── Init primary track CSD ────────────────────────────────────────────────────
|
|
if (window.CSD) new CSD('csd_t1');
|
|
|
|
// ── State ─────────────────────────────────────────────────────────────────────
|
|
let _editSaving = false;
|
|
let _editExtraCount = 0;
|
|
let _editMode = 'generic';
|
|
let _editSlidesData = {}; // keyed by track id, array of {id?, url, file?}
|
|
let _editSlidesDragSrc = null;
|
|
let _editDeleteTrackIds = [];
|
|
window._editCurrentVideoId = null;
|
|
|
|
// ── Modal open / close ────────────────────────────────────────────────────────
|
|
function openEditVideoModal(videoId) {
|
|
if (window.innerWidth < 992) {
|
|
window.location.href = `/videos/${videoId}/edit`;
|
|
return;
|
|
}
|
|
window._editCurrentVideoId = videoId;
|
|
|
|
fetch(`/videos/${videoId}/edit`, {
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Accept': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (!data.success) { showToast('Failed to load video data', 'error'); return; }
|
|
const v = data.video;
|
|
|
|
document.getElementById('edit-form').action = `/videos/${videoId}`;
|
|
|
|
// Header
|
|
const lbl = document.getElementById('edit-modal-label');
|
|
if (lbl) lbl.textContent = v.title || 'Video';
|
|
|
|
// Reset everything first
|
|
_editResetForm();
|
|
|
|
// Global settings
|
|
const typeMap = {generic:'bi-film',music:'bi-music-note-beamed',match:'bi-trophy'};
|
|
const typeLabels = {generic:'Generic',music:'Music',match:'Match'};
|
|
const t = v.type || 'generic';
|
|
_editGsSet('type', t, typeMap[t]||'bi-film', typeLabels[t]||'Generic');
|
|
|
|
const visMap = {public:'bi-globe',unlisted:'bi-link-45deg',private:'bi-lock'};
|
|
const visLabel = {public:'Public',unlisted:'Unlisted',private:'Private'};
|
|
const vis = v.visibility || 'public';
|
|
_editGsSet('vis', vis, visMap[vis]||'bi-globe', visLabel[vis]||'Public');
|
|
|
|
const dlMap = {disabled:'bi-slash-circle',everyone:'bi-globe',registered:'bi-person-check',subscribers:'bi-star'};
|
|
const dlLabel = {disabled:'Off',everyone:'Everyone',registered:'Members',subscribers:'Subscribers'};
|
|
const dl = v.download_access || 'disabled';
|
|
_editGsSet('dl', dl, dlMap[dl]||'bi-slash-circle', dlLabel[dl]||'Off');
|
|
|
|
_editApplyMode(t);
|
|
|
|
// Track 1 popup fields
|
|
const titleEl = document.getElementById('edit-track1-title');
|
|
const descEl = document.getElementById('edit-track1-desc');
|
|
if (titleEl) titleEl.value = v.title || '';
|
|
if (descEl) { descEl.value = v.description || ''; descEl.dispatchEvent(new Event('rte:refresh')); }
|
|
_editSetLangSelect(v.language || '');
|
|
|
|
// Thumbnail
|
|
if (v.thumbnail && v.thumbnail_url) {
|
|
const prev = document.getElementById('edit-t1-thumbnail-preview');
|
|
const ph = document.getElementById('edit-t1-thumbnail-ph');
|
|
const info = document.getElementById('edit-t1-thumbnail-info');
|
|
const fn = document.getElementById('edit-t1-thumbnail-fname');
|
|
if (prev) prev.src = v.thumbnail_url;
|
|
if (fn) fn.textContent = 'Current thumbnail';
|
|
if (ph) ph.style.display = 'none';
|
|
if (info) info.style.display = 'flex';
|
|
}
|
|
|
|
// Slides
|
|
_editSlidesData['t1'] = (v.slides || []).map(s => ({ id: s.id, url: s.url }));
|
|
_editRenderSlides('t1');
|
|
|
|
// Extra tracks — show whenever the row is declared music, even if the
|
|
// primary file is still a video (mid-conversion state).
|
|
if ((v.type === 'music' || v.is_audio) && v.audio_tracks) {
|
|
v.audio_tracks.forEach(track => _editAddExistingTrack(track));
|
|
}
|
|
|
|
// Update track 1 card
|
|
editUpdateTrackCard('t1');
|
|
|
|
// Open modal
|
|
const modal = new bootstrap.Modal(document.getElementById('editVideoModal'));
|
|
modal.show();
|
|
setTimeout(() => document.getElementById('editVideoModal').classList.add('show'), 10);
|
|
})
|
|
.catch(() => showToast('Failed to load video data', 'error'));
|
|
}
|
|
|
|
function closeEditVideoModal() {
|
|
const el = document.getElementById('editVideoModal');
|
|
const m = bootstrap.Modal.getInstance(el);
|
|
if (m) m.hide();
|
|
el.addEventListener('hidden.bs.modal', () => {
|
|
el.classList.remove('show');
|
|
window._editCurrentVideoId = null;
|
|
}, { once: true });
|
|
}
|
|
|
|
function _editHideConfirm() { document.getElementById('edit-close-confirm').style.display = 'none'; }
|
|
function _editDoClose() { _editHideConfirm(); closeEditVideoModal(); }
|
|
|
|
// ── Form reset ────────────────────────────────────────────────────────────────
|
|
function _editResetForm() {
|
|
_editSaving = false;
|
|
_editExtraCount = 0;
|
|
_editDeleteTrackIds = [];
|
|
|
|
document.getElementById('edit-form').reset();
|
|
|
|
document.getElementById('edit-status-msg').className = 'status-message-modal';
|
|
const btn = document.getElementById('edit-submit-btn');
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="bi bi-check-lg"></i> <span>Save Changes</span>';
|
|
|
|
document.getElementById('edit-track-popup').style.display = 'none';
|
|
document.getElementById('edit-tf-t1').style.display = 'none';
|
|
|
|
// Clear extra track cards (leave only the primary card in #edit-tc-list)
|
|
const _tcList = document.getElementById('edit-tc-list');
|
|
const _primaryCard = document.getElementById('edit-tc-t1-card');
|
|
Array.from(_tcList.children).forEach(c => { if (c !== _primaryCard) c.remove(); });
|
|
document.getElementById('edit-promote-track-id').value = '';
|
|
document.getElementById('edit-tf-extra').innerHTML = '';
|
|
_editUpdateTrackPositions();
|
|
|
|
// Clear slides data
|
|
_editSlidesData = {};
|
|
_editRenderSlides('t1');
|
|
|
|
// Reset video dropzone
|
|
document.getElementById('edit-t1-dz-idle').style.display = '';
|
|
document.getElementById('edit-t1-file-info').style.display = 'none';
|
|
|
|
// Reset thumbnail
|
|
document.getElementById('edit-t1-thumbnail-ph').style.display = '';
|
|
document.getElementById('edit-t1-thumbnail-info').style.display = 'none';
|
|
const prev = document.getElementById('edit-t1-thumbnail-preview');
|
|
if (prev) prev.src = '';
|
|
|
|
// Reset audio box
|
|
const fn = document.getElementById('edit-t1-fname');
|
|
if (fn) fn.textContent = 'Keep existing / choose new…';
|
|
const audioBox = document.getElementById('edit-tf-t1-audio-box');
|
|
if (audioBox) audioBox.style.borderColor = '';
|
|
|
|
// Reset language CSD
|
|
_editSetLangSelect('');
|
|
}
|
|
|
|
function _editGsSet(name, value, icon, label) {
|
|
const ico = document.getElementById('edit-gs-' + name + '-ico');
|
|
const txt = document.getElementById('edit-gs-' + name + '-txt');
|
|
const menu = document.getElementById('edit-gs-' + name + '-menu');
|
|
const btn = document.getElementById('edit-gs-' + name + '-btn');
|
|
if (ico) ico.className = 'bi ' + icon + ' um-gs-ico';
|
|
if (txt) txt.textContent = label;
|
|
if (menu) menu.querySelectorAll('[data-egs]').forEach(li => li.classList.toggle('active', li.dataset.value === value));
|
|
if (btn) { btn.classList.remove('open'); if (menu) menu.hidden = true; }
|
|
const inputId = {type:'edit-type-val', vis:'edit-vis-val', dl:'edit-dl-val'}[name];
|
|
if (inputId) document.getElementById(inputId).value = value;
|
|
}
|
|
|
|
// ── Apply mode ────────────────────────────────────────────────────────────────
|
|
function _editApplyMode(type) {
|
|
_editMode = type;
|
|
const isMusic = type === 'music';
|
|
document.getElementById('edit-add-track-btn').style.display = isMusic ? '' : 'none';
|
|
const lbl = document.getElementById('edit-tracks-section-label');
|
|
const sub = document.getElementById('edit-tracks-section-sub');
|
|
if (isMusic) {
|
|
if (lbl) lbl.textContent = 'Language Tracks';
|
|
if (sub) sub.textContent = 'Add audio tracks in different languages';
|
|
} else {
|
|
if (lbl) lbl.textContent = type === 'match' ? 'Match Video' : 'Video Details';
|
|
if (sub) sub.textContent = 'Click Edit to update title, description and media';
|
|
}
|
|
const videoZone = document.getElementById('edit-tf-t1-video-zone');
|
|
const thumbWrap = document.getElementById('edit-tf-t1-thumb-wrap');
|
|
const musicPair = document.getElementById('edit-tf-t1-music-pair');
|
|
if (isMusic) {
|
|
if (videoZone) videoZone.style.display = 'none';
|
|
if (thumbWrap) thumbWrap.style.display = 'none';
|
|
if (musicPair) musicPair.style.display = 'flex';
|
|
} else {
|
|
if (videoZone) videoZone.style.display = 'flex';
|
|
if (thumbWrap) thumbWrap.style.display = 'flex';
|
|
if (musicPair) musicPair.style.display = 'none';
|
|
}
|
|
document.getElementById('edit-type-val').value = type;
|
|
}
|
|
|
|
// ── Global Settings dropdowns ─────────────────────────────────────────────────
|
|
document.querySelectorAll('#editVideoModal .um-gs-btn').forEach(btn => {
|
|
btn.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
const name = this.id.replace('edit-gs-', '').replace('-btn', '');
|
|
const menu = document.getElementById('edit-gs-' + name + '-menu');
|
|
const wasOpen = !menu.hidden;
|
|
document.querySelectorAll('#editVideoModal .um-gs-menu').forEach(m => m.hidden = true);
|
|
document.querySelectorAll('#editVideoModal .um-gs-btn').forEach(b => b.classList.remove('open'));
|
|
if (!wasOpen) { menu.hidden = false; this.classList.add('open'); }
|
|
});
|
|
});
|
|
|
|
document.addEventListener('click', function(e) {
|
|
if (!e.target.closest('#editVideoModal .um-gs-wrap')) {
|
|
document.querySelectorAll('#editVideoModal .um-gs-menu').forEach(m => m.hidden = true);
|
|
document.querySelectorAll('#editVideoModal .um-gs-btn').forEach(b => b.classList.remove('open'));
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll('[data-egs]').forEach(opt => {
|
|
opt.addEventListener('click', function() {
|
|
const name = this.dataset.egs;
|
|
const value = this.dataset.value;
|
|
const icon = this.dataset.icon;
|
|
const label = this.dataset.label;
|
|
_editGsSet(name, value, icon, label);
|
|
if (name === 'type') _editApplyMode(value);
|
|
});
|
|
});
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
function _editFmtSize(bytes) {
|
|
if (!bytes) return '0 B';
|
|
const k = 1024, sizes = ['B','KB','MB','GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k,i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
// ── Track ordering (drag-and-drop) ────────────────────────────────────────────
|
|
function _editUpdateTrackPositions() {
|
|
const list = document.getElementById('edit-tc-list');
|
|
if (!list) return;
|
|
const cards = Array.from(list.querySelectorAll(':scope > .um-track-card'));
|
|
cards.forEach((card, i) => {
|
|
const numEl = card.querySelector('.um-tc-num');
|
|
if (numEl) numEl.textContent = i + 1;
|
|
const badge = card.querySelector('.um-tc-primary');
|
|
if (badge) badge.style.display = i === 0 ? '' : 'none';
|
|
});
|
|
// Set promote_track_id: non-empty only when a secondary is at position 1
|
|
const first = cards[0];
|
|
const promoteEl = document.getElementById('edit-promote-track-id');
|
|
if (promoteEl) {
|
|
const isSecondary = first && first.id !== 'edit-tc-t1-card';
|
|
promoteEl.value = isSecondary ? (first.dataset.trackId || '') : '';
|
|
console.log('%c[EditTrack] Position 1 is now:', 'color:#3b82f6',
|
|
isSecondary ? ('secondary track ' + promoteEl.value + ' (will be promoted to primary)') : 'original primary (no promotion)');
|
|
}
|
|
}
|
|
|
|
// HTML5 drag-and-drop: bind once on the shared parent so newly appended cards
|
|
// pick it up without extra wiring. The card starts non-draggable so text
|
|
// selection / button clicks work; grabbing the grip handle flips draggable=on
|
|
// for the duration of the drag.
|
|
(function () {
|
|
const list = document.getElementById('edit-tc-list');
|
|
if (!list || list.dataset.dndWired) return;
|
|
list.dataset.dndWired = '1';
|
|
|
|
// Reset any draggable="true" set by the initial HTML — we only enable it
|
|
// on demand via the grip handle.
|
|
list.querySelectorAll('.um-track-card').forEach(c => c.removeAttribute('draggable'));
|
|
|
|
let dragged = null;
|
|
|
|
list.addEventListener('mousedown', function (e) {
|
|
const handle = e.target.closest('.um-tc-drag');
|
|
if (!handle) return;
|
|
const card = handle.closest('.um-track-card');
|
|
if (!card || card.parentElement !== list) return;
|
|
card.setAttribute('draggable', 'true');
|
|
});
|
|
list.addEventListener('mouseup', function () {
|
|
list.querySelectorAll('.um-track-card[draggable="true"]').forEach(c => c.removeAttribute('draggable'));
|
|
});
|
|
|
|
list.addEventListener('dragstart', function (e) {
|
|
const card = e.target.closest('.um-track-card');
|
|
if (!card || card.parentElement !== list || card.getAttribute('draggable') !== 'true') {
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
dragged = card;
|
|
card.classList.add('dragging');
|
|
try { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', card.id); } catch (_) {}
|
|
});
|
|
|
|
list.addEventListener('dragend', function () {
|
|
if (dragged) dragged.classList.remove('dragging');
|
|
list.querySelectorAll('.drop-before, .drop-after').forEach(c => c.classList.remove('drop-before', 'drop-after'));
|
|
list.querySelectorAll('.um-track-card[draggable="true"]').forEach(c => c.removeAttribute('draggable'));
|
|
dragged = null;
|
|
});
|
|
|
|
// Always accept drops anywhere inside the list so the browser doesn't
|
|
// veto the drop when the cursor slips into gaps between cards.
|
|
list.addEventListener('dragover', function (e) {
|
|
if (!dragged) return;
|
|
e.preventDefault();
|
|
try { e.dataTransfer.dropEffect = 'move'; } catch (_) {}
|
|
const over = e.target.closest('.um-track-card');
|
|
list.querySelectorAll('.drop-before, .drop-after').forEach(c => c.classList.remove('drop-before', 'drop-after'));
|
|
if (!over || over === dragged || over.parentElement !== list) return;
|
|
const r = over.getBoundingClientRect();
|
|
const after = (e.clientY - r.top) > (r.height / 2);
|
|
over.classList.add(after ? 'drop-after' : 'drop-before');
|
|
});
|
|
|
|
list.addEventListener('drop', function (e) {
|
|
if (!dragged) return;
|
|
e.preventDefault();
|
|
const cards = Array.from(list.querySelectorAll(':scope > .um-track-card'));
|
|
const over = e.target.closest('.um-track-card');
|
|
let refNode;
|
|
if (over && over !== dragged && over.parentElement === list) {
|
|
const r = over.getBoundingClientRect();
|
|
const after = (e.clientY - r.top) > (r.height / 2);
|
|
refNode = after ? over.nextSibling : over;
|
|
} else {
|
|
// Cursor fell into a gap or outside a card — figure out the closest slot
|
|
// by comparing cursor Y against each card's midpoint.
|
|
let insertBefore = null;
|
|
for (const c of cards) {
|
|
if (c === dragged) continue;
|
|
const r = c.getBoundingClientRect();
|
|
if (e.clientY < r.top + r.height / 2) { insertBefore = c; break; }
|
|
}
|
|
refNode = insertBefore; // null → append to end
|
|
}
|
|
if (refNode === dragged) refNode = dragged.nextSibling;
|
|
list.insertBefore(dragged, refNode);
|
|
list.querySelectorAll('.drop-before, .drop-after').forEach(c => c.classList.remove('drop-before', 'drop-after'));
|
|
console.log('%c[EditTrack] Drag-reordered:', 'color:#3b82f6', { moved: dragged.id, refNode: refNode?.id || '(end)' });
|
|
_editUpdateTrackPositions();
|
|
});
|
|
})();
|
|
|
|
function _editSetLangCsd(wrap, code) {
|
|
if (!wrap || !code) return;
|
|
const opt = wrap.querySelector(`.csd-opt[data-v="${code}"]`);
|
|
const ico = wrap.querySelector('.csd-ico');
|
|
const valEl = wrap.querySelector('.csd-val');
|
|
if (!opt) return;
|
|
if (ico) ico.innerHTML = opt.querySelector('.csd-opt-ico').innerHTML;
|
|
if (valEl) { valEl.textContent = opt.querySelector('.csd-opt-main').textContent; valEl.classList.remove('ph'); }
|
|
wrap.querySelectorAll('.csd-opt').forEach(o => o.setAttribute('aria-selected', o === opt ? 'true' : 'false'));
|
|
}
|
|
|
|
function _editSetLangSelect(code) {
|
|
const inp = document.getElementById('edit_primary_language');
|
|
if (!inp) return;
|
|
inp.value = code;
|
|
const wrap = inp.closest('.csd-wrap');
|
|
if (!wrap) return;
|
|
const ico = wrap.querySelector('.csd-ico');
|
|
const valEl = wrap.querySelector('.csd-val');
|
|
if (!code) {
|
|
if (ico) ico.innerHTML = '<span class="fi fi-xx lsd-flag"></span>';
|
|
if (valEl) { valEl.textContent = 'Select language'; valEl.classList.add('ph'); }
|
|
wrap.querySelectorAll('.csd-opt').forEach(o => o.setAttribute('aria-selected','false'));
|
|
} else {
|
|
const opt = wrap.querySelector(`.csd-opt[data-v="${code}"]`);
|
|
if (opt) {
|
|
if (ico) ico.innerHTML = opt.querySelector('.csd-opt-ico').innerHTML;
|
|
if (valEl) { valEl.textContent = opt.querySelector('.csd-opt-main').textContent; valEl.classList.remove('ph'); }
|
|
wrap.querySelectorAll('.csd-opt').forEach(o => o.setAttribute('aria-selected', o===opt ? 'true':'false'));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Video file handling ───────────────────────────────────────────────────────
|
|
const editVideoFile = document.getElementById('edit-video-file');
|
|
editVideoFile.addEventListener('change', function() { editHandleVideoSelect(this); });
|
|
|
|
const EDIT_AUDIO_EXTS = ['mp3','m4a','aac','flac','wav'];
|
|
function _editIsAudio(file) {
|
|
const ext = file.name.split('.').pop().toLowerCase();
|
|
return EDIT_AUDIO_EXTS.includes(ext) || file.type.startsWith('audio/');
|
|
}
|
|
|
|
function editHandleVideoSelect(input) {
|
|
if (!input.files || !input.files[0]) return;
|
|
const file = input.files[0];
|
|
const audio = _editIsAudio(file);
|
|
|
|
if (audio && _editMode !== 'music') {
|
|
_editApplyMode('music');
|
|
_editGsSet('type','music','bi-music-note-beamed','Music');
|
|
} else if (!audio && _editMode === 'music') {
|
|
_editApplyMode('generic');
|
|
_editGsSet('type','generic','bi-film','Generic');
|
|
}
|
|
|
|
if (audio) {
|
|
const fn = document.getElementById('edit-t1-fname');
|
|
if (fn) fn.textContent = file.name;
|
|
const audioBox = document.getElementById('edit-tf-t1-audio-box');
|
|
if (audioBox) audioBox.style.borderColor = '#22c55e';
|
|
} else {
|
|
document.getElementById('edit-t1-filename').textContent = file.name;
|
|
document.getElementById('edit-t1-filesize').textContent = _editFmtSize(file.size);
|
|
document.getElementById('edit-t1-dz-idle').style.display = 'none';
|
|
document.getElementById('edit-t1-file-info').style.display = 'flex';
|
|
}
|
|
editUpdateTrackCard('t1');
|
|
}
|
|
|
|
function editRemoveVideo(e) {
|
|
e.preventDefault(); e.stopPropagation();
|
|
editVideoFile.value = '';
|
|
document.getElementById('edit-t1-dz-idle').style.display = '';
|
|
document.getElementById('edit-t1-file-info').style.display = 'none';
|
|
}
|
|
|
|
// ── Thumbnail ─────────────────────────────────────────────────────────────────
|
|
const editThumbDz = document.getElementById('edit-t1-thumbnail-dropzone');
|
|
let editThumbInp = document.getElementById('edit-t1-thumbnail-input');
|
|
|
|
editThumbDz.addEventListener('click', function(e) {
|
|
if (e.target.closest('.btn-remove-file')) return;
|
|
if (typeof window.openCropperModal_thumb_edit === 'function') {
|
|
window.openCropperModal_thumb_edit();
|
|
const internal = document.getElementById('tcInput_thumb_edit');
|
|
if (internal) internal.click();
|
|
} else { editThumbInp.click(); }
|
|
});
|
|
editThumbDz.addEventListener('dragover', e => { e.preventDefault(); editThumbDz.style.borderColor='#e61e1e'; });
|
|
editThumbDz.addEventListener('dragleave', () => { editThumbDz.style.borderColor=''; });
|
|
editThumbDz.addEventListener('drop', e => {
|
|
e.preventDefault(); editThumbDz.style.borderColor='';
|
|
if (e.dataTransfer.files.length) {
|
|
if (typeof window.tcPreload_thumb_edit === 'function') {
|
|
window.tcPreload_thumb_edit(e.dataTransfer.files[0]);
|
|
window.openCropperModal_thumb_edit();
|
|
} else { editThumbInp.files = e.dataTransfer.files; editHandleThumbnail(editThumbInp, 't1'); }
|
|
}
|
|
});
|
|
|
|
function editHandleThumbnail(input, prefix) {
|
|
if (!input.files || !input.files[0]) return;
|
|
const file = input.files[0];
|
|
document.getElementById('edit-' + prefix + '-thumbnail-fname').textContent = file.name;
|
|
document.getElementById('edit-' + prefix + '-thumbnail-fsize').textContent = _editFmtSize(file.size);
|
|
const reader = new FileReader();
|
|
reader.onload = e => { document.getElementById('edit-' + prefix + '-thumbnail-preview').src = e.target.result; };
|
|
reader.readAsDataURL(file);
|
|
document.getElementById('edit-' + prefix + '-thumbnail-ph').style.display = 'none';
|
|
document.getElementById('edit-' + prefix + '-thumbnail-info').style.display = 'flex';
|
|
}
|
|
|
|
function editRemoveThumbnail(e, prefix) {
|
|
prefix = prefix || 't1';
|
|
if (e) { e.preventDefault(); e.stopPropagation(); }
|
|
if (prefix === 't1') {
|
|
const fresh = editThumbInp.cloneNode(false);
|
|
editThumbInp.parentNode.replaceChild(fresh, editThumbInp);
|
|
editThumbInp = fresh;
|
|
fresh.setAttribute('onchange', "editHandleThumbnail(this,'t1')");
|
|
}
|
|
document.getElementById('edit-' + prefix + '-thumbnail-ph').style.display = '';
|
|
document.getElementById('edit-' + prefix + '-thumbnail-info').style.display = 'none';
|
|
document.getElementById('edit-' + prefix + '-thumbnail-preview').src = '';
|
|
}
|
|
|
|
// ── Slides ────────────────────────────────────────────────────────────────────
|
|
function editSlidesZoneClick(e, tid) { tid=tid||'t1'; if (e.target.closest('button')) return; document.getElementById('edit-'+tid+'-slides-input').click(); }
|
|
function editSlidesZoneDragover(e, tid) { tid=tid||'t1'; if (Array.from(e.dataTransfer.types||[]).includes('Files')) { e.preventDefault(); document.getElementById('edit-'+tid+'-slides-zone').classList.add('dragover'); } }
|
|
function editSlidesZoneDragleave(tid) { tid=tid||'t1'; document.getElementById('edit-'+tid+'-slides-zone').classList.remove('dragover'); }
|
|
function editSlidesZoneDrop(e, tid) { tid=tid||'t1'; e.preventDefault(); document.getElementById('edit-'+tid+'-slides-zone').classList.remove('dragover'); if (e.dataTransfer.files.length) editHandleSlides(e.dataTransfer.files, tid); }
|
|
|
|
function editHandleSlides(fileList, tid) {
|
|
tid = tid || 't1';
|
|
_editSlidesCropStart(fileList, tid, _editRenderSlides);
|
|
}
|
|
|
|
// ── Slides crop queue: every added image is cropped before it enters the strip ──
|
|
let _editSlidesCropQueue = [];
|
|
let _editSlidesCropTid = null;
|
|
let _editSlidesCropRender = null;
|
|
|
|
function _editSlidesCropStart(fileList, tid, renderFn) {
|
|
const imgs = Array.from(fileList || []).filter(f => f.type && f.type.startsWith('image/'));
|
|
if (!imgs.length) return;
|
|
_editSlidesCropTid = tid;
|
|
_editSlidesCropRender = renderFn;
|
|
_editSlidesCropQueue = imgs;
|
|
_editSlidesCropLoadNext();
|
|
}
|
|
|
|
function _editSlidesCropLoadNext() {
|
|
if (!_editSlidesCropQueue.length) { window.closeCropperModal('slides_edit'); return; }
|
|
const f = _editSlidesCropQueue.shift();
|
|
if (typeof window.tcPreload_slides_edit === 'function') {
|
|
window.tcPreload_slides_edit(f);
|
|
window.openCropperModal_slides_edit();
|
|
}
|
|
}
|
|
|
|
function editSlidesCropDone(file) {
|
|
const tid = _editSlidesCropTid;
|
|
if (tid && file) {
|
|
if (!_editSlidesData[tid]) _editSlidesData[tid] = [];
|
|
if (_editSlidesData[tid].length < 10) {
|
|
const reader = new FileReader();
|
|
reader.onload = ev => {
|
|
_editSlidesData[tid].push({ file, url: ev.target.result });
|
|
if (_editSlidesCropRender) _editSlidesCropRender(tid);
|
|
_editSlidesCropLoadNext();
|
|
};
|
|
reader.readAsDataURL(file);
|
|
return;
|
|
}
|
|
}
|
|
_editSlidesCropLoadNext();
|
|
}
|
|
|
|
function editClearSlides(e, tid) {
|
|
tid = tid || 't1';
|
|
if (e) { e.preventDefault(); e.stopPropagation(); }
|
|
_editSlidesData[tid] = [];
|
|
const inp = document.getElementById('edit-' + tid + '-slides-input');
|
|
if (inp) { const fresh = inp.cloneNode(false); fresh.setAttribute('onchange',"editHandleSlides(this.files,'"+tid+"')"); inp.parentNode.replaceChild(fresh, inp); }
|
|
_editRenderSlides(tid);
|
|
}
|
|
|
|
let _editSlidesDrag = null;
|
|
function _editRenderSlides(tid) {
|
|
const files = _editSlidesData[tid] || [];
|
|
const strip = document.getElementById('edit-' + tid + '-slides-strip');
|
|
const ph = document.getElementById('edit-' + tid + '-slides-ph');
|
|
const prev = document.getElementById('edit-' + tid + '-slides-preview');
|
|
const cnt = document.getElementById('edit-' + tid + '-slides-count');
|
|
if (!strip) return;
|
|
strip.innerHTML = '';
|
|
if (!files.length) {
|
|
if (ph) ph.style.display = '';
|
|
if (prev) prev.style.display = 'none';
|
|
document.getElementById('edit-' + tid + '-slides-order').value = '[]';
|
|
return;
|
|
}
|
|
files.forEach((item, index) => {
|
|
const wrap = document.createElement('div');
|
|
wrap.style.cssText = 'position:relative;flex-shrink:0;cursor:grab;';
|
|
wrap.draggable = true; wrap.dataset.index = index;
|
|
wrap.addEventListener('dragstart', ev => { _editSlidesDrag = index; ev.dataTransfer.effectAllowed='move'; setTimeout(()=>{wrap.style.opacity='.4';},0); });
|
|
wrap.addEventListener('dragend', () => { _editSlidesDrag = null; wrap.style.opacity=''; });
|
|
wrap.addEventListener('dragover', ev => { if (_editSlidesDrag==null) return; ev.preventDefault(); wrap.style.outline='2px solid #e61e1e'; });
|
|
wrap.addEventListener('dragleave', () => { wrap.style.outline=''; });
|
|
wrap.addEventListener('drop', ev => {
|
|
ev.preventDefault(); ev.stopPropagation(); wrap.style.outline='';
|
|
if (_editSlidesDrag==null || _editSlidesDrag===index) return;
|
|
const arr = _editSlidesData[tid];
|
|
const [moved] = arr.splice(_editSlidesDrag, 1);
|
|
arr.splice(index, 0, moved);
|
|
_editRenderSlides(tid);
|
|
});
|
|
const rb = document.createElement('button');
|
|
rb.type='button'; rb.style.cssText='position:absolute;top:-4px;right:-4px;width:16px;height:16px;background:#e61e1e;border:none;border-radius:50%;color:#fff;font-size:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;z-index:1;';
|
|
rb.innerHTML='<i class="bi bi-x"></i>';
|
|
rb.addEventListener('click', ev => { ev.stopPropagation(); _editSlidesData[tid].splice(index,1); _editRenderSlides(tid); });
|
|
const img = document.createElement('img');
|
|
img.style.cssText='width:52px;height:52px;object-fit:cover;border-radius:6px;border:1px solid rgba(255,255,255,.12);display:block;'; img.alt=''; img.src=item.url;
|
|
wrap.appendChild(img); wrap.appendChild(rb); strip.appendChild(wrap);
|
|
});
|
|
const ids = files.filter(s => s.id).map(s => s.id);
|
|
document.getElementById('edit-' + tid + '-slides-order').value = JSON.stringify(ids);
|
|
if (cnt) cnt.textContent = files.length===1 ? '1 image — static cover' : files.length+' images — will crossfade during playback';
|
|
if (ph) ph.style.display = 'none';
|
|
if (prev) prev.style.display = '';
|
|
document.getElementById('edit-' + tid + '-slides-zone').style.borderColor = '#22c55e';
|
|
}
|
|
|
|
// ── Slides helpers for extra tracks ──────────────────────────────────────────
|
|
function editSlidesZoneClickE(e, tid) { if (e.target.closest('button')) return; document.getElementById('edit-slides-input-'+tid).click(); }
|
|
function editSlidesZoneDragoverE(e, tid) { if (Array.from(e.dataTransfer.types||[]).includes('Files')) { e.preventDefault(); document.getElementById('edit-slides-zone-'+tid).classList.add('dragover'); } }
|
|
function editSlidesZoneDragleaveE(tid) { document.getElementById('edit-slides-zone-'+tid).classList.remove('dragover'); }
|
|
function editSlidesZoneDropE(e, tid) { e.preventDefault(); document.getElementById('edit-slides-zone-'+tid).classList.remove('dragover'); if (e.dataTransfer.files.length) editHandleSlidesForTrack(tid, e.dataTransfer.files); }
|
|
|
|
function editHandleSlidesForTrack(tid, fileList) {
|
|
_editSlidesCropStart(fileList, tid, _editRenderSlidesForTrack);
|
|
}
|
|
|
|
function editClearSlidesForTrack(e, tid) {
|
|
if (e) { e.preventDefault(); e.stopPropagation(); }
|
|
_editSlidesData[tid] = [];
|
|
const inp = document.getElementById('edit-slides-input-'+tid);
|
|
if (inp) { const fresh = inp.cloneNode(false); fresh.setAttribute('onchange', "editHandleSlidesForTrack('"+tid+"',this.files)"); inp.parentNode.replaceChild(fresh, inp); }
|
|
_editRenderSlidesForTrack(tid);
|
|
}
|
|
|
|
let _editExtraSlidesDrag = null;
|
|
function _editRenderSlidesForTrack(tid) {
|
|
const files = _editSlidesData[tid] || [];
|
|
const strip = document.getElementById('edit-slides-strip-'+tid);
|
|
const ph = document.getElementById('edit-slides-ph-'+tid);
|
|
const prev = document.getElementById('edit-slides-preview-'+tid);
|
|
const cnt = document.getElementById('edit-slides-count-'+tid);
|
|
const zone = document.getElementById('edit-slides-zone-'+tid);
|
|
if (!strip) return;
|
|
strip.innerHTML = '';
|
|
if (!files.length) {
|
|
if (ph) ph.style.display = '';
|
|
if (prev) prev.style.display = 'none';
|
|
return;
|
|
}
|
|
files.forEach((item, index) => {
|
|
const wrap = document.createElement('div');
|
|
wrap.style.cssText = 'position:relative;flex-shrink:0;cursor:grab;';
|
|
wrap.draggable = true; wrap.dataset.index = index;
|
|
wrap.addEventListener('dragstart', ev => { _editExtraSlidesDrag = {tid, index}; ev.dataTransfer.effectAllowed='move'; setTimeout(()=>{wrap.style.opacity='.4';},0); });
|
|
wrap.addEventListener('dragend', () => { _editExtraSlidesDrag = null; wrap.style.opacity=''; });
|
|
wrap.addEventListener('dragover', ev => { if (!_editExtraSlidesDrag || _editExtraSlidesDrag.tid !== tid) return; ev.preventDefault(); wrap.style.outline='2px solid #e61e1e'; });
|
|
wrap.addEventListener('dragleave', () => { wrap.style.outline=''; });
|
|
wrap.addEventListener('drop', ev => {
|
|
ev.preventDefault(); ev.stopPropagation(); wrap.style.outline='';
|
|
if (!_editExtraSlidesDrag || _editExtraSlidesDrag.tid !== tid || _editExtraSlidesDrag.index === index) return;
|
|
const arr = _editSlidesData[tid];
|
|
const [moved] = arr.splice(_editExtraSlidesDrag.index, 1);
|
|
arr.splice(index, 0, moved);
|
|
_editRenderSlidesForTrack(tid);
|
|
});
|
|
const rb = document.createElement('button');
|
|
rb.type='button'; rb.style.cssText='position:absolute;top:-4px;right:-4px;width:16px;height:16px;background:#e61e1e;border:none;border-radius:50%;color:#fff;font-size:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;z-index:1;';
|
|
rb.innerHTML='<i class="bi bi-x"></i>';
|
|
rb.addEventListener('click', ev => { ev.stopPropagation(); _editSlidesData[tid].splice(index,1); _editRenderSlidesForTrack(tid); });
|
|
const img = document.createElement('img');
|
|
img.style.cssText='width:52px;height:52px;object-fit:cover;border-radius:6px;border:1px solid rgba(255,255,255,.12);display:block;'; img.alt=''; img.src=item.url;
|
|
wrap.appendChild(img); wrap.appendChild(rb); strip.appendChild(wrap);
|
|
});
|
|
if (cnt) cnt.textContent = files.length===1 ? '1 image — static cover' : files.length+' images — will crossfade during playback';
|
|
if (ph) ph.style.display = 'none';
|
|
if (prev) prev.style.display = '';
|
|
if (zone) zone.style.borderColor = '#22c55e';
|
|
}
|
|
|
|
// ── Track popup ───────────────────────────────────────────────────────────────
|
|
function editOpenTrackPopup(trackId) {
|
|
document.querySelectorAll('#edit-track-popup .um-track-form').forEach(f => f.style.display = 'none');
|
|
const form = document.getElementById('edit-tf-' + trackId);
|
|
if (form) form.style.display = 'block';
|
|
const popup = document.getElementById('edit-track-popup');
|
|
popup.style.opacity = '0';
|
|
popup.style.display = 'flex';
|
|
requestAnimationFrame(() => { popup.style.opacity = '1'; });
|
|
}
|
|
|
|
function editCloseTrackPopup() {
|
|
document.querySelectorAll('#edit-track-popup .um-track-form').forEach(f => f.style.display = 'none');
|
|
document.getElementById('edit-track-popup').style.display = 'none';
|
|
editUpdateTrackCard('t1');
|
|
for (let i = 1; i <= _editExtraCount; i++) {
|
|
if (document.getElementById('edit-tc-e'+i)) editUpdateTrackCard('e'+i);
|
|
}
|
|
}
|
|
|
|
function editUpdateTrackCard(trackId) {
|
|
let langCode = '', title = '';
|
|
if (trackId === 't1') {
|
|
const li = document.getElementById('edit_primary_language');
|
|
const ti = document.getElementById('edit-track1-title');
|
|
langCode = li ? li.value : '';
|
|
title = ti ? ti.value.trim() : '';
|
|
} else {
|
|
const n = trackId.replace('e','');
|
|
const li = document.getElementById('csd_v_e' + n);
|
|
const ti = document.getElementById('edit-e' + n + '-title');
|
|
langCode = li ? li.value : '';
|
|
title = ti ? ti.value.trim() : '';
|
|
}
|
|
const OPTS = LANG_OPTIONS_EDIT_MODAL;
|
|
const lang = OPTS.find(o => o.value === langCode);
|
|
const flagEl = document.getElementById('edit-tc-flag-' + trackId);
|
|
const titleEl = document.getElementById('edit-tc-title-' + trackId);
|
|
if (flagEl) flagEl.className = 'fi fi-' + (lang ? lang.flag : 'xx') + ' um-tc-flag';
|
|
if (titleEl) {
|
|
if (lang && title) titleEl.textContent = title + ' — ' + lang.label;
|
|
else if (lang) titleEl.textContent = lang.label;
|
|
else if (title) titleEl.textContent = title;
|
|
else titleEl.textContent = '';
|
|
}
|
|
}
|
|
|
|
// ── Extra track management ────────────────────────────────────────────────────
|
|
const LANG_OPTIONS_EDIT_MODAL = @json(\App\Data\Languages::forLanguage());
|
|
|
|
function _editCloneTrackForm(prefix, langName, titleName, descName, audioName) {
|
|
const tpl = document.getElementById('edit-extra-track-tpl');
|
|
if (!tpl) return null;
|
|
const html = tpl.innerHTML
|
|
.replaceAll('__TPL__', prefix)
|
|
.replaceAll('__LANGNAME__', langName)
|
|
.replaceAll('__TITLENAME__', titleName)
|
|
.replaceAll('__DESCNAME__', descName)
|
|
.replaceAll('__AUDIONAME__', audioName);
|
|
const tmp = document.createElement('div');
|
|
tmp.innerHTML = html;
|
|
const formEl = tmp.querySelector('.um-track-form');
|
|
if (!formEl) return null;
|
|
formEl.style.display = 'none';
|
|
document.getElementById('edit-tf-extra').appendChild(formEl);
|
|
_editSlidesData[prefix] = [];
|
|
if (window.CSD) new CSD('csd_' + prefix);
|
|
return formEl;
|
|
}
|
|
|
|
function _editTrackCard(n, trackId, isExisting) {
|
|
const card = document.createElement('div');
|
|
card.className = 'um-track-card'; card.id = 'edit-tc-e' + n;
|
|
card.dataset.trackId = trackId || '';
|
|
const deleteBtn = isExisting
|
|
? `<button type="button" class="action-btn action-btn-danger icon-only" onclick="editDeleteTrack(${trackId},${n})" title="Delete"><i class="bi bi-trash"></i></button>`
|
|
: `<button type="button" class="action-btn action-btn-danger icon-only" onclick="editRemoveNewTrack(${n})" title="Remove"><i class="bi bi-trash"></i></button>`;
|
|
card.innerHTML = `
|
|
<div class="um-tc-body">
|
|
<div class="um-tc-left">
|
|
<span class="um-tc-drag" title="Drag to reorder"><i class="bi bi-grip-vertical"></i></span>
|
|
<div class="um-tc-num">${n + 1}</div>
|
|
<span class="fi fi-xx um-tc-flag" id="edit-tc-flag-e${n}"></span>
|
|
<div class="um-tc-info">
|
|
<span class="um-tc-title" id="edit-tc-title-e${n}"></span>
|
|
<span class="um-tc-primary" style="display:none;"><i class="bi bi-star-fill"></i> Primary</span>
|
|
</div>
|
|
</div>
|
|
<div class="um-tc-right">
|
|
<button type="button" class="action-btn" onclick="editOpenTrackPopup('e${n}')"><i class="bi bi-pencil"></i> <span>Edit</span></button>
|
|
${deleteBtn}
|
|
</div>
|
|
</div>`;
|
|
document.getElementById('edit-tc-list').appendChild(card);
|
|
return card;
|
|
}
|
|
|
|
function _editAddExistingTrack(track) {
|
|
const n = ++_editExtraCount;
|
|
const pfx = 'e' + n;
|
|
_editTrackCard(n, track.id, true);
|
|
_editCloneTrackForm(
|
|
pfx,
|
|
'track_language_updates[' + track.id + ']',
|
|
'track_title_updates[' + track.id + ']',
|
|
'track_description_updates[' + track.id + ']',
|
|
'track_file_updates[' + track.id + ']'
|
|
);
|
|
const titleEl = document.getElementById('edit-' + pfx + '-title');
|
|
if (titleEl) titleEl.value = track.title || '';
|
|
const descEl = document.getElementById('edit-' + pfx + '-desc');
|
|
if (descEl) { descEl.value = track.description || ''; descEl.dispatchEvent(new Event('rte:refresh')); }
|
|
if (track.language && window.CSD) {
|
|
const hi = document.getElementById('csd_v_' + pfx);
|
|
const wrap = document.getElementById('csd_' + pfx);
|
|
if (hi) hi.value = track.language;
|
|
if (wrap) _editSetLangCsd(wrap, track.language);
|
|
}
|
|
const hi = document.getElementById('csd_v_' + pfx);
|
|
if (hi) hi.addEventListener('change', () => editUpdateTrackCard(pfx));
|
|
editUpdateTrackCard(pfx);
|
|
_editUpdateTrackPositions();
|
|
}
|
|
|
|
function editDeleteTrack(trackId, n) {
|
|
_editDeleteTrackIds.push(trackId);
|
|
document.getElementById('edit-tc-e'+n)?.remove();
|
|
document.getElementById('edit-tf-e'+n)?.remove();
|
|
const popup = document.getElementById('edit-track-popup');
|
|
if (popup.style.display !== 'none') {
|
|
const vis = popup.querySelector('.um-track-form:not([style*="none"])');
|
|
if (!vis || vis.id === 'edit-tf-e'+n) popup.style.display = 'none';
|
|
}
|
|
_editUpdateTrackPositions();
|
|
}
|
|
|
|
function editAddExtraTrack() {
|
|
const n = ++_editExtraCount;
|
|
const pfx = 'e' + n;
|
|
_editTrackCard(n, null, false);
|
|
_editCloneTrackForm(
|
|
pfx,
|
|
'extra_track_languages[]',
|
|
'extra_track_titles[]',
|
|
'extra_track_descriptions[]',
|
|
'extra_track_files[]'
|
|
);
|
|
const hi = document.getElementById('csd_v_' + pfx);
|
|
if (hi) hi.addEventListener('change', () => editUpdateTrackCard(pfx));
|
|
_editUpdateTrackPositions();
|
|
editOpenTrackPopup(pfx);
|
|
}
|
|
|
|
function editRemoveNewTrack(n) {
|
|
document.getElementById('edit-tc-e'+n)?.remove();
|
|
document.getElementById('edit-tf-e'+n)?.remove();
|
|
delete _editSlidesData['e'+n];
|
|
const popup = document.getElementById('edit-track-popup');
|
|
if (popup.style.display !== 'none') {
|
|
const vis = popup.querySelector('.um-track-form:not([style*="none"])');
|
|
if (!vis || vis.id === 'edit-tf-e'+n) popup.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Wire primary language → update card
|
|
;(function() {
|
|
const li = document.getElementById('edit_primary_language');
|
|
if (li) li.addEventListener('change', () => editUpdateTrackCard('t1'));
|
|
}());
|
|
|
|
// ── Form submission ───────────────────────────────────────────────────────────
|
|
document.getElementById('edit-form').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const titleVal = (document.getElementById('edit-track1-title')?.value || '').trim();
|
|
if (!titleVal) {
|
|
document.getElementById('edit-status-msg').innerHTML = '<i class="bi bi-exclamation-circle-fill"></i> Please enter a title';
|
|
document.getElementById('edit-status-msg').className = 'status-message-modal error';
|
|
editOpenTrackPopup('t1');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData(this);
|
|
|
|
// Append new slide files (track 1)
|
|
(_editSlidesData['t1'] || []).filter(s => s.file).forEach(s => formData.append('slides_add[]', s.file, s.file.name));
|
|
|
|
// Append new slide files for extra tracks
|
|
for (let i = 1; i <= _editExtraCount; i++) {
|
|
const tid = 'e'+i;
|
|
(_editSlidesData[tid] || []).filter(s => s.file).forEach(s => formData.append('extra_track_slides_'+tid+'[]', s.file, s.file.name));
|
|
}
|
|
|
|
// Append delete IDs
|
|
_editDeleteTrackIds.forEach(id => formData.append('delete_track_ids[]', id));
|
|
|
|
// Ordered list of track IDs as displayed. Index 0 is the primary slot;
|
|
// "primary" is the sentinel for the original primary track row, since
|
|
// the primary isn't a video_audio_tracks row of its own.
|
|
Array.from(document.querySelectorAll('#edit-tc-list > .um-track-card')).forEach(card => {
|
|
const val = (card.id === 'edit-tc-t1-card') ? 'primary' : (card.dataset.trackId || '');
|
|
if (val) formData.append('track_order[]', val);
|
|
});
|
|
|
|
// ── Log tracker: dump the exact track-related payload being submitted ──────
|
|
(function () {
|
|
const promoteId = document.getElementById('edit-promote-track-id')?.value || '(none)';
|
|
const order = Array.from(document.querySelectorAll('#edit-tc-list > .um-track-card')).map((c, i) => ({
|
|
pos: i + 1,
|
|
card: c.id,
|
|
trackId: c.dataset.trackId || 'primary',
|
|
title: c.querySelector('.um-tc-title')?.textContent?.trim() || ''
|
|
}));
|
|
const trackFields = {};
|
|
for (const [k, v] of formData.entries()) {
|
|
if (/^(promote_track_id|primary_language|title|description|track_|extra_track_|delete_track_ids)/.test(k)) {
|
|
trackFields[k] = (v instanceof File) ? `[File: ${v.name}]` : v;
|
|
}
|
|
}
|
|
console.groupCollapsed('%c[EditTrack] Submitting edit for video ' + window._editCurrentVideoId, 'color:#e61e1e;font-weight:700');
|
|
console.log('Primary language:', document.getElementById('edit_primary_language')?.value || '(none)');
|
|
console.log('Promote track id (secondary→primary):', promoteId);
|
|
console.table(order);
|
|
console.log('Delete track ids:', _editDeleteTrackIds);
|
|
console.log('Track form fields:', trackFields);
|
|
console.groupEnd();
|
|
})();
|
|
|
|
const btn = document.getElementById('edit-submit-btn');
|
|
const st = document.getElementById('edit-status-msg');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<i class="bi bi-arrow-repeat"></i> Saving…';
|
|
if (st) st.className = 'status-message-modal';
|
|
|
|
fetch(`/videos/${window._editCurrentVideoId}`, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Accept': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(r => { if (!r.ok) throw new Error('Server error'); return r.json(); })
|
|
.then(data => {
|
|
console.log('%c[EditTrack] Server response:', 'color:#22c55e;font-weight:700', data);
|
|
if (data.success) {
|
|
if (st) {
|
|
st.innerHTML = '<i class="bi bi-check-circle-fill"></i> ' + (data.message || 'Saved!');
|
|
st.className = 'status-message-modal success';
|
|
}
|
|
setTimeout(() => { closeEditVideoModal(); window.location.reload(); }, 1200);
|
|
} else {
|
|
throw new Error(data.message || 'Update failed');
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('%c[EditTrack] Save failed:', 'color:#e61e1e;font-weight:700', err);
|
|
if (st) {
|
|
st.innerHTML = '<i class="bi bi-exclamation-circle-fill"></i> ' + (err.message || 'Save failed');
|
|
st.className = 'status-message-modal error';
|
|
}
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="bi bi-check-lg"></i> <span>Save Changes</span>';
|
|
});
|
|
});
|
|
|
|
// Initialise
|
|
_editApplyMode('generic');
|
|
</script>
|
|
|
|
<x-image-cropper
|
|
id="thumb_edit"
|
|
:width="448"
|
|
:height="252"
|
|
shape="square"
|
|
target-input="edit-t1-thumbnail-input"
|
|
preview-img="edit-t1-thumbnail-preview"
|
|
output-width="1280"
|
|
title="Crop Thumbnail"
|
|
/>
|
|
|
|
<x-image-cropper
|
|
id="slides_edit"
|
|
:width="448"
|
|
:height="252"
|
|
shape="square"
|
|
output-width="1280"
|
|
title="Crop Cover Slide"
|
|
result-callback="editSlidesCropDone"
|
|
/>
|