WIP: storage-fix-local-nas work before playlist controls feature

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
ghassanandClaude Sonnet 4.6 committed 2026-05-16 11:15:20 +03:00
1 parent 6b3ab5b65e
commit c160242dbc
46 files changed
+5365 -815

No files matched your search

+105 -3
View File
@@ -179,7 +179,7 @@
<div class="form-group">
<label class="form-label"><i class="bi bi-image"></i> Current Thumbnail</label>
<div id="current-thumb-wrap" class="{{ $video->thumbnail ? 'has-thumb' : '' }}">
<img src="{{ $video->thumbnail ? asset('storage/thumbnails/'.$video->thumbnail) : '' }}" alt="Thumbnail">
<img src="{{ $video->thumbnail ? route('media.thumbnail', $video->thumbnail) : '' }}" alt="Thumbnail">
<div id="current-thumb-placeholder">
<i class="bi bi-card-image"></i>
<span>No thumbnail set</span>
@@ -305,8 +305,41 @@
</button>
</form>
{{-- Replace File --}}
<div class="danger-zone" style="margin-top:20px;">
<p style="color:#f87171;"><i class="bi bi-arrow-repeat" style="margin-right:6px;"></i>Replace Media File</p>
<p style="font-size:12px;color:#888;margin:-8px 0 14px;line-height:1.5;">
Fix a corrupted or missing file without losing any views, likes, or comments.
</p>
<div id="ep-rfl-dropzone" onclick="document.getElementById('ep-rfl-input').click()"
style="border:2px dashed #444;border-radius:10px;padding:18px;text-align:center;cursor:pointer;transition:border-color .15s,background .15s;margin-bottom:10px;"
onmouseenter="this.style.borderColor='#ef4444';this.style.background='rgba(239,68,68,.05)'"
onmouseleave="this.style.borderColor='#444';this.style.background='transparent'">
<i class="bi bi-cloud-upload" style="font-size:26px;color:#666;display:block;margin-bottom:6px;"></i>
<span id="ep-rfl-label" style="font-size:13px;color:#888;">Tap to choose replacement file</span>
<input type="file" id="ep-rfl-input" accept="video/*,audio/*,.mp4,.webm,.ogg,.mov,.avi,.wmv,.flv,.mkv,.mp3,.m4a,.aac,.wav,.flac,.opus" style="display:none;" onchange="epRflSelected(this)">
</div>
<div id="ep-rfl-info" style="display:none;background:#1a1a1a;border:1px solid #333;border-radius:8px;padding:10px 14px;align-items:center;gap:10px;margin-bottom:10px;">
<i class="bi bi-file-earmark-play" style="font-size:20px;color:#ef4444;flex-shrink:0;"></i>
<div style="flex:1;min-width:0;">
<div id="ep-rfl-name" style="font-size:13px;font-weight:600;color:#e5e5e5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></div>
<div id="ep-rfl-size" style="font-size:11px;color:#888;margin-top:2px;"></div>
</div>
<button type="button" onclick="epRflClear()" style="background:none;border:none;color:#888;cursor:pointer;font-size:16px;flex-shrink:0;padding:0;"><i class="bi bi-x-lg"></i></button>
</div>
<div id="ep-rfl-status" style="display:none;margin-bottom:10px;font-size:12px;padding:8px 12px;border-radius:6px;"></div>
<button type="button" id="ep-rfl-btn" onclick="epRflSubmit()" disabled
class="action-btn action-btn-danger" style="width:100%;justify-content:center;">
<i class="bi bi-arrow-repeat"></i> <span>Replace File</span>
</button>
</div>
{{-- Danger zone --}}
<div class="danger-zone">
<div class="danger-zone" style="margin-top:12px;">
<p>Danger Zone</p>
<form action="{{ route('videos.destroy', $video) }}" method="POST" id="delete-edit-form">
@csrf
@@ -380,7 +413,7 @@
}
@else
// ── Slides manager (audio tracks only) ────────────────────────
let epSlidesData = @json($video->slides->map(fn($s) => ['id' => $s->id, 'url' => asset('storage/thumbnails/'.$s->filename)])->values());
let epSlidesData = @json($video->slides->map(fn($s) => ['id' => $s->id, 'url' => route('media.thumbnail', $s->filename)])->values());
let epDragSrc = null;
function _epSyncOrder() {
@@ -536,6 +569,75 @@
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-check-lg"></i> <span>Save Changes</span>';
}
// ── Replace File ──────────────────────────────────────────────────────
let _epRflFile = null;
const _epRflFmtSize = b => b >= 1073741824 ? (b/1073741824).toFixed(1)+' GB' : b >= 1048576 ? (b/1048576).toFixed(1)+' MB' : Math.round(b/1024)+' KB';
function epRflSelected(input) {
_epRflFile = input.files[0] || null;
const info = document.getElementById('ep-rfl-info');
if (_epRflFile) {
document.getElementById('ep-rfl-name').textContent = _epRflFile.name;
document.getElementById('ep-rfl-size').textContent = _epRflFmtSize(_epRflFile.size);
document.getElementById('ep-rfl-label').textContent = _epRflFile.name;
info.style.display = 'flex';
} else {
info.style.display = 'none';
document.getElementById('ep-rfl-label').textContent = 'Tap to choose replacement file';
}
document.getElementById('ep-rfl-btn').disabled = !_epRflFile;
document.getElementById('ep-rfl-status').style.display = 'none';
}
function epRflClear() {
_epRflFile = null;
document.getElementById('ep-rfl-input').value = '';
document.getElementById('ep-rfl-info').style.display = 'none';
document.getElementById('ep-rfl-label').textContent = 'Tap to choose replacement file';
document.getElementById('ep-rfl-btn').disabled = true;
document.getElementById('ep-rfl-status').style.display = 'none';
}
function epRflSubmit() {
if (!_epRflFile) return;
const btn = document.getElementById('ep-rfl-btn');
const status = document.getElementById('ep-rfl-status');
btn.disabled = true;
btn.innerHTML = '<i class="bi bi-arrow-repeat"></i> Uploading…';
status.style.display = 'none';
const fd = new FormData();
fd.append('_token', '{{ csrf_token() }}');
fd.append('replacement_file', _epRflFile);
fetch('{{ route("videos.replaceFile", $video) }}', {
method: 'POST',
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
body: fd
})
.then(r => r.json())
.then(data => {
if (data.success) {
status.style.cssText = 'display:block;background:rgba(74,222,128,.1);border:1px solid rgba(74,222,128,.25);color:#4ade80;';
status.innerHTML = '<i class="bi bi-check-circle-fill"></i> ' + data.message;
epRflClear();
setTimeout(() => location.reload(), 2000);
} else {
status.style.cssText = 'display:block;background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.25);color:#f87171;';
status.textContent = data.message || 'Upload failed. Please try again.';
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-arrow-repeat"></i> <span>Replace File</span>';
}
})
.catch(() => {
status.style.cssText = 'display:block;background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.25);color:#f87171;';
status.textContent = 'Upload failed. Please try again.';
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-arrow-repeat"></i> <span>Replace File</span>';
});
}
</script>
<x-image-cropper
id="thumb_edit_mobile"