b24f0dc416
Changed: [includes/db.php (line 43)](/Users/tyemeclifford/Documents/GH/mediaplayer/includes/db.php:43) adds source_type and source_url, with an automatic migration for existing SQLite databases. [admin/add.php (line 260)](/Users/tyemeclifford/Documents/GH/mediaplayer/admin/add.php:260) and [admin/edit.php (line 383)](/Users/tyemeclifford/Documents/GH/mediaplayer/admin/edit.php:383) now let each source be an upload or an external URL. [index.php (line 226)](/Users/tyemeclifford/Documents/GH/mediaplayer/index.php:226) and [embed.php (line 193)](/Users/tyemeclifford/Documents/GH/mediaplayer/embed.php:193) resolve playback URLs based on where the source lives. [catalogue.php (line 79)](/Users/tyemeclifford/Documents/GH/mediaplayer/catalogue.php:79) now has public search for published videos only. [README.md (line 63)](/Users/tyemeclifford/Documents/GH/mediaplayer/README.md:63) documents local vs remote sources and public search.
492 lines
26 KiB
PHP
492 lines
26 KiB
PHP
<?php
|
|
require_once __DIR__ . '/auth.php';
|
|
require_once __DIR__ . '/../includes/layout.php';
|
|
admin_require_auth();
|
|
|
|
$db = get_db();
|
|
$id = (int)($_GET['id'] ?? 0);
|
|
$video = get_video_by_id($id);
|
|
|
|
if (!$video) {
|
|
header('Location: index.php');
|
|
exit;
|
|
}
|
|
|
|
$error = '';
|
|
$msg = '';
|
|
|
|
if (isset($_GET['saved'])) $msg = 'Video saved successfully.';
|
|
|
|
// Handle delete source
|
|
if (isset($_GET['del_src'])) {
|
|
$sid = (int)$_GET['del_src'];
|
|
$src = $db->prepare("SELECT * FROM video_sources WHERE id=? AND video_id=?");
|
|
$src->execute([$sid, $id]);
|
|
$s = $src->fetch();
|
|
if ($s) {
|
|
if (source_is_local($s)) {
|
|
$f = MEDIA_DIR . $s['file_path'];
|
|
if ($s['file_path'] && file_exists($f)) unlink($f);
|
|
}
|
|
$db->prepare("DELETE FROM video_sources WHERE id=?")->execute([$sid]);
|
|
$msg = 'Source removed.';
|
|
}
|
|
$video = get_video_by_id($id); // refresh
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = $_POST['action'] ?? 'save';
|
|
|
|
if ($action === 'save') {
|
|
$title = trim($_POST['title'] ?? '');
|
|
$description = trim($_POST['description'] ?? '');
|
|
$duration = (int)($_POST['duration'] ?? 0);
|
|
$sort_order = (int)($_POST['sort_order'] ?? 0);
|
|
$published = isset($_POST['published']) ? 1 : 0;
|
|
|
|
if (!$title) {
|
|
$error = 'Title is required.';
|
|
} else {
|
|
$slug = make_slug($title, $id);
|
|
|
|
// Handle thumbnail replacement
|
|
$thumbnail = $video['thumbnail'];
|
|
if (!empty($_FILES['thumbnail']['tmp_name'])) {
|
|
$ext = strtolower(pathinfo($_FILES['thumbnail']['name'], PATHINFO_EXTENSION));
|
|
$allowed = ['jpg','jpeg','png','webp','gif'];
|
|
if (in_array($ext, $allowed)) {
|
|
$thumb_dir = MEDIA_DIR . 'thumbs/';
|
|
if (!is_dir($thumb_dir)) mkdir($thumb_dir, 0755, true);
|
|
// Remove old thumb
|
|
if ($thumbnail && file_exists(MEDIA_DIR . $thumbnail)) unlink(MEDIA_DIR . $thumbnail);
|
|
$fname = $slug . '_thumb_' . time() . '.' . $ext;
|
|
if (move_uploaded_file($_FILES['thumbnail']['tmp_name'], $thumb_dir . $fname)) {
|
|
$thumbnail = 'thumbs/' . $fname;
|
|
}
|
|
} else {
|
|
$error = 'Thumbnail must be JPG, PNG, WebP, or GIF.';
|
|
}
|
|
}
|
|
|
|
if (!$error) {
|
|
$db->prepare("
|
|
UPDATE videos SET title=?, description=?, slug=?, thumbnail=?,
|
|
duration=?, sort_order=?, published=?,
|
|
updated_at=datetime('now')
|
|
WHERE id=?
|
|
")->execute([$title, $description, $slug, $thumbnail, $duration, $sort_order, $published, $id]);
|
|
|
|
// Update existing sources sort/label/quality
|
|
$upd_labels = $_POST['src_label'] ?? [];
|
|
$upd_quality = $_POST['src_quality'] ?? [];
|
|
$upd_sort = $_POST['src_sort'] ?? [];
|
|
$upd_urls = $_POST['src_url'] ?? [];
|
|
$upd_types = $_POST['src_type'] ?? [];
|
|
$upd_ids = $_POST['src_id'] ?? [];
|
|
foreach ($upd_ids as $i => $sid) {
|
|
$source_type = ($upd_types[$i] ?? 'local') === 'remote' ? 'remote' : 'local';
|
|
$source_url = '';
|
|
if ($source_type === 'remote') {
|
|
$source_url = normalize_media_url($upd_urls[$i] ?? '');
|
|
if ($source_url === '') {
|
|
$error = 'External video URLs must start with http:// or https://.';
|
|
break;
|
|
}
|
|
}
|
|
|
|
$params = [
|
|
$upd_labels[$i] ?? '',
|
|
$upd_quality[$i] ?? '',
|
|
(int)($upd_sort[$i] ?? $i),
|
|
];
|
|
$sql = "UPDATE video_sources SET label=?, quality=?, sort_order=?";
|
|
if ($source_type === 'remote') {
|
|
$sql .= ", source_url=?, mime_type=?";
|
|
$params[] = $source_url;
|
|
$params[] = mime_from_ext($source_url);
|
|
}
|
|
$sql .= " WHERE id=? AND video_id=?";
|
|
$params[] = (int)$sid;
|
|
$params[] = $id;
|
|
|
|
$db->prepare($sql)->execute($params);
|
|
}
|
|
|
|
// Add new sources
|
|
if (!$error) {
|
|
$new_types = $_POST['new_source_type'] ?? [];
|
|
$new_labels = $_POST['new_source_label'] ?? [];
|
|
$new_quality = $_POST['new_source_quality'] ?? [];
|
|
$new_sort = $_POST['new_source_sort'] ?? [];
|
|
$new_urls = $_POST['new_source_url'] ?? [];
|
|
$new_files = $_FILES['new_source_file'] ?? [];
|
|
$videos_dir = MEDIA_DIR . 'videos/';
|
|
if (!is_dir($videos_dir)) mkdir($videos_dir, 0755, true);
|
|
$allowed_video = ['mp4','webm','ogv','ogg','mov','mkv','avi'];
|
|
|
|
$new_count = max(
|
|
count($new_types),
|
|
count($new_labels),
|
|
count($new_urls),
|
|
isset($new_files['tmp_name']) && is_array($new_files['tmp_name']) ? count($new_files['tmp_name']) : 0
|
|
);
|
|
|
|
$moved_files = [];
|
|
$inserted_source_ids = [];
|
|
for ($i = 0; $i < $new_count; $i++) {
|
|
$type = ($new_types[$i] ?? 'local') === 'remote' ? 'remote' : 'local';
|
|
$label = trim($new_labels[$i] ?? '');
|
|
$quality = trim($new_quality[$i] ?? '720p');
|
|
$sort = (int)($new_sort[$i] ?? $i);
|
|
|
|
if ($type === 'remote') {
|
|
$url = normalize_media_url($new_urls[$i] ?? '');
|
|
if ($url === '') {
|
|
if (trim($new_urls[$i] ?? '') !== '') $error = 'External video URLs must start with http:// or https://.';
|
|
continue;
|
|
}
|
|
|
|
$db->prepare("
|
|
INSERT INTO video_sources (video_id, label, source_type, file_path, source_url, mime_type, quality, sort_order)
|
|
VALUES (?,?,?,?,?,?,?,?)
|
|
")->execute([$id, $label, 'remote', '', $url, mime_from_ext($url), $quality, $sort]);
|
|
$inserted_source_ids[] = (int)$db->lastInsertId();
|
|
continue;
|
|
}
|
|
|
|
$tmp = $new_files['tmp_name'][$i] ?? '';
|
|
if (empty($tmp) || !is_uploaded_file($tmp)) continue;
|
|
$orig = $new_files['name'][$i] ?? '';
|
|
$ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
|
|
if (!in_array($ext, $allowed_video, true)) continue;
|
|
$fname = $slug . '_' . time() . '_' . $i . '.' . $ext;
|
|
if (move_uploaded_file($tmp, $videos_dir . $fname)) {
|
|
$moved_files[] = $videos_dir . $fname;
|
|
$db->prepare("
|
|
INSERT INTO video_sources (video_id, label, source_type, file_path, source_url, mime_type, quality, sort_order)
|
|
VALUES (?,?,?,?,?,?,?,?)
|
|
")->execute([$id, $label, 'local', 'videos/' . $fname, '', mime_from_ext($fname), $quality, $sort]);
|
|
$inserted_source_ids[] = (int)$db->lastInsertId();
|
|
}
|
|
}
|
|
|
|
if ($error) {
|
|
foreach ($inserted_source_ids as $inserted_id) {
|
|
$db->prepare("DELETE FROM video_sources WHERE id=? AND video_id=?")->execute([$inserted_id, $id]);
|
|
}
|
|
foreach ($moved_files as $moved) {
|
|
if (file_exists($moved)) unlink($moved);
|
|
}
|
|
} else {
|
|
header('Location: edit.php?id=' . $id . '&saved=1');
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Refresh
|
|
$video = get_video_by_id($id);
|
|
|
|
render_head('Edit Video — Admin', '
|
|
.form-grid { display:grid; grid-template-columns:1fr; gap:1.2rem; }
|
|
@media(min-width:700px) { .form-grid { grid-template-columns:1fr 1fr; } }
|
|
.form-grid .span2 { grid-column:1/-1; }
|
|
.section-heading { font-family:"Share Tech Mono",monospace; font-size:.65rem; letter-spacing:.15em; text-transform:uppercase; color:var(--text-3); margin-bottom:.75rem; }
|
|
.hint { font-size:.72rem; color:var(--text-3); margin-top:.2rem; }
|
|
|
|
.source-list { display:flex; flex-direction:column; gap:.75rem; }
|
|
.source-item {
|
|
background:var(--surface-2); border:1px solid var(--border);
|
|
border-radius:var(--r); padding:.85rem 1rem;
|
|
}
|
|
.source-item-header { display:flex; align-items:center; justify-content:space-between; gap:.75rem; margin-bottom:.75rem; flex-wrap:wrap; }
|
|
.source-item-file { font-family:"Share Tech Mono",monospace; font-size:.68rem; color:var(--text-2); word-break:break-all; }
|
|
.source-item-mime { font-family:"Share Tech Mono",monospace; font-size:.6rem; color:var(--text-3); }
|
|
.source-item-fields { display:grid; grid-template-columns:1.5fr 1fr 1fr 80px; gap:.6rem; }
|
|
.source-item-fields .span4 { grid-column:1/-1; }
|
|
@media(max-width:760px) { .source-item-fields { grid-template-columns:1fr 1fr; } }
|
|
@media(max-width:560px) { .source-item-fields { grid-template-columns:1fr; } }
|
|
.source-badge { display:inline-flex; align-items:center; padding:.16em .5em; border-radius:99px; font-family:"Share Tech Mono",monospace; font-size:.56rem; letter-spacing:.08em; text-transform:uppercase; border:1px solid var(--border-2); color:var(--text-3); margin-bottom:.25rem; }
|
|
.source-badge-remote { color:var(--blue); border-color:rgba(0,200,255,.35); background:rgba(0,200,255,.08); }
|
|
.source-badge-local { color:var(--green); border-color:rgba(0,232,122,.3); background:rgba(0,232,122,.08); }
|
|
|
|
.add-source-row { background:var(--surface-2); border:1px solid var(--border-2); border-radius:var(--r); padding:1rem; display:grid; grid-template-columns:140px minmax(220px,1.5fr) 1fr 1fr 80px auto; gap:.75rem; align-items:end; }
|
|
@media(max-width:900px) { .add-source-row { grid-template-columns:1fr 1fr; } }
|
|
@media(max-width:560px) { .add-source-row { grid-template-columns:1fr; } }
|
|
.source-location-panel[hidden] { display:none; }
|
|
|
|
.admin-layout { display:grid; grid-template-columns:1fr; gap:1.5rem; }
|
|
@media(min-width:900px) { .admin-layout { grid-template-columns:220px 1fr; } }
|
|
.admin-nav { background:var(--surface); border:1px solid var(--border); border-radius:calc(var(--r)+2px); padding:1.2rem; display:flex; flex-direction:column; gap:.4rem; align-self:start; position:sticky; top:1rem; }
|
|
.admin-nav-label { font-family:"Share Tech Mono",monospace; font-size:.58rem; letter-spacing:.18em; text-transform:uppercase; color:var(--text-3); margin-bottom:.3rem; padding-left:.5rem; }
|
|
.admin-nav a { display:flex; align-items:center; gap:.6rem; padding:.55rem .75rem; border-radius:var(--r); font-size:.82rem; color:var(--text-2); text-decoration:none; transition:background .15s,color .15s; }
|
|
.admin-nav a:hover { background:rgba(255,255,255,.05); color:var(--text); }
|
|
.nav-divider { border:none; border-top:1px solid var(--border); margin:.4rem 0; }
|
|
|
|
.thumb-preview { width:100%; max-width:240px; aspect-ratio:16/9; object-fit:cover; border-radius:var(--r); border:1px solid var(--border); background:var(--surface-2); display:block; margin-bottom:.5rem; }
|
|
');
|
|
?>
|
|
<main class="page" role="main">
|
|
<header class="topbar">
|
|
<div class="topbar-brand">
|
|
<div>
|
|
<div class="brand-name">Ty Clifford</div>
|
|
<div class="brand-sub">admin / edit video</div>
|
|
</div>
|
|
</div>
|
|
<nav class="topbar-social">
|
|
<a class="social-pill" href="../index.php?v=<?= urlencode($video['slug']) ?>" target="_blank">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15,3 21,3 21,9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
|
Preview
|
|
</a>
|
|
<a class="social-pill" href="logout.php">Logout</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<?php if ($msg): ?>
|
|
<div class="notice notice-success"><?= h($msg) ?></div>
|
|
<?php endif; ?>
|
|
<?php if ($error): ?>
|
|
<div class="notice notice-error"><?= h($error) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="admin-layout">
|
|
<aside class="admin-nav">
|
|
<div class="admin-nav-label">Navigation</div>
|
|
<a href="index.php">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
Videos
|
|
</a>
|
|
<a href="add.php">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
|
|
Add Video
|
|
</a>
|
|
<hr class="nav-divider">
|
|
<a href="settings.php">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93l-1.41 1.41M4.93 4.93l1.41 1.41M19.07 19.07l-1.41-1.41M4.93 19.07l1.41-1.41M12 2v2M12 20v2M2 12h2M20 12h2"/></svg>
|
|
Settings
|
|
</a>
|
|
</aside>
|
|
|
|
<div>
|
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem;flex-wrap:wrap;gap:.75rem;">
|
|
<div>
|
|
<p class="eyebrow eyebrow-purple" style="margin-bottom:.3rem">Editing</p>
|
|
<h1 style="font-size:1.05rem;font-weight:700;color:var(--text);"><?= h($video['title']) ?></h1>
|
|
</div>
|
|
<a href="index.php" class="btn btn-secondary btn-sm">← Back</a>
|
|
</div>
|
|
|
|
<form method="post" action="edit.php?id=<?= $id ?>" enctype="multipart/form-data">
|
|
<input type="hidden" name="action" value="save">
|
|
|
|
<!-- ── Details ── -->
|
|
<div class="card" style="margin-bottom:1rem;">
|
|
<p class="section-heading">Video Details</p>
|
|
<div class="form-grid">
|
|
<div class="form-group span2">
|
|
<label class="form-label" for="title">Title *</label>
|
|
<input class="form-input" type="text" id="title" name="title"
|
|
value="<?= h($video['title']) ?>" required>
|
|
</div>
|
|
<div class="form-group span2">
|
|
<label class="form-label" for="description">Description</label>
|
|
<textarea class="form-textarea" id="description" name="description" rows="3"><?= h($video['description']) ?></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Slug (auto-generated)</label>
|
|
<input class="form-input" type="text" value="<?= h($video['slug']) ?>" disabled style="opacity:.5;">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="duration">Duration (seconds)</label>
|
|
<input class="form-input" type="number" id="duration" name="duration"
|
|
value="<?= (int)$video['duration'] ?>" min="0">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="sort_order">Sort Order</label>
|
|
<input class="form-input" type="number" id="sort_order" name="sort_order"
|
|
value="<?= (int)$video['sort_order'] ?>" min="0">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Published</label>
|
|
<label style="display:flex;align-items:center;gap:.6rem;cursor:pointer;margin-top:.35rem;">
|
|
<input type="checkbox" name="published" value="1" <?= $video['published'] ? 'checked' : '' ?>>
|
|
<span style="font-size:.82rem;color:var(--text-2);">Published</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-group span2">
|
|
<label class="form-label">Thumbnail</label>
|
|
<?php if ($video['thumbnail']): ?>
|
|
<img class="thumb-preview" src="<?= h('../' . MEDIA_URL . $video['thumbnail']) ?>" alt="Current thumbnail">
|
|
<span class="hint" style="display:block;margin-bottom:.5rem;">Current: <?= h($video['thumbnail']) ?></span>
|
|
<?php endif; ?>
|
|
<input class="form-input" type="file" id="thumbnail" name="thumbnail"
|
|
accept="image/jpeg,image/png,image/webp,image/gif" style="padding:.45rem .6rem;">
|
|
<span class="hint">Upload new image to replace current thumbnail.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Existing sources ── -->
|
|
<?php if (!empty($video['sources'])): ?>
|
|
<div class="card" style="margin-bottom:1rem;">
|
|
<p class="section-heading">Existing Sources</p>
|
|
<div class="source-list">
|
|
<?php foreach ($video['sources'] as $i => $s): ?>
|
|
<div class="source-item">
|
|
<div class="source-item-header">
|
|
<div>
|
|
<span class="source-badge <?= source_is_remote($s) ? 'source-badge-remote' : 'source-badge-local' ?>">
|
|
<?= source_is_remote($s) ? 'External URL' : 'Uploaded File' ?>
|
|
</span>
|
|
<div class="source-item-file"><?= h(source_location($s)) ?></div>
|
|
<div class="source-item-mime"><?= h($s['mime_type']) ?></div>
|
|
</div>
|
|
<a href="edit.php?id=<?= $id ?>&del_src=<?= $s['id'] ?>"
|
|
class="btn btn-danger btn-sm"
|
|
onclick="return confirm('Remove this source?<?= source_is_local($s) ? ' Uploaded files are also deleted from disk.' : '' ?>')">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3,6 5,6 21,6"/><path d="M19 6l-1 14H6L5 6"/><path d="M9 6V4h6v2"/></svg>
|
|
Remove
|
|
</a>
|
|
</div>
|
|
<div class="source-item-fields">
|
|
<input type="hidden" name="src_id[]" value="<?= $s['id'] ?>">
|
|
<input type="hidden" name="src_type[]" value="<?= h($s['source_type'] ?? 'local') ?>">
|
|
<?php if (source_is_remote($s)): ?>
|
|
<div class="form-group span4">
|
|
<label class="form-label">External URL</label>
|
|
<input class="form-input" type="url" name="src_url[]" value="<?= h($s['source_url']) ?>">
|
|
</div>
|
|
<?php else: ?>
|
|
<input type="hidden" name="src_url[]" value="">
|
|
<?php endif; ?>
|
|
<div class="form-group">
|
|
<label class="form-label">Quality</label>
|
|
<select class="form-select" name="src_quality[]">
|
|
<?php foreach (['1080p','720p','480p','360p','4K',''] as $q): ?>
|
|
<option value="<?= $q ?>" <?= $s['quality']===$q ? 'selected' : '' ?>><?= $q ?: '—' ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Label</label>
|
|
<input class="form-input" type="text" name="src_label[]" value="<?= h($s['label']) ?>" placeholder="e.g. HD">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Sort</label>
|
|
<input class="form-input" type="number" name="src_sort[]" value="<?= (int)$s['sort_order'] ?>" min="0">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- ── Add new sources ── -->
|
|
<div class="card" style="margin-bottom:1rem;">
|
|
<p class="section-heading">Add New Sources</p>
|
|
<p class="hint" style="margin-bottom:.85rem;">Upload another file or link an external URL. Remote URLs must use http:// or https://.</p>
|
|
<div class="source-list" id="new-sources-container">
|
|
<div class="add-source-row" data-new-row="0">
|
|
<div class="form-group">
|
|
<label class="form-label">Source</label>
|
|
<select class="form-select source-type-select" name="new_source_type[]">
|
|
<option value="local" selected>Upload</option>
|
|
<option value="remote">External URL</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group source-location-panel source-location-local">
|
|
<label class="form-label">Video File</label>
|
|
<input class="form-input" type="file" name="new_source_file[]"
|
|
accept="video/mp4,video/webm,video/ogg,.mp4,.webm,.ogv,.mov,.mkv,.avi"
|
|
style="padding:.45rem .6rem;">
|
|
</div>
|
|
<div class="form-group source-location-panel source-location-remote" hidden>
|
|
<label class="form-label">External URL</label>
|
|
<input class="form-input" type="url" name="new_source_url[]" placeholder="https://example.com/video.mp4">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Quality</label>
|
|
<select class="form-select" name="new_source_quality[]">
|
|
<option value="1080p">1080p</option>
|
|
<option value="720p" selected>720p</option>
|
|
<option value="480p">480p</option>
|
|
<option value="360p">360p</option>
|
|
<option value="4K">4K</option>
|
|
<option value="">—</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Label</label>
|
|
<input class="form-input" type="text" name="new_source_label[]" placeholder="e.g. HD">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Sort</label>
|
|
<input class="form-input" type="number" name="new_source_sort[]" value="0" min="0" style="width:70px;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button type="button" id="add-new-source" class="btn btn-secondary btn-sm" style="margin-top:.75rem;align-self:flex-start;">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
Add Another Source
|
|
</button>
|
|
</div>
|
|
|
|
<div style="display:flex;gap:.75rem;flex-wrap:wrap;">
|
|
<button class="btn btn-primary" type="submit">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17,21 17,13 7,13 7,21"/><polyline points="7,3 7,8 15,8"/></svg>
|
|
Save Changes
|
|
</button>
|
|
<a href="index.php" class="btn btn-secondary">Cancel</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<?php render_footer(); ?>
|
|
</main>
|
|
|
|
<script>
|
|
function toggleSourceRow(row) {
|
|
var select = row.querySelector('.source-type-select');
|
|
var isRemote = select && select.value === 'remote';
|
|
row.querySelectorAll('.source-location-local').forEach(function(el){ el.hidden = isRemote; });
|
|
row.querySelectorAll('.source-location-remote').forEach(function(el){ el.hidden = !isRemote; });
|
|
}
|
|
|
|
document.querySelectorAll('[data-new-row]').forEach(function(row) {
|
|
toggleSourceRow(row);
|
|
var select = row.querySelector('.source-type-select');
|
|
if (select) select.addEventListener('change', function(){ toggleSourceRow(row); });
|
|
});
|
|
|
|
document.getElementById('add-new-source').addEventListener('click', function() {
|
|
var container = document.getElementById('new-sources-container');
|
|
var rows = container.querySelectorAll('[data-new-row]');
|
|
var idx = rows.length;
|
|
var clone = rows[0].cloneNode(true);
|
|
clone.setAttribute('data-new-row', idx);
|
|
clone.querySelectorAll('input[type="file"]').forEach(function(el){ el.value=''; });
|
|
clone.querySelectorAll('input[type="text"]').forEach(function(el){ el.value=''; });
|
|
clone.querySelectorAll('input[type="url"]').forEach(function(el){ el.value=''; });
|
|
clone.querySelectorAll('input[type="number"]').forEach(function(el){ el.value=idx; });
|
|
clone.querySelectorAll('.source-type-select').forEach(function(el){ el.value='local'; });
|
|
if (!clone.querySelector('.remove-row')) {
|
|
var btn = document.createElement('button');
|
|
btn.type='button'; btn.className='btn btn-danger btn-sm remove-row'; btn.style='align-self:flex-end'; btn.innerHTML='✕';
|
|
btn.onclick=function(){ this.closest('[data-new-row]').remove(); };
|
|
clone.appendChild(btn);
|
|
}
|
|
toggleSourceRow(clone);
|
|
var select = clone.querySelector('.source-type-select');
|
|
if (select) select.addEventListener('change', function(){ toggleSourceRow(clone); });
|
|
container.appendChild(clone);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|