Introduce per-video language support and multiple audio tracks (VideoAudioTrack model + migrations for language, description, title), a reusable language-select component, and a track-editor form. Bundle the self-hosted flag-icons v7.2.3 library and a NAS auto-sync command. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1033 lines
53 KiB
PHP
1033 lines
53 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">
|
|
<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 icon-only edit-tc-arrow-up" onclick="editMoveTrack('t1-card','up')" title="Move up" style="display:none;">
|
|
<i class="bi bi-arrow-up"></i>
|
|
</button>
|
|
<button type="button" class="action-btn icon-only edit-tc-arrow-down" onclick="editMoveTrack('t1-card','down')" title="Move down" style="display:none;">
|
|
<i class="bi bi-arrow-down"></i>
|
|
</button>
|
|
<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; }
|
|
</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 || '';
|
|
_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
|
|
if (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 ────────────────────────────────────────────────────────────
|
|
function editMoveTrack(cardSuffix, dir) {
|
|
const card = document.getElementById('edit-tc-' + cardSuffix);
|
|
if (!card) return;
|
|
const list = document.getElementById('edit-tc-list');
|
|
const cards = Array.from(list.querySelectorAll(':scope > .um-track-card'));
|
|
const idx = cards.indexOf(card);
|
|
console.log('%c[EditTrack] Reorder:', 'color:#3b82f6', { card: card.id, trackId: card.dataset.trackId || 'primary', direction: dir, fromPos: idx + 1 });
|
|
if (dir === 'up' && idx > 0) list.insertBefore(card, cards[idx - 1]);
|
|
if (dir === 'down' && idx < cards.length - 1) list.insertBefore(cards[idx + 1], card);
|
|
_editUpdateTrackPositions();
|
|
}
|
|
|
|
function _editUpdateTrackPositions() {
|
|
const list = document.getElementById('edit-tc-list');
|
|
if (!list) return;
|
|
const cards = Array.from(list.querySelectorAll(':scope > .um-track-card'));
|
|
const total = cards.length;
|
|
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';
|
|
const up = card.querySelector('.edit-tc-arrow-up');
|
|
const down = card.querySelector('.edit-tc-arrow-down');
|
|
if (up) up.style.display = (i === 0) ? 'none' : '';
|
|
if (down) down.style.display = (i === total - 1) ? '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)');
|
|
}
|
|
}
|
|
|
|
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';
|
|
if (!fileList || !fileList.length) return;
|
|
if (!_editSlidesData[tid]) _editSlidesData[tid] = [];
|
|
for (const f of Array.from(fileList)) {
|
|
if (_editSlidesData[tid].length >= 10) break;
|
|
const reader = new FileReader();
|
|
reader.onload = ev => { _editSlidesData[tid].push({ file: f, url: ev.target.result }); _editRenderSlides(tid); };
|
|
reader.readAsDataURL(f);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (!fileList || !fileList.length) return;
|
|
if (!_editSlidesData[tid]) _editSlidesData[tid] = [];
|
|
for (const f of Array.from(fileList)) {
|
|
if (_editSlidesData[tid].length >= 10) break;
|
|
const reader = new FileReader();
|
|
reader.onload = ev => { _editSlidesData[tid].push({ file: f, url: ev.target.result }); _editRenderSlidesForTrack(tid); };
|
|
reader.readAsDataURL(f);
|
|
}
|
|
}
|
|
|
|
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">
|
|
<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 icon-only edit-tc-arrow-up" onclick="editMoveTrack('e${n}','up')" title="Move up" style="display:none;"><i class="bi bi-arrow-up"></i></button>
|
|
<button type="button" class="action-btn icon-only edit-tc-arrow-down" onclick="editMoveTrack('e${n}','down')" title="Move down" style="display:none;"><i class="bi bi-arrow-down"></i></button>
|
|
<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 || '';
|
|
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));
|
|
|
|
// ── 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"
|
|
/>
|