Unify upload/edit modal track-form + fix slide cropper hang & reorder loss
Track-form unification: the "Add Language Track" dialog in the edit modal now
clones the same #um-track-form-template partial that the create modal uses, so
future tweaks to markup/CSS/behaviour show up in both flows automatically.
Added window.cloneTrackFormTemplate() + window.initTrackFormNode() as the
shared cloner+init. updateTrackCard() writes to both um-tc-*-{id} and
edit-tc-*-{id} ids so one helper serves both modals. Dropped the dead
#edit-extra-track-tpl and the custom editHandleTrackAudio (handleTrackAudio
auto-fills title identically).
Cover slides cropper fixes (both modals):
- Second batch no longer hangs. _slidesCropLoadNext now calls
openCropperModal_* BEFORE tcPreload_*; previously preloading first measured
canvas.clientWidth=0 against a display:none overlay and left _st.ready
false so Save silently returned.
- Reorder no longer loses thumbnails. Create flow stored raw Files and
spawned a FileReader per slide per render; under memory pressure some
readers failed silently. Now stores {file, url} with URL.createObjectURL
called once per slide, and renderSlidesStrip assigns img.src = url
directly. Removal revokes the URL; clear revokes everything it drops.
- Reset .value on the slides input after processing so re-selecting the
same filename still fires change.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
2d33e39b9b
commit
468820c9fe
3 files changed
+243
-196
No files matched your search
@@ -178,22 +178,9 @@
|
||||
</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>
|
||||
{{-- Secondary tracks in this modal are cloned from the SHARED
|
||||
#um-track-form-template defined in layouts.partials.upload-modal. One
|
||||
template → identical UX in both the create (upload) modal and this modal. --}}
|
||||
|
||||
<style>
|
||||
#editVideoModal .modal-dialog { opacity: 0; transition: opacity .25s ease; }
|
||||
@@ -720,6 +707,8 @@ function editSlidesZoneDrop(e, tid) { tid=tid||'t1'; e.preventDefault(); doc
|
||||
function editHandleSlides(fileList, tid) {
|
||||
tid = tid || 't1';
|
||||
_editSlidesCropStart(fileList, tid, _editRenderSlides);
|
||||
const inp = document.getElementById('edit-' + tid + '-slides-input');
|
||||
if (inp) inp.value = '';
|
||||
}
|
||||
|
||||
// ── Slides crop queue: every added image is cropped before it enters the strip ──
|
||||
@@ -740,8 +729,12 @@ 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);
|
||||
// Open modal first so the cropper canvas has a real layout box when
|
||||
// initCropper measures. Preloading first (prior order) caused the
|
||||
// second batch to open with stale zero-sized measurements and a Save
|
||||
// button that silently did nothing.
|
||||
window.openCropperModal_slides_edit();
|
||||
requestAnimationFrame(() => window.tcPreload_slides_edit(f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,6 +820,8 @@ function editSlidesZoneDropE(e, tid) { e.preventDefault(); document.getEleme
|
||||
|
||||
function editHandleSlidesForTrack(tid, fileList) {
|
||||
_editSlidesCropStart(fileList, tid, _editRenderSlidesForTrack);
|
||||
const inp = document.getElementById('edit-slides-input-' + tid);
|
||||
if (inp) inp.value = '';
|
||||
}
|
||||
|
||||
function editClearSlidesForTrack(e, tid) {
|
||||
@@ -885,7 +880,9 @@ function _editRenderSlidesForTrack(tid) {
|
||||
// ── 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);
|
||||
// Primary track form uses the legacy id (edit-tf-t1); extra tracks are
|
||||
// cloned from the SHARED #um-track-form-template and use id um-tf-{id}.
|
||||
const form = document.getElementById('edit-tf-' + trackId) || document.getElementById('um-tf-' + trackId);
|
||||
if (form) form.style.display = 'block';
|
||||
const popup = document.getElementById('edit-track-popup');
|
||||
popup.style.opacity = '0';
|
||||
@@ -902,6 +899,10 @@ function editCloseTrackPopup() {
|
||||
}
|
||||
}
|
||||
|
||||
// For t1 the inputs are the primary-track Blade ids. For extra tracks (both
|
||||
// newly-added and existing-track rows) the inputs come from the SHARED
|
||||
// #um-track-form-template, so they use the shared ids track-lang-{id} /
|
||||
// track-title-{id}. One function reads whichever is present.
|
||||
function editUpdateTrackCard(trackId) {
|
||||
let langCode = '', title = '';
|
||||
if (trackId === 't1') {
|
||||
@@ -910,9 +911,8 @@ function editUpdateTrackCard(trackId) {
|
||||
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');
|
||||
const li = document.getElementById('track-lang-' + trackId);
|
||||
const ti = document.getElementById('track-title-' + trackId);
|
||||
langCode = li ? li.value : '';
|
||||
title = ti ? ti.value.trim() : '';
|
||||
}
|
||||
@@ -932,24 +932,26 @@ function editUpdateTrackCard(trackId) {
|
||||
// ── Extra track management ────────────────────────────────────────────────────
|
||||
const LANG_OPTIONS_EDIT_MODAL = @json(\App\Data\Languages::forLanguage());
|
||||
|
||||
// Clone the SHARED upload-modal track-form partial into the edit popup. Both
|
||||
// "add new track" and "load existing track" go through here, so there is one
|
||||
// source of truth for the form markup. The caller picks the submission names:
|
||||
// - new track → extra_track_*[]
|
||||
// - existing track → track_*_updates[trackId]
|
||||
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;
|
||||
const frag = window.cloneTrackFormTemplate({
|
||||
key: prefix, langName, titleName, descName, audioName,
|
||||
});
|
||||
if (!frag) return null;
|
||||
const host = document.getElementById('edit-tf-extra');
|
||||
host.appendChild(frag);
|
||||
// _slidesData is the shared state used by the shared slides-zone handlers.
|
||||
// Mirror the handle under _editSlidesData so the edit submit code stays
|
||||
// oblivious to the migration (one array, two aliases).
|
||||
window._slidesData = window._slidesData || {};
|
||||
_slidesData[prefix] = [];
|
||||
_editSlidesData[prefix] = _slidesData[prefix];
|
||||
window.initTrackFormNode(prefix, pfx => editUpdateTrackCard(pfx));
|
||||
return document.getElementById('um-tf-' + prefix);
|
||||
}
|
||||
|
||||
function _editTrackCard(n, trackId, isExisting) {
|
||||
@@ -990,34 +992,47 @@ function _editAddExistingTrack(track) {
|
||||
'track_description_updates[' + track.id + ']',
|
||||
'track_file_updates[' + track.id + ']'
|
||||
);
|
||||
const titleEl = document.getElementById('edit-' + pfx + '-title');
|
||||
// Shared partial field ids: track-title-{pfx} / track-desc-{pfx} / track-lang-{pfx}
|
||||
const titleEl = document.getElementById('track-title-' + pfx);
|
||||
if (titleEl) titleEl.value = track.title || '';
|
||||
const descEl = document.getElementById('edit-' + pfx + '-desc');
|
||||
const descEl = document.getElementById('track-desc-' + pfx);
|
||||
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);
|
||||
const hi = document.getElementById('track-lang-' + pfx);
|
||||
const wrap = document.getElementById('lsd_track_' + 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();
|
||||
function _editRemoveTrackForms(n) {
|
||||
const pfx = 'e' + n;
|
||||
document.getElementById('edit-tc-' + pfx)?.remove();
|
||||
// Form id depends on which cloner built it: shared template → um-tf-{pfx},
|
||||
// legacy edit template → edit-tf-{pfx}. Clean up whichever exists.
|
||||
document.getElementById('um-tf-' + pfx)?.remove();
|
||||
document.getElementById('edit-tf-' + pfx)?.remove();
|
||||
delete _editSlidesData[pfx];
|
||||
delete (window._slidesData || {})[pfx];
|
||||
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';
|
||||
if (!vis || vis.id === 'edit-tf-' + pfx || vis.id === 'um-tf-' + pfx) popup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function editDeleteTrack(trackId, n) {
|
||||
_editDeleteTrackIds.push(trackId);
|
||||
_editRemoveTrackForms(n);
|
||||
_editUpdateTrackPositions();
|
||||
}
|
||||
|
||||
function editRemoveNewTrack(n) {
|
||||
_editRemoveTrackForms(n);
|
||||
}
|
||||
|
||||
function editAddExtraTrack() {
|
||||
const n = ++_editExtraCount;
|
||||
const pfx = 'e' + n;
|
||||
@@ -1029,23 +1044,10 @@ function editAddExtraTrack() {
|
||||
'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');
|
||||
|
||||
Reference in new issue
Block a user