Drag & drop a video here
or click to select a file
@@ -636,53 +648,87 @@
const sectionYoutube = document.getElementById('section-youtube');
const sectionVideo = document.getElementById('section-video');
- const stepSelect = document.getElementById('step-select');
+ const stepType = document.getElementById('step-type');
+ const stepVideoSource = document.getElementById('step-video-source');
+ const stepInput = document.getElementById('step-input');
const stepCrop = document.getElementById('step-crop');
const backBtn = document.getElementById('add-item-back');
const errorEl = document.getElementById('add-item-error');
+ const stepInputHint = document.getElementById('step-input-hint');
function setError(msg) {
if (!errorEl) return;
errorEl.textContent = (msg || '').trim();
}
+ let currentStep = 'type';
+
+ function updatePrimaryButton() {
+ // Primary button acts as Next in early steps, and Add in the final steps.
+ if (!submitBtn) return;
+ const isNextStep = (currentStep === 'type' || currentStep === 'video-source');
+ submitBtn.textContent = isNextStep ? 'Next' : 'Add';
+ }
+
function showStep(which) {
- stepSelect?.classList.toggle('active', which === 'select');
+ currentStep = which;
+ stepType?.classList.toggle('active', which === 'type');
+ stepVideoSource?.classList.toggle('active', which === 'video-source');
+ stepInput?.classList.toggle('active', which === 'input');
stepCrop?.classList.toggle('active', which === 'crop');
- const isCrop = which === 'crop';
- backBtn.disabled = !isCrop;
+ // Back is enabled for all steps except the first.
+ backBtn.disabled = (which === 'type');
- // For image: allow Add only in crop step (so we always crop if image)
- if (typeHidden.value === 'image') {
- submitBtn.disabled = !isCrop;
+ // Enable Next for the initial steps.
+ if (which === 'type' || which === 'video-source') {
+ submitBtn.disabled = false;
+ updatePrimaryButton();
+ return;
}
+
+ // For input/crop steps: image requires crop step before enabling Add.
+ if (typeHidden.value === 'image') {
+ submitBtn.disabled = (which !== 'crop');
+ } else {
+ submitBtn.disabled = false;
+ }
+
+ updatePrimaryButton();
+ }
+
+ function videoSource() {
+ return document.getElementById('video-source-youtube')?.checked ? 'youtube' : 'upload';
}
function setType(t) {
+ // For this UI, "video" is a top-level type, but it can map to item_type=video OR item_type=youtube.
typeHidden.value = t;
setError('');
- sectionImage.classList.toggle('d-none', t !== 'image');
- sectionWebpage.classList.toggle('d-none', t !== 'webpage');
- sectionYoutube.classList.toggle('d-none', t !== 'youtube');
- sectionVideo.classList.toggle('d-none', t !== 'video');
- // duration applies to image/webpage/youtube. Video plays until ended.
- durationGroup.classList.toggle('d-none', t === 'video');
- cropModeGroup?.classList.toggle('d-none', t !== 'image');
- submitBtn.disabled = false;
- submitBtn.title = '';
- if (t !== 'image') {
- destroyCropper();
- showStep('select');
- backBtn.disabled = true;
+ // Visible section is decided by type + (video source)
+ const vs = (t === 'video') ? videoSource() : null;
+ const effectiveType = (t === 'video' && vs === 'youtube') ? 'youtube' : t;
+ typeHidden.value = effectiveType;
+
+ sectionImage.classList.toggle('d-none', effectiveType !== 'image');
+ sectionWebpage.classList.toggle('d-none', effectiveType !== 'webpage');
+ sectionYoutube.classList.toggle('d-none', effectiveType !== 'youtube');
+ sectionVideo.classList.toggle('d-none', effectiveType !== 'video');
+
+ // duration applies to image/webpage/youtube. Video upload plays until ended.
+ durationGroup.classList.toggle('d-none', effectiveType === 'video');
+ cropModeGroup?.classList.toggle('d-none', effectiveType !== 'image');
+
+ if (stepInputHint) {
+ if (effectiveType === 'image') stepInputHint.textContent = 'Select an image. After selecting, you\'ll crop it.';
+ else if (effectiveType === 'webpage') stepInputHint.textContent = 'Enter a webpage URL.';
+ else if (effectiveType === 'youtube') stepInputHint.textContent = 'Paste a YouTube URL.';
+ else stepInputHint.textContent = 'Upload a video file.';
}
- // For images we enforce crop step before allowing submit.
- if (t === 'image') {
- submitBtn.disabled = true;
- backBtn.disabled = true;
- }
+ // Reset cropper when leaving image.
+ if (effectiveType !== 'image') destroyCropper();
}
function currentCropMode() {
@@ -705,10 +751,39 @@
}
}
- document.getElementById('type-image')?.addEventListener('change', () => setType('image'));
- document.getElementById('type-webpage')?.addEventListener('change', () => setType('webpage'));
- document.getElementById('type-youtube')?.addEventListener('change', () => setType('youtube'));
- document.getElementById('type-video')?.addEventListener('change', () => setType('video'));
+ document.getElementById('type-image')?.addEventListener('change', () => {
+ setType('image');
+ // Stay on type step; user clicks Next.
+ showStep('type');
+ });
+ document.getElementById('type-webpage')?.addEventListener('change', () => {
+ setType('webpage');
+ // Stay on type step; user clicks Next.
+ showStep('type');
+ });
+ document.getElementById('type-video')?.addEventListener('change', () => {
+ // We show an intermediate step for video so user chooses upload vs YouTube.
+ // Keep item_type unset until that choice is made.
+ setError('');
+ destroyCropper();
+ // Hide crop/duration while selecting source (they depend on source).
+ cropModeGroup?.classList.add('d-none');
+ durationGroup?.classList.add('d-none');
+ showStep('video-source');
+ });
+
+ document.getElementById('video-source-upload')?.addEventListener('change', () => {
+ // effective type becomes "video"
+ setType('video');
+ // Stay on source step; user clicks Next.
+ showStep('video-source');
+ });
+ document.getElementById('video-source-youtube')?.addEventListener('change', () => {
+ // effective type becomes "youtube"
+ setType('video');
+ // Stay on source step; user clicks Next.
+ showStep('video-source');
+ });
// -------------------------
// Image: drag/drop + crop
@@ -754,7 +829,7 @@
cropStatus.textContent = '';
if (imageSelectStatus) imageSelectStatus.textContent = `Selected: ${file.name}`;
- // Move to crop step after image selection (requested behavior)
+ // Move to crop step after image selection
showStep('crop');
// Wait for image to be ready
@@ -991,15 +1066,7 @@
}
// Reset modal state + close
- form.reset();
- typeHidden.value = 'image';
- document.getElementById('type-image')?.click();
- if (cropModeHidden) cropModeHidden.value = '16:9';
- document.getElementById('crop-16-9')?.click();
- destroyCropper();
- showStep('select');
- submitBtn.disabled = true;
- resetVideoProgress();
+ resetModalState();
modal?.hide();
}
@@ -1098,32 +1165,87 @@
if (t === 'webpage') {
// Keep preview behavior
schedulePreview();
- } else {
- // Hide webpage preview if not active
- preview?.classList.add('d-none');
- if (iframe) iframe.src = 'about:blank';
- if (openLink) openLink.href = '#';
+ return;
}
+
+ // Hide webpage preview if not active
+ preview?.classList.add('d-none');
+ if (iframe) iframe.src = 'about:blank';
+ if (openLink) openLink.href = '#';
}
- // Set initial state
- setType('image');
- if (cropModeHidden) cropModeHidden.value = '16:9';
- showStep('select');
- syncEnabledInputs();
- updateCropHint();
+ function resetModalState() {
+ setError('');
+ try { form.reset(); } catch (e) {}
+ destroyCropper();
+
+ // Default selections (without triggering change handlers)
+ const typeImage = document.getElementById('type-image');
+ const typeWebpage = document.getElementById('type-webpage');
+ const typeVideo = document.getElementById('type-video');
+ if (typeImage) typeImage.checked = true;
+ if (typeWebpage) typeWebpage.checked = false;
+ if (typeVideo) typeVideo.checked = false;
+
+ const vsUpload = document.getElementById('video-source-upload');
+ const vsYoutube = document.getElementById('video-source-youtube');
+ if (vsUpload) vsUpload.checked = true;
+ if (vsYoutube) vsYoutube.checked = false;
+
+ if (cropModeHidden) cropModeHidden.value = '16:9';
+ document.getElementById('crop-16-9')?.click();
+
+ // Set UI for default type, but start at type selection step
+ setType('image');
+ showStep('type');
+ syncEnabledInputs();
+ updateCropHint();
+
+ // Also reset video upload progress UI if present
+ try {
+ const bar = document.getElementById('video-upload-progress-bar');
+ if (bar) {
+ bar.style.width = '0%';
+ bar.setAttribute('aria-valuenow', '0');
+ }
+ document.getElementById('video-upload-progress-text') && (document.getElementById('video-upload-progress-text').textContent = 'Uploading…');
+ document.getElementById('video-upload-progress')?.classList.add('d-none');
+ } catch (e) {}
+ }
+
+ // Initialize modal state once on page load
+ resetModalState();
// Modal open
openBtn?.addEventListener('click', () => {
+ // Always start from the beginning.
+ resetModalState();
modal?.show();
});
- // Back button: only relevant for image crop step
+ // Back button: stepwise navigation
backBtn?.addEventListener('click', () => {
- if (typeHidden.value === 'image') {
- showStep('select');
+ if (currentStep === 'crop') {
+ // Going back from crop returns to input for image
+ showStep('input');
submitBtn.disabled = true;
- backBtn.disabled = true;
+ return;
+ }
+
+ if (currentStep === 'input') {
+ // If top-level selected is video, go back to video source selection.
+ const isTopLevelVideo = document.getElementById('type-video')?.checked;
+ if (isTopLevelVideo) {
+ showStep('video-source');
+ } else {
+ showStep('type');
+ }
+ return;
+ }
+
+ if (currentStep === 'video-source') {
+ showStep('type');
+ return;
}
});
@@ -1165,13 +1287,43 @@
document.getElementById('crop-none')?.addEventListener('change', () => setCropMode('none'));
// Whenever type changes, keep enabled inputs in sync
- ['type-image','type-webpage','type-youtube','type-video'].forEach((id) => {
+ ['type-image','type-webpage','type-video','video-source-upload','video-source-youtube'].forEach((id) => {
document.getElementById(id)?.addEventListener('change', syncEnabledInputs);
});
// Add button
submitBtn?.addEventListener('click', async () => {
try {
+ // Multi-step behavior:
+ // - Type step: Next -> (video ? source step : input step)
+ // - Video source step: Next -> input step
+ // - Input/crop: Add -> submit
+ if (currentStep === 'type') {
+ const isVideo = document.getElementById('type-video')?.checked;
+ const isWebpage = document.getElementById('type-webpage')?.checked;
+
+ if (isVideo) {
+ // Hide crop/duration while selecting source.
+ cropModeGroup?.classList.add('d-none');
+ durationGroup?.classList.add('d-none');
+ showStep('video-source');
+ return;
+ }
+
+ setType(isWebpage ? 'webpage' : 'image');
+ showStep('input');
+ syncEnabledInputs();
+ return;
+ }
+
+ if (currentStep === 'video-source') {
+ // Apply the chosen source (upload vs YouTube) and continue.
+ setType('video');
+ showStep('input');
+ syncEnabledInputs();
+ return;
+ }
+
await submitViaAjax();
} catch (err) {
console.warn(err);