Initial dev export (exclude uploads/runtime)
This commit is contained in:
168
plugins/releases/views/admin/edit.php
Normal file
168
plugins/releases/views/admin/edit.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
$pageTitle = $title ?? 'Edit Release';
|
||||
$release = $release ?? [];
|
||||
$error = $error ?? '';
|
||||
$uploadError = (string)($_GET['upload_error'] ?? '');
|
||||
$storePluginEnabled = (bool)($store_plugin_enabled ?? false);
|
||||
ob_start();
|
||||
?>
|
||||
<section class="admin-card">
|
||||
<div class="badge">Releases</div>
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:16px;">
|
||||
<div>
|
||||
<h1 style="font-size:28px; margin:0;"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
|
||||
<p style="color: var(--muted); margin-top:6px;">Create or update a release.</p>
|
||||
</div>
|
||||
<div style="display:flex; gap:10px; align-items:center;">
|
||||
<?php if ((int)($release['id'] ?? 0) > 0): ?>
|
||||
<a href="/admin/releases/tracks?release_id=<?= (int)$release['id'] ?>" class="btn outline">Manage Tracks</a>
|
||||
<?php endif; ?>
|
||||
<a href="/admin/releases" class="btn outline">Back</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div style="margin-top:16px; color:#f3b0b0; font-size:13px;"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($uploadError !== ''): ?>
|
||||
<div style="margin-top:12px; color:#f3b0b0; font-size:13px;"><?= htmlspecialchars($uploadError, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="/admin/releases/save" enctype="multipart/form-data" style="margin-top:16px; display:grid; gap:16px;">
|
||||
<input type="hidden" name="id" value="<?= (int)($release['id'] ?? 0) ?>">
|
||||
<div class="admin-card" style="padding:16px;">
|
||||
<div style="display:grid; gap:12px;">
|
||||
<label class="label">Title</label>
|
||||
<input class="input" name="title" value="<?= htmlspecialchars((string)($release['title'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="Release title">
|
||||
<label class="label">Artist</label>
|
||||
<input class="input" name="artist_name" value="<?= htmlspecialchars((string)($release['artist_name'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="Artist name">
|
||||
<label class="label">Slug</label>
|
||||
<input class="input" name="slug" value="<?= htmlspecialchars((string)($release['slug'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="release-title">
|
||||
<label class="label">Release Date</label>
|
||||
<input class="input" type="date" name="release_date" value="<?= htmlspecialchars((string)($release['release_date'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
|
||||
<label class="label">Catalog Number</label>
|
||||
<input class="input" name="catalog_no" value="<?= htmlspecialchars((string)($release['catalog_no'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="CAT-001">
|
||||
<div class="admin-card" style="padding:14px; background: rgba(10,10,12,0.6);">
|
||||
<div class="label">Upload cover</div>
|
||||
<input type="hidden" name="release_id" value="<?= (int)($release['id'] ?? 0) ?>">
|
||||
<label for="releaseCoverFile" id="releaseCoverDropzone" style="display:flex; flex-direction:column; gap:8px; align-items:center; justify-content:center; padding:18px; border-radius:14px; border:1px dashed rgba(255,255,255,0.2); background: rgba(0,0,0,0.2); cursor:pointer;">
|
||||
<div style="font-size:11px; text-transform:uppercase; letter-spacing:0.2em; color:var(--muted);">Drag & Drop</div>
|
||||
<div style="font-size:13px; color:var(--text);">or click to upload</div>
|
||||
<div id="releaseCoverFileName" style="font-size:11px; color:var(--muted);">No file selected</div>
|
||||
</label>
|
||||
<input class="input" type="file" id="releaseCoverFile" name="release_cover" accept="image/*" style="display:none;">
|
||||
<div style="margin-top:10px; display:flex; justify-content:flex-end;">
|
||||
<button type="submit" class="btn small" formaction="/admin/releases/upload" formmethod="post" formenctype="multipart/form-data" name="upload_type" value="cover">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
||||
<label class="label" style="margin:0;">Cover URL</label>
|
||||
<button type="button" class="btn outline small" data-media-picker="release_cover_url" data-media-picker-mode="url">Pick from Media</button>
|
||||
</div>
|
||||
<input class="input" id="release_cover_url" name="cover_url" value="<?= htmlspecialchars((string)($release['cover_url'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="https://...">
|
||||
<div class="admin-card" style="padding:14px; background: rgba(10,10,12,0.6);">
|
||||
<div class="label">Upload sample (MP3)</div>
|
||||
<input type="hidden" name="release_id" value="<?= (int)($release['id'] ?? 0) ?>">
|
||||
<label for="releaseSampleFile" id="releaseSampleDropzone" style="display:flex; flex-direction:column; gap:8px; align-items:center; justify-content:center; padding:18px; border-radius:14px; border:1px dashed rgba(255,255,255,0.2); background: rgba(0,0,0,0.2); cursor:pointer;">
|
||||
<div style="font-size:11px; text-transform:uppercase; letter-spacing:0.2em; color:var(--muted);">Drag & Drop</div>
|
||||
<div style="font-size:13px; color:var(--text);">or click to upload</div>
|
||||
<div id="releaseSampleFileName" style="font-size:11px; color:var(--muted);">No file selected</div>
|
||||
</label>
|
||||
<input class="input" type="file" id="releaseSampleFile" name="release_sample" accept="audio/mpeg" style="display:none;">
|
||||
<div style="margin-top:10px; display:flex; justify-content:flex-end;">
|
||||
<button type="submit" class="btn small" formaction="/admin/releases/upload" formmethod="post" formenctype="multipart/form-data" name="upload_type" value="sample">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="label">Sample URL (MP3)</label>
|
||||
<input class="input" name="sample_url" value="<?= htmlspecialchars((string)($release['sample_url'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="https://...">
|
||||
<label class="label">Description</label>
|
||||
<textarea class="input" name="description" rows="6" style="resize:vertical; font-family:'IBM Plex Mono', monospace; font-size:13px; line-height:1.6;"><?= htmlspecialchars((string)($release['description'] ?? ''), ENT_QUOTES, 'UTF-8') ?></textarea>
|
||||
<label class="label">Release Credits</label>
|
||||
<textarea class="input" name="credits" rows="4" style="resize:vertical; font-family:'IBM Plex Mono', monospace; font-size:13px; line-height:1.6;" placeholder="Written by..., Produced by..., Mastered by..."><?= htmlspecialchars((string)($release['credits'] ?? ''), ENT_QUOTES, 'UTF-8') ?></textarea>
|
||||
<?php if ($storePluginEnabled): ?>
|
||||
<div class="admin-card" style="padding:14px; background: rgba(10,10,12,0.6);">
|
||||
<div class="label" style="margin-bottom:10px;">Store Options</div>
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
|
||||
<input type="checkbox" name="store_enabled" value="1" <?= ((int)($release['store_enabled'] ?? 0) === 1) ? 'checked' : '' ?>>
|
||||
Enable release purchase
|
||||
</label>
|
||||
<div style="display:grid; grid-template-columns:1fr 120px; gap:10px; margin-top:10px;">
|
||||
<div>
|
||||
<label class="label">Bundle Price</label>
|
||||
<input class="input" name="bundle_price" value="<?= htmlspecialchars((string)($release['bundle_price'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="3.99">
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Currency</label>
|
||||
<input class="input" name="store_currency" value="<?= htmlspecialchars((string)($release['store_currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?>" placeholder="GBP">
|
||||
</div>
|
||||
</div>
|
||||
<label class="label" style="margin-top:10px;">Button Label (optional)</label>
|
||||
<input class="input" name="purchase_label" value="<?= htmlspecialchars((string)($release['purchase_label'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="Buy Release">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
|
||||
<input type="checkbox" name="is_published" value="1" <?= ((int)($release['is_published'] ?? 1) === 1) ? 'checked' : '' ?>>
|
||||
Published
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:flex-end; gap:12px; align-items:center;">
|
||||
<button type="submit" class="btn">Save release</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<script>
|
||||
(function () {
|
||||
const coverDrop = document.getElementById('releaseCoverDropzone');
|
||||
const coverFile = document.getElementById('releaseCoverFile');
|
||||
const coverName = document.getElementById('releaseCoverFileName');
|
||||
if (coverDrop && coverFile && coverName) {
|
||||
coverDrop.addEventListener('dragover', (event) => {
|
||||
event.preventDefault();
|
||||
coverDrop.style.borderColor = 'var(--accent)';
|
||||
});
|
||||
coverDrop.addEventListener('dragleave', () => {
|
||||
coverDrop.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
});
|
||||
coverDrop.addEventListener('drop', (event) => {
|
||||
event.preventDefault();
|
||||
coverDrop.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length) {
|
||||
coverFile.files = event.dataTransfer.files;
|
||||
coverName.textContent = event.dataTransfer.files[0].name;
|
||||
}
|
||||
});
|
||||
coverFile.addEventListener('change', () => {
|
||||
coverName.textContent = coverFile.files.length ? coverFile.files[0].name : 'No file selected';
|
||||
});
|
||||
}
|
||||
|
||||
const sampleDrop = document.getElementById('releaseSampleDropzone');
|
||||
const sampleFile = document.getElementById('releaseSampleFile');
|
||||
const sampleName = document.getElementById('releaseSampleFileName');
|
||||
if (sampleDrop && sampleFile && sampleName) {
|
||||
sampleDrop.addEventListener('dragover', (event) => {
|
||||
event.preventDefault();
|
||||
sampleDrop.style.borderColor = 'var(--accent)';
|
||||
});
|
||||
sampleDrop.addEventListener('dragleave', () => {
|
||||
sampleDrop.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
});
|
||||
sampleDrop.addEventListener('drop', (event) => {
|
||||
event.preventDefault();
|
||||
sampleDrop.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length) {
|
||||
sampleFile.files = event.dataTransfer.files;
|
||||
sampleName.textContent = event.dataTransfer.files[0].name;
|
||||
}
|
||||
});
|
||||
sampleFile.addEventListener('change', () => {
|
||||
sampleName.textContent = sampleFile.files.length ? sampleFile.files[0].name : 'No file selected';
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../../modules/admin/views/layout.php';
|
||||
87
plugins/releases/views/admin/index.php
Normal file
87
plugins/releases/views/admin/index.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
$pageTitle = 'Releases';
|
||||
$releases = $releases ?? [];
|
||||
$tableReady = $table_ready ?? false;
|
||||
$pageId = (int)($page_id ?? 0);
|
||||
$pagePublished = (int)($page_published ?? 0);
|
||||
ob_start();
|
||||
?>
|
||||
<section class="admin-card">
|
||||
<div class="badge">Releases</div>
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:16px;">
|
||||
<div>
|
||||
<h1 style="font-size:28px; margin:0;">Releases</h1>
|
||||
<p style="color: var(--muted); margin-top:6px;">Manage singles, EPs, and albums.</p>
|
||||
</div>
|
||||
<a href="/admin/releases/new" class="btn">New Release</a>
|
||||
</div>
|
||||
|
||||
<?php if (!$tableReady): ?>
|
||||
<div class="admin-card" style="margin-top:16px; padding:16px; display:flex; align-items:center; justify-content:space-between; gap:16px;">
|
||||
<div>
|
||||
<div style="font-weight:600;">Database not initialized</div>
|
||||
<div style="color: var(--muted); font-size:13px; margin-top:4px;">Create the releases table before adding records.</div>
|
||||
</div>
|
||||
<form method="post" action="/admin/releases/install">
|
||||
<button type="submit" class="btn small">Create Tables</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="admin-card" style="margin-top:16px; padding:14px; display:flex; align-items:center; justify-content:space-between; gap:16px;">
|
||||
<div>
|
||||
<div style="font-weight:600;">Releases page</div>
|
||||
<div style="color: var(--muted); font-size:12px; margin-top:4px;">
|
||||
Slug: <code>releases</code>
|
||||
<?php if ($pageId > 0): ?>
|
||||
- Status: <?= $pagePublished === 1 ? 'Published' : 'Draft' ?>
|
||||
<?php else: ?>
|
||||
- Not created
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($pageId > 0): ?>
|
||||
<a href="/admin/pages/edit?id=<?= $pageId ?>" class="btn outline small">Edit Page Content</a>
|
||||
<?php else: ?>
|
||||
<span class="pill">Re-enable plugin to create</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!$releases): ?>
|
||||
<div style="margin-top:18px; color: var(--muted); font-size:13px;">No releases yet.</div>
|
||||
<?php else: ?>
|
||||
<div style="margin-top:18px; display:grid; gap:12px;">
|
||||
<?php foreach ($releases as $release): ?>
|
||||
<div class="admin-card" style="padding:14px; display:flex; align-items:center; justify-content:space-between; gap:16px;">
|
||||
<div style="display:flex; gap:12px; align-items:center;">
|
||||
<div style="width:44px; height:44px; border-radius:12px; overflow:hidden; background:rgba(255,255,255,0.06); display:grid; place-items:center;">
|
||||
<?php if (!empty($release['cover_url'])): ?>
|
||||
<img src="<?= htmlspecialchars((string)$release['cover_url'], ENT_QUOTES, 'UTF-8') ?>" alt="" style="width:100%; height:100%; object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<span style="font-size:12px; color:var(--muted);">N/A</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight:600;"><?= htmlspecialchars((string)$release['title'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<div style="font-size:12px; color:var(--muted);"><?= htmlspecialchars((string)$release['slug'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:8px; align-items:center;">
|
||||
<?php if ((int)$release['is_published'] !== 1): ?>
|
||||
<span class="pill">Draft</span>
|
||||
<?php endif; ?>
|
||||
<a href="/admin/releases/tracks?release_id=<?= (int)$release['id'] ?>" class="btn outline small">Tracks</a>
|
||||
<a href="/admin/releases/edit?id=<?= (int)$release['id'] ?>" class="btn outline small">Edit</a>
|
||||
<form method="post" action="/admin/releases/delete" onsubmit="return confirm('Delete this release?');">
|
||||
<input type="hidden" name="id" value="<?= (int)$release['id'] ?>">
|
||||
<button type="submit" class="btn outline small">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../../modules/admin/views/layout.php';
|
||||
161
plugins/releases/views/admin/track_edit.php
Normal file
161
plugins/releases/views/admin/track_edit.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
$pageTitle = $title ?? 'Edit Track';
|
||||
$track = $track ?? [];
|
||||
$release = $release ?? null;
|
||||
$error = $error ?? '';
|
||||
$uploadError = (string)($_GET['upload_error'] ?? '');
|
||||
$storePluginEnabled = (bool)($store_plugin_enabled ?? false);
|
||||
ob_start();
|
||||
?>
|
||||
<section class="admin-card">
|
||||
<div class="badge">Releases</div>
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:16px;">
|
||||
<div>
|
||||
<h1 style="font-size:28px; margin:0;"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
|
||||
<p style="color: var(--muted); margin-top:6px;">
|
||||
<?= $release ? htmlspecialchars((string)$release['title'], ENT_QUOTES, 'UTF-8') : 'Track details' ?>
|
||||
</p>
|
||||
</div>
|
||||
<a href="/admin/releases/tracks?release_id=<?= (int)($track['release_id'] ?? 0) ?>" class="btn outline">Back</a>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div style="margin-top:16px; color:#f3b0b0; font-size:13px;"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($uploadError !== ''): ?>
|
||||
<div style="margin-top:12px; color:#f3b0b0; font-size:13px;"><?= htmlspecialchars($uploadError, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="/admin/releases/tracks/save" enctype="multipart/form-data" style="margin-top:16px; display:grid; gap:16px;">
|
||||
<input type="hidden" name="id" value="<?= (int)($track['id'] ?? 0) ?>">
|
||||
<input type="hidden" name="track_id" value="<?= (int)($track['id'] ?? 0) ?>">
|
||||
<input type="hidden" name="release_id" value="<?= (int)($track['release_id'] ?? 0) ?>">
|
||||
|
||||
<div class="admin-card" style="padding:16px;">
|
||||
<div style="display:grid; gap:12px;">
|
||||
<label class="label">Track #</label>
|
||||
<input class="input" name="track_no" value="<?= htmlspecialchars((string)($track['track_no'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="1">
|
||||
<label class="label">Title</label>
|
||||
<input class="input" name="title" value="<?= htmlspecialchars((string)($track['title'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="Track title">
|
||||
<label class="label">Mix Name</label>
|
||||
<input class="input" name="mix_name" value="<?= htmlspecialchars((string)($track['mix_name'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="Extended Mix">
|
||||
<label class="label">Duration</label>
|
||||
<input class="input" name="duration" value="<?= htmlspecialchars((string)($track['duration'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="6:12">
|
||||
<label class="label">BPM</label>
|
||||
<input class="input" name="bpm" value="<?= htmlspecialchars((string)($track['bpm'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="140">
|
||||
<label class="label">Key</label>
|
||||
<input class="input" name="key_signature" value="<?= htmlspecialchars((string)($track['key_signature'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="B Minor">
|
||||
|
||||
<div class="admin-card" style="padding:14px; background: rgba(10,10,12,0.6);">
|
||||
<div class="label">Upload sample (MP3)</div>
|
||||
<label for="trackSampleFile" id="trackSampleDropzone" style="display:flex; flex-direction:column; gap:8px; align-items:center; justify-content:center; padding:18px; border-radius:14px; border:1px dashed rgba(255,255,255,0.2); background: rgba(0,0,0,0.2); cursor:pointer;">
|
||||
<div style="font-size:11px; text-transform:uppercase; letter-spacing:0.2em; color:var(--muted);">Drag & Drop</div>
|
||||
<div style="font-size:13px; color:var(--text);">or click to upload</div>
|
||||
<div id="trackSampleFileName" style="font-size:11px; color:var(--muted);">No file selected</div>
|
||||
</label>
|
||||
<input class="input" type="file" id="trackSampleFile" name="track_sample" accept="audio/mpeg" style="display:none;">
|
||||
<div style="margin-top:10px; display:flex; justify-content:flex-end;">
|
||||
<button type="submit" class="btn small" formaction="/admin/releases/tracks/upload" formmethod="post" formenctype="multipart/form-data" name="upload_kind" value="sample">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="label">Sample URL (MP3)</label>
|
||||
<input class="input" name="sample_url" value="<?= htmlspecialchars((string)($track['sample_url'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="https://...">
|
||||
|
||||
<?php if ($storePluginEnabled): ?>
|
||||
<div class="admin-card" style="padding:14px; background: rgba(10,10,12,0.6);">
|
||||
<div class="label">Upload full track (MP3)</div>
|
||||
<label for="trackFullFile" id="trackFullDropzone" style="display:flex; flex-direction:column; gap:8px; align-items:center; justify-content:center; padding:18px; border-radius:14px; border:1px dashed rgba(255,255,255,0.2); background: rgba(0,0,0,0.2); cursor:pointer;">
|
||||
<div style="font-size:11px; text-transform:uppercase; letter-spacing:0.2em; color:var(--muted);">Drag & Drop</div>
|
||||
<div style="font-size:13px; color:var(--text);">or click to upload</div>
|
||||
<div id="trackFullFileName" style="font-size:11px; color:var(--muted);">No file selected</div>
|
||||
</label>
|
||||
<input class="input" type="file" id="trackFullFile" name="track_sample" accept="audio/mpeg" style="display:none;">
|
||||
<div style="margin-top:10px; display:flex; justify-content:flex-end;">
|
||||
<button type="submit" class="btn small" formaction="/admin/releases/tracks/upload" formmethod="post" formenctype="multipart/form-data" name="upload_kind" value="full">Upload Full</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="label">Full File URL (Store Download)</label>
|
||||
<input class="input" name="full_file_url" value="<?= htmlspecialchars((string)($track['full_file_url'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="/uploads/media/track-full.mp3">
|
||||
|
||||
<div class="admin-card" style="padding:14px; background: rgba(10,10,12,0.6);">
|
||||
<div class="label" style="margin-bottom:10px;">Store Options</div>
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.2em;">
|
||||
<input type="checkbox" name="store_enabled" value="1" <?= ((int)($track['store_enabled'] ?? 0) === 1) ? 'checked' : '' ?>>
|
||||
Enable track purchase
|
||||
</label>
|
||||
<div style="display:grid; grid-template-columns:1fr 120px; gap:10px; margin-top:10px;">
|
||||
<div>
|
||||
<label class="label">Track Price</label>
|
||||
<input class="input" name="track_price" value="<?= htmlspecialchars((string)($track['track_price'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" placeholder="1.49">
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Currency</label>
|
||||
<input class="input" name="store_currency" value="<?= htmlspecialchars((string)($track['store_currency'] ?? 'GBP'), ENT_QUOTES, 'UTF-8') ?>" placeholder="GBP">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:flex-end; gap:12px; align-items:center;">
|
||||
<button type="submit" class="btn">Save track</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<script>
|
||||
(function () {
|
||||
const drop = document.getElementById('trackSampleDropzone');
|
||||
const file = document.getElementById('trackSampleFile');
|
||||
const name = document.getElementById('trackSampleFileName');
|
||||
if (drop && file && name) {
|
||||
drop.addEventListener('dragover', (event) => {
|
||||
event.preventDefault();
|
||||
drop.style.borderColor = 'var(--accent)';
|
||||
});
|
||||
drop.addEventListener('dragleave', () => {
|
||||
drop.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
});
|
||||
drop.addEventListener('drop', (event) => {
|
||||
event.preventDefault();
|
||||
drop.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length) {
|
||||
file.files = event.dataTransfer.files;
|
||||
name.textContent = event.dataTransfer.files[0].name;
|
||||
}
|
||||
});
|
||||
file.addEventListener('change', () => {
|
||||
name.textContent = file.files.length ? file.files[0].name : 'No file selected';
|
||||
});
|
||||
}
|
||||
|
||||
const fullDrop = document.getElementById('trackFullDropzone');
|
||||
const fullFile = document.getElementById('trackFullFile');
|
||||
const fullName = document.getElementById('trackFullFileName');
|
||||
if (fullDrop && fullFile && fullName) {
|
||||
fullDrop.addEventListener('dragover', (event) => {
|
||||
event.preventDefault();
|
||||
fullDrop.style.borderColor = 'var(--accent)';
|
||||
});
|
||||
fullDrop.addEventListener('dragleave', () => {
|
||||
fullDrop.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
});
|
||||
fullDrop.addEventListener('drop', (event) => {
|
||||
event.preventDefault();
|
||||
fullDrop.style.borderColor = 'rgba(255,255,255,0.2)';
|
||||
if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length) {
|
||||
fullFile.files = event.dataTransfer.files;
|
||||
fullName.textContent = event.dataTransfer.files[0].name;
|
||||
}
|
||||
});
|
||||
fullFile.addEventListener('change', () => {
|
||||
fullName.textContent = fullFile.files.length ? fullFile.files[0].name : 'No file selected';
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../../modules/admin/views/layout.php';
|
||||
69
plugins/releases/views/admin/tracks_index.php
Normal file
69
plugins/releases/views/admin/tracks_index.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
$pageTitle = 'Release Tracks';
|
||||
$release = $release ?? null;
|
||||
$tracks = $tracks ?? [];
|
||||
$tableReady = $table_ready ?? false;
|
||||
$releaseId = (int)($release_id ?? 0);
|
||||
ob_start();
|
||||
?>
|
||||
<section class="admin-card">
|
||||
<div class="badge">Releases</div>
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:16px;">
|
||||
<div>
|
||||
<h1 style="font-size:28px; margin:0;">Tracks</h1>
|
||||
<p style="color: var(--muted); margin-top:6px;">
|
||||
<?= $release ? htmlspecialchars((string)$release['title'], ENT_QUOTES, 'UTF-8') : 'Select a release to manage tracks.' ?>
|
||||
</p>
|
||||
</div>
|
||||
<div style="display:flex; gap:10px; align-items:center;">
|
||||
<a href="/admin/releases" class="btn outline">Back</a>
|
||||
<?php if ($releaseId > 0): ?>
|
||||
<a href="/admin/releases/tracks/new?release_id=<?= $releaseId ?>" class="btn">New Track</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!$tableReady): ?>
|
||||
<div style="margin-top:18px; color: var(--muted); font-size:13px;">Tracks table is not available. Run Releases ? Create Tables.</div>
|
||||
<?php elseif (!$release): ?>
|
||||
<div style="margin-top:18px; color: var(--muted); font-size:13px;">Release not found.</div>
|
||||
<?php elseif (!$tracks): ?>
|
||||
<div style="margin-top:18px; color: var(--muted); font-size:13px;">No tracks yet.</div>
|
||||
<?php else: ?>
|
||||
<div style="margin-top:18px; display:grid; gap:12px;">
|
||||
<?php foreach ($tracks as $track): ?>
|
||||
<div class="admin-card" style="padding:14px; display:flex; align-items:center; justify-content:space-between; gap:16px;">
|
||||
<div style="display:flex; gap:12px; align-items:center;">
|
||||
<div style="width:38px; height:38px; border-radius:10px; background:rgba(255,255,255,0.06); display:grid; place-items:center; font-size:12px; color:var(--muted);">
|
||||
<?= (int)($track['track_no'] ?? 0) > 0 ? (int)$track['track_no'] : '<27>' ?>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight:600;">
|
||||
<?= htmlspecialchars((string)$track['title'], ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php if (!empty($track['mix_name'])): ?>
|
||||
<span style="color:var(--muted); font-weight:400;">(<?= htmlspecialchars((string)$track['mix_name'], ENT_QUOTES, 'UTF-8') ?>)</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div style="font-size:12px; color:var(--muted);">
|
||||
<?= htmlspecialchars((string)($track['duration'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
|
||||
<?= !empty($track['bpm']) ? ' <20> ' . htmlspecialchars((string)$track['bpm'], ENT_QUOTES, 'UTF-8') . ' BPM' : '' ?>
|
||||
<?= !empty($track['key_signature']) ? ' <20> ' . htmlspecialchars((string)$track['key_signature'], ENT_QUOTES, 'UTF-8') : '' ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:8px; align-items:center;">
|
||||
<a href="/admin/releases/tracks/edit?release_id=<?= $releaseId ?>&id=<?= (int)$track['id'] ?>" class="btn outline small">Edit</a>
|
||||
<form method="post" action="/admin/releases/tracks/delete" onsubmit="return confirm('Delete this track?');">
|
||||
<input type="hidden" name="id" value="<?= (int)$track['id'] ?>">
|
||||
<input type="hidden" name="release_id" value="<?= $releaseId ?>">
|
||||
<button type="submit" class="btn outline small">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../../modules/admin/views/layout.php';
|
||||
366
plugins/releases/views/site/index.php
Normal file
366
plugins/releases/views/site/index.php
Normal file
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
$pageTitle = $title ?? 'Releases';
|
||||
$releases = is_array($releases ?? null) ? $releases : [];
|
||||
$releaseCount = (int)($total_releases ?? count($releases));
|
||||
$artistFilter = trim((string)($artist_filter ?? ''));
|
||||
$artistOptions = is_array($artist_options ?? null) ? $artist_options : [];
|
||||
$search = trim((string)($search ?? ''));
|
||||
$sort = trim((string)($sort ?? 'newest'));
|
||||
$currentPage = max(1, (int)($current_page ?? 1));
|
||||
$totalPages = max(1, (int)($total_pages ?? 1));
|
||||
|
||||
$buildReleaseUrl = static function (int $page) use ($search, $artistFilter, $sort): string {
|
||||
$params = [];
|
||||
if ($search !== '') {
|
||||
$params['q'] = $search;
|
||||
}
|
||||
if ($artistFilter !== '') {
|
||||
$params['artist'] = $artistFilter;
|
||||
}
|
||||
if ($sort !== 'newest') {
|
||||
$params['sort'] = $sort;
|
||||
}
|
||||
if ($page > 1) {
|
||||
$params['p'] = $page;
|
||||
}
|
||||
$qs = http_build_query($params);
|
||||
return '/releases' . ($qs !== '' ? ('?' . $qs) : '');
|
||||
};
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div class="ac-releases-page">
|
||||
<section class="card ac-releases-shell">
|
||||
<div class="ac-releases-header">
|
||||
<div class="badge">Releases</div>
|
||||
<h1>Latest Drops</h1>
|
||||
<p>Singles, EPs, and albums from the AudioCore catalog.</p>
|
||||
</div>
|
||||
|
||||
<form method="get" action="/releases" class="ac-release-controls">
|
||||
<div class="ac-search-wrap">
|
||||
<span class="ac-search-icon"><i class="fa-solid fa-magnifying-glass"></i></span>
|
||||
<input class="ac-search-input" type="text" name="q" value="<?= htmlspecialchars($search, ENT_QUOTES, 'UTF-8') ?>" placeholder="Search releases, artists, catalog number">
|
||||
</div>
|
||||
<select class="ac-control-input" name="artist">
|
||||
<option value="">All artists</option>
|
||||
<?php foreach ($artistOptions as $artist): ?>
|
||||
<option value="<?= htmlspecialchars((string)$artist, ENT_QUOTES, 'UTF-8') ?>" <?= $artistFilter === (string)$artist ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars((string)$artist, ENT_QUOTES, 'UTF-8') ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select class="ac-control-input" name="sort">
|
||||
<option value="newest" <?= $sort === 'newest' ? 'selected' : '' ?>>Newest first</option>
|
||||
<option value="oldest" <?= $sort === 'oldest' ? 'selected' : '' ?>>Oldest first</option>
|
||||
<option value="title_asc" <?= $sort === 'title_asc' ? 'selected' : '' ?>>Title A-Z</option>
|
||||
<option value="title_desc" <?= $sort === 'title_desc' ? 'selected' : '' ?>>Title Z-A</option>
|
||||
</select>
|
||||
<button type="submit" class="ac-btn ac-btn-primary">Apply</button>
|
||||
<a href="/releases" class="ac-btn ac-btn-ghost">Reset</a>
|
||||
</form>
|
||||
|
||||
<?php if ($artistFilter !== '' || $search !== '' || $sort !== 'newest'): ?>
|
||||
<div class="ac-active-filters">
|
||||
<?php if ($artistFilter !== ''): ?><div class="ac-chip">Artist: <?= htmlspecialchars($artistFilter, ENT_QUOTES, 'UTF-8') ?></div><?php endif; ?>
|
||||
<?php if ($search !== ''): ?><div class="ac-chip">Search: <?= htmlspecialchars($search, ENT_QUOTES, 'UTF-8') ?></div><?php endif; ?>
|
||||
<?php if ($sort !== 'newest'): ?><div class="ac-chip">Sort: <?= htmlspecialchars(str_replace('_', ' ', $sort), ENT_QUOTES, 'UTF-8') ?></div><?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$releases): ?>
|
||||
<div class="ac-empty">No releases published yet.</div>
|
||||
<?php else: ?>
|
||||
<div class="ac-release-grid">
|
||||
<?php foreach ($releases as $release): ?>
|
||||
<a class="ac-release-card" href="/release?slug=<?= htmlspecialchars((string)$release['slug'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<div class="ac-release-cover">
|
||||
<?php if (!empty($release['cover_url'])): ?>
|
||||
<img src="<?= htmlspecialchars((string)$release['cover_url'], ENT_QUOTES, 'UTF-8') ?>" alt="">
|
||||
<?php else: ?>
|
||||
<div class="ac-release-placeholder">AC</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($release['release_date'])): ?>
|
||||
<span class="ac-release-date"><?= htmlspecialchars((string)$release['release_date'], ENT_QUOTES, 'UTF-8') ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="ac-release-meta">
|
||||
<div class="ac-release-title"><?= htmlspecialchars((string)$release['title'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php if (!empty($release['artist_name'])): ?>
|
||||
<div class="ac-release-artist"><?= htmlspecialchars((string)$release['artist_name'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<nav class="ac-release-pagination">
|
||||
<?php $prevPage = max(1, $currentPage - 1); ?>
|
||||
<?php $nextPage = min($totalPages, $currentPage + 1); ?>
|
||||
<a class="ac-btn ac-btn-ghost<?= $currentPage <= 1 ? ' is-disabled' : '' ?>" href="<?= $currentPage <= 1 ? '#' : htmlspecialchars($buildReleaseUrl($prevPage), ENT_QUOTES, 'UTF-8') ?>">Prev</a>
|
||||
<div class="ac-pagination-meta">Page <?= $currentPage ?> of <?= $totalPages ?> · <?= $releaseCount ?> total</div>
|
||||
<a class="ac-btn ac-btn-ghost<?= $currentPage >= $totalPages ? ' is-disabled' : '' ?>" href="<?= $currentPage >= $totalPages ? '#' : htmlspecialchars($buildReleaseUrl($nextPage), ENT_QUOTES, 'UTF-8') ?>">Next</a>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.ac-releases-page .ac-releases-shell {
|
||||
margin-top: 14px;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
.ac-releases-page .ac-releases-header {
|
||||
border-bottom: 1px solid rgba(255,255,255,0.06);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.ac-releases-page .ac-releases-header h1 {
|
||||
margin: 8px 0 0;
|
||||
font-size: 52px;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.ac-releases-page .ac-releases-header p {
|
||||
margin: 8px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ac-releases-page .ac-release-controls {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 1fr) 180px 180px auto auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border: 1px solid rgba(255,255,255,0.07);
|
||||
border-radius: 14px;
|
||||
background: rgba(8, 12, 19, 0.16);
|
||||
}
|
||||
.ac-releases-page .ac-search-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
border: 1px solid rgba(255,255,255,0.07);
|
||||
border-radius: 10px;
|
||||
background: rgba(7,10,16,0.10);
|
||||
height: 36px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.ac-releases-page .ac-search-icon {
|
||||
color: rgba(255,255,255,0.72);
|
||||
font-size: 12px;
|
||||
width: 16px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.ac-releases-page .ac-search-input,
|
||||
.ac-releases-page .ac-control-input {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255,255,255,0.07);
|
||||
background: rgba(7,10,16,0.10);
|
||||
color: rgba(233,237,247,0.94);
|
||||
padding: 0 10px;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
.ac-releases-page .ac-search-input {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.ac-releases-page .ac-search-input::placeholder {
|
||||
color: rgba(220,228,245,.45);
|
||||
}
|
||||
.ac-releases-page .ac-search-input:focus,
|
||||
.ac-releases-page .ac-control-input:focus {
|
||||
box-shadow: 0 0 0 2px rgba(34,242,165,.12);
|
||||
border-color: rgba(34,242,165,.38);
|
||||
}
|
||||
|
||||
.ac-releases-page .ac-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255,255,255,.10);
|
||||
text-decoration: none;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .12em;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
.ac-releases-page .ac-btn-primary {
|
||||
background: rgba(34,242,165,.14);
|
||||
color: #87f2cb;
|
||||
border-color: rgba(34,242,165,.34);
|
||||
}
|
||||
.ac-releases-page .ac-btn-primary:hover {
|
||||
background: rgba(34,242,165,.20);
|
||||
}
|
||||
.ac-releases-page .ac-btn-ghost {
|
||||
color: #a1acc4;
|
||||
background: transparent;
|
||||
border-color: rgba(255,255,255,.10);
|
||||
}
|
||||
.ac-releases-page .ac-btn-ghost:hover {
|
||||
color: #e5ebf7;
|
||||
border-color: rgba(255,255,255,.18);
|
||||
}
|
||||
.ac-releases-page .ac-btn.is-disabled {
|
||||
opacity: .45;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ac-releases-page .ac-active-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ac-releases-page .ac-chip {
|
||||
border-radius: 999px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid rgba(255,255,255,.12);
|
||||
font-size: 10px;
|
||||
letter-spacing: .12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
.ac-releases-page .ac-release-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 240px));
|
||||
justify-content: start;
|
||||
gap: 16px;
|
||||
}
|
||||
.ac-releases-page .ac-release-card {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 10px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid rgba(255,255,255,.08);
|
||||
background: rgba(0,0,0,.14);
|
||||
transition: border-color .2s ease, transform .2s ease;
|
||||
}
|
||||
.ac-releases-page .ac-release-card:hover {
|
||||
border-color: rgba(255,255,255,.18);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.ac-releases-page .ac-release-cover {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: rgba(255,255,255,.03);
|
||||
}
|
||||
.ac-releases-page .ac-release-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.ac-releases-page .ac-release-date {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
bottom: 10px;
|
||||
border-radius: 999px;
|
||||
padding: 6px 10px;
|
||||
font-size: 11px;
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,.56);
|
||||
border: 1px solid rgba(255,255,255,.18);
|
||||
}
|
||||
.ac-releases-page .ac-release-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--muted);
|
||||
letter-spacing: .16em;
|
||||
}
|
||||
.ac-releases-page .ac-release-meta {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
.ac-releases-page .ac-release-title {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.ac-releases-page .ac-release-artist {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.ac-releases-page .ac-empty {
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(255,255,255,.08);
|
||||
border-radius: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.ac-releases-page .ac-release-pagination {
|
||||
margin-top: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ac-releases-page .ac-pagination-meta {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.ac-releases-page .ac-release-controls {
|
||||
grid-template-columns: 1fr 160px 160px auto auto;
|
||||
}
|
||||
.ac-releases-page .ac-release-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(210px, 230px));
|
||||
}
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.ac-releases-page .ac-release-controls {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
.ac-releases-page .ac-release-controls .ac-search-wrap {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.ac-releases-page .ac-releases-header h1 {
|
||||
font-size: 40px;
|
||||
}
|
||||
.ac-releases-page .ac-release-controls {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.ac-releases-page .ac-release-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
justify-content: stretch;
|
||||
}
|
||||
}
|
||||
@media (max-width: 520px) {
|
||||
.ac-releases-page .ac-release-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../../views/site/layout.php';
|
||||
326
plugins/releases/views/site/show.php
Normal file
326
plugins/releases/views/site/show.php
Normal file
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$pageTitle = $title ?? 'Release';
|
||||
$release = $release ?? null;
|
||||
$tracks = $tracks ?? [];
|
||||
$storePluginEnabled = (bool)($store_plugin_enabled ?? false);
|
||||
$releaseCover = (string)($release['cover_url'] ?? '');
|
||||
$returnUrl = (string)($_SERVER['REQUEST_URI'] ?? '/releases');
|
||||
$releaseStoreEnabled = (int)($release['store_enabled'] ?? 0) === 1;
|
||||
$bundlePrice = (float)($release['bundle_price'] ?? 0);
|
||||
$bundleCurrency = (string)($release['store_currency'] ?? 'GBP');
|
||||
$bundleLabel = trim((string)($release['purchase_label'] ?? ''));
|
||||
$bundleLabel = $bundleLabel !== '' ? $bundleLabel : 'Buy Release';
|
||||
ob_start();
|
||||
?>
|
||||
<section class="card" style="display:grid; gap:18px; padding-bottom:110px;">
|
||||
<div class="badge">Release</div>
|
||||
<?php if (!$release): ?>
|
||||
<h1 style="margin:0; font-size:28px;">Release not found</h1>
|
||||
<p style="color:var(--muted);">This release is unavailable.</p>
|
||||
<?php else: ?>
|
||||
<div class="release-wrap" style="display:grid; gap:18px;">
|
||||
<div class="release-hero" style="display:grid; grid-template-columns:minmax(0,1fr) 360px; gap:22px; align-items:start;">
|
||||
<div class="release-meta" style="display:grid; gap:14px;">
|
||||
<h1 style="margin:0; font-size:46px; line-height:1.06;"><?= htmlspecialchars((string)$release['title'], ENT_QUOTES, 'UTF-8') ?></h1>
|
||||
<?php if (!empty($release['artist_name'])): ?>
|
||||
<div style="font-size:14px; color:var(--muted);">
|
||||
By
|
||||
<a href="/releases?artist=<?= rawurlencode((string)$release['artist_name']) ?>" style="color:#dfe7fb; text-decoration:none; border-bottom:1px solid rgba(223,231,251,.35);">
|
||||
<?= htmlspecialchars((string)$release['artist_name'], ENT_QUOTES, 'UTF-8') ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px; max-width:640px;">
|
||||
<?php if (!empty($release['catalog_no'])): ?>
|
||||
<div style="padding:10px 12px; border-radius:12px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.22);">
|
||||
<div class="badge" style="font-size:9px;">Catalog</div>
|
||||
<div style="margin-top:6px; font-size:14px;"><?= htmlspecialchars((string)$release['catalog_no'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($release['release_date'])): ?>
|
||||
<div style="padding:10px 12px; border-radius:12px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.22);">
|
||||
<div class="badge" style="font-size:9px;">Release Date</div>
|
||||
<div style="margin-top:6px; font-size:14px;"><?= htmlspecialchars((string)$release['release_date'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if (!empty($release['description'])): ?>
|
||||
<div style="color:var(--muted); line-height:1.75; max-width:760px;">
|
||||
<?= nl2br(htmlspecialchars((string)$release['description'], ENT_QUOTES, 'UTF-8')) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($storePluginEnabled && $releaseStoreEnabled && $bundlePrice > 0): ?>
|
||||
<div style="margin-top:4px;">
|
||||
<form method="post" action="/store/cart/add" style="margin:0;">
|
||||
<input type="hidden" name="item_type" value="release">
|
||||
<input type="hidden" name="item_id" value="<?= (int)($release['id'] ?? 0) ?>">
|
||||
<input type="hidden" name="title" value="<?= htmlspecialchars((string)$release['title'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="cover_url" value="<?= htmlspecialchars($releaseCover, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="currency" value="<?= htmlspecialchars($bundleCurrency, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="price" value="<?= htmlspecialchars(number_format($bundlePrice, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="qty" value="1">
|
||||
<input type="hidden" name="return_url" value="<?= htmlspecialchars($returnUrl, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<button type="submit" class="track-buy-btn">
|
||||
<i class="fa-solid fa-cart-plus"></i>
|
||||
<span><?= htmlspecialchars($bundleLabel, ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($bundleCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($bundlePrice, 2) ?></span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="release-cover-box" style="border-radius:20px; overflow:hidden; background:rgba(255,255,255,0.05); border:1px solid rgba(255,255,255,0.1); aspect-ratio:1/1;">
|
||||
<?php if ($releaseCover !== ''): ?>
|
||||
<img id="releaseCoverMain" src="<?= htmlspecialchars($releaseCover, ENT_QUOTES, 'UTF-8') ?>" alt="" style="width:100%; height:100%; object-fit:cover;">
|
||||
<?php else: ?>
|
||||
<div id="releaseCoverMain" style="height:100%; display:grid; place-items:center; color:var(--muted); letter-spacing:0.3em; font-size:12px;">AUDIOCORE</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($tracks): ?>
|
||||
<div style="padding:16px; border-radius:18px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.22); display:grid; gap:10px;">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
||||
<div class="badge">Tracklist</div>
|
||||
<div style="display:flex; align-items:center; gap:10px;">
|
||||
<div id="releasePlayerNow" style="font-size:12px; color:var(--muted);">Select a track to play sample</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid; gap:8px;">
|
||||
<?php foreach ($tracks as $track): ?>
|
||||
<?php
|
||||
$sample = (string)($track['sample_url'] ?? '');
|
||||
$trackTitle = (string)($track['title'] ?? 'Track');
|
||||
$mix = (string)($track['mix_name'] ?? '');
|
||||
$fullTitle = $mix !== '' ? ($trackTitle . ' (' . $mix . ')') : $trackTitle;
|
||||
?>
|
||||
<div class="track-row" style="display:grid; grid-template-columns:92px minmax(0,1fr) auto; gap:12px; align-items:center; padding:10px 12px; border-radius:12px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.26);">
|
||||
<button type="button"
|
||||
class="track-play-btn"
|
||||
data-src="<?= htmlspecialchars($sample, ENT_QUOTES, 'UTF-8') ?>"
|
||||
data-title="<?= htmlspecialchars($fullTitle, ENT_QUOTES, 'UTF-8') ?>"
|
||||
data-cover="<?= htmlspecialchars($releaseCover, ENT_QUOTES, 'UTF-8') ?>"
|
||||
<?= $sample === '' ? 'disabled' : '' ?>>
|
||||
<i class="fa-solid fa-play"></i> <span>Play</span>
|
||||
</button>
|
||||
<div style="min-width:0;">
|
||||
<div style="font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
|
||||
<?= htmlspecialchars($trackTitle, ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php if ($mix !== ''): ?>
|
||||
<span style="color:var(--muted); font-weight:400;">(<?= htmlspecialchars($mix, ENT_QUOTES, 'UTF-8') ?>)</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div style="font-size:12px; color:var(--muted); margin-top:4px;">
|
||||
<?= (int)($track['track_no'] ?? 0) > 0 ? '#' . (int)$track['track_no'] : 'Track' ?>
|
||||
<?= !empty($track['duration']) ? ' - ' . htmlspecialchars((string)$track['duration'], ENT_QUOTES, 'UTF-8') : '' ?>
|
||||
<?= !empty($track['bpm']) ? ' - ' . htmlspecialchars((string)$track['bpm'], ENT_QUOTES, 'UTF-8') . ' BPM' : '' ?>
|
||||
<?= !empty($track['key_signature']) ? ' - ' . htmlspecialchars((string)$track['key_signature'], ENT_QUOTES, 'UTF-8') : '' ?>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<?php
|
||||
$trackStoreEnabled = (int)($track['store_enabled'] ?? 0) === 1;
|
||||
$trackPrice = (float)($track['track_price'] ?? 0);
|
||||
$trackCurrency = (string)($track['store_currency'] ?? 'GBP');
|
||||
?>
|
||||
<?php if ($storePluginEnabled && $trackStoreEnabled && $trackPrice > 0): ?>
|
||||
<form method="post" action="/store/cart/add" style="margin:0;">
|
||||
<input type="hidden" name="item_type" value="track">
|
||||
<input type="hidden" name="item_id" value="<?= (int)($track['id'] ?? 0) ?>">
|
||||
<input type="hidden" name="title" value="<?= htmlspecialchars($fullTitle, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="cover_url" value="<?= htmlspecialchars($releaseCover, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="currency" value="<?= htmlspecialchars($trackCurrency, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="price" value="<?= htmlspecialchars(number_format($trackPrice, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="qty" value="1">
|
||||
<input type="hidden" name="return_url" value="<?= htmlspecialchars($returnUrl, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<button type="submit" class="track-buy-btn">
|
||||
<i class="fa-solid fa-cart-plus"></i>
|
||||
<span>Buy <?= htmlspecialchars($trackCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($trackPrice, 2) ?></span>
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<div style="font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:0.18em;">
|
||||
<?= $sample !== '' ? 'Sample' : 'No sample' ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($release['credits'])): ?>
|
||||
<div style="padding:12px 14px; border-radius:14px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.2);">
|
||||
<div class="badge" style="font-size:9px;">Credits</div>
|
||||
<div style="margin-top:6px; color:var(--muted); line-height:1.55; font-size:13px;">
|
||||
<?= nl2br(htmlspecialchars((string)$release['credits'], ENT_QUOTES, 'UTF-8')) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<div id="acDock" class="ac-dock" hidden>
|
||||
<div class="ac-dock-inner">
|
||||
<div class="ac-dock-meta">
|
||||
<div id="acDockArt" class="ac-dock-art">AC</div>
|
||||
<div class="ac-dock-title-wrap">
|
||||
<div class="badge" style="font-size:9px;">Now Playing</div>
|
||||
<div id="acDockTitle" class="ac-dock-title">Track sample</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="acDockToggle" class="ac-dock-toggle"><i class="fa-solid fa-play"></i> <span>Play</span></button>
|
||||
<input id="acDockSeek" class="ac-seek" type="range" min="0" max="100" value="0" step="0.1">
|
||||
<div class="ac-dock-time"><span id="acDockCurrent">0:00</span> / <span id="acDockDuration">0:00</span></div>
|
||||
<input id="acDockVolume" class="ac-volume" type="range" min="0" max="1" value="1" step="0.01">
|
||||
<button type="button" id="acDockClose" class="ac-dock-close" aria-label="Close player">X</button>
|
||||
<audio id="acDockAudio" preload="none"></audio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.release-wrap {
|
||||
min-width: 0;
|
||||
}
|
||||
.track-row.is-active { border-color: rgba(34,242,165,.45)!important; background: rgba(34,242,165,.08)!important; }
|
||||
.track-play-btn,.ac-dock-toggle{height:34px;border:1px solid rgba(34,242,165,.35);border-radius:999px;background:rgba(34,242,165,.12);color:#bffff0;font-weight:700;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 14px}
|
||||
.track-buy-btn{height:34px;border:1px solid rgba(255,255,255,.18);border-radius:999px;background:rgba(255,255,255,.08);color:#e9eefc;font-weight:700;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 14px;font-size:12px;white-space:nowrap}
|
||||
.track-buy-btn:hover{background:rgba(255,255,255,.14)}
|
||||
.track-play-btn[disabled]{border-color:rgba(255,255,255,.2);background:rgba(255,255,255,.08);color:var(--muted);cursor:not-allowed}
|
||||
.ac-dock{position:fixed;left:14px;right:14px;bottom:14px;z-index:50}
|
||||
.ac-dock-inner{display:grid;grid-template-columns:minmax(210px,280px) 96px minmax(160px,1fr) 110px 110px 44px;gap:10px;align-items:center;padding:12px;border-radius:14px;border:1px solid rgba(255,255,255,.14);background:rgba(10,11,15,.95)}
|
||||
.ac-dock-meta{display:grid;grid-template-columns:44px minmax(0,1fr);align-items:center;gap:10px;min-width:0}
|
||||
.ac-dock-art{width:44px;height:44px;border-radius:10px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.06);overflow:hidden;display:grid;place-items:center;font-size:10px;color:var(--muted)}
|
||||
.ac-dock-art img{width:100%;height:100%;object-fit:cover;display:block}
|
||||
.ac-dock-title-wrap{min-width:0}.ac-dock-title{margin-top:4px;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.ac-dock-time{font-size:12px;color:var(--muted);font-family:'IBM Plex Mono',monospace}
|
||||
.ac-dock-close{height:34px;border-radius:10px;border:1px solid rgba(255,255,255,.2);background:rgba(255,255,255,.05);color:var(--muted);cursor:pointer}
|
||||
.ac-seek,.ac-volume{-webkit-appearance:none;appearance:none;height:6px;border-radius:999px;background:rgba(255,255,255,.2);outline:none}
|
||||
.ac-seek::-webkit-slider-thumb,.ac-volume::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#22f2a5;border:1px solid rgba(0,0,0,.45)}
|
||||
@media (max-width: 980px) {
|
||||
.release-hero {
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 16px !important;
|
||||
}
|
||||
.release-cover-box {
|
||||
max-width: 420px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.release-meta h1 {
|
||||
font-size: 32px !important;
|
||||
line-height: 1.12 !important;
|
||||
}
|
||||
.track-row {
|
||||
grid-template-columns: 88px minmax(0,1fr) !important;
|
||||
gap: 10px !important;
|
||||
}
|
||||
.track-buy-btn { font-size: 11px; padding: 0 10px; }
|
||||
.ac-dock {
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
}
|
||||
.ac-dock-inner {
|
||||
grid-template-columns: minmax(0,1fr) 96px auto 44px !important;
|
||||
grid-template-areas:
|
||||
"meta toggle time close"
|
||||
"seek seek seek seek";
|
||||
row-gap: 8px;
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.ac-dock-meta { grid-area: meta; gap: 8px; }
|
||||
.ac-dock-art { width: 38px; height: 38px; border-radius: 8px; }
|
||||
.ac-dock-title { font-size: 12px; }
|
||||
.ac-dock-close { grid-area: close; width: 44px; justify-self: end; }
|
||||
.ac-dock-toggle {
|
||||
grid-area: toggle;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
border-radius: 999px;
|
||||
justify-self: center;
|
||||
width: 96px;
|
||||
min-width: 96px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.ac-dock-time {
|
||||
grid-area: time;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
align-self: center;
|
||||
}
|
||||
.ac-seek { grid-area: seek; margin-top: 0; align-self: center; }
|
||||
.ac-volume { display: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const dock = document.getElementById('acDock');
|
||||
const audio = document.getElementById('acDockAudio');
|
||||
const toggle = document.getElementById('acDockToggle');
|
||||
const seek = document.getElementById('acDockSeek');
|
||||
const volume = document.getElementById('acDockVolume');
|
||||
const current = document.getElementById('acDockCurrent');
|
||||
const duration = document.getElementById('acDockDuration');
|
||||
const titleEl = document.getElementById('acDockTitle');
|
||||
const dockArt = document.getElementById('acDockArt');
|
||||
const closeBtn = document.getElementById('acDockClose');
|
||||
const status = document.getElementById('releasePlayerNow');
|
||||
const mainCover = document.getElementById('releaseCoverMain');
|
||||
if (!dock || !audio || !toggle || !seek || !volume || !current || !duration || !titleEl || !closeBtn) return;
|
||||
|
||||
const mainCoverSrc = mainCover && mainCover.tagName === 'IMG' ? (mainCover.getAttribute('src') || '') : '';
|
||||
const defaultCover = mainCoverSrc || <?= json_encode($releaseCover, JSON_UNESCAPED_SLASHES) ?> || '';
|
||||
let activeBtn = null;
|
||||
|
||||
function fmt(sec){ if(!isFinite(sec)||sec<0) return '0:00'; const m=Math.floor(sec/60), s=Math.floor(sec%60); return m+':'+String(s).padStart(2,'0'); }
|
||||
function setPlayState(btn,playing){ if(!btn) return; btn.innerHTML = playing ? '<i class="fa-solid fa-pause"></i> <span>Pause</span>' : '<i class="fa-solid fa-play"></i> <span>Play</span>'; }
|
||||
function setDockArt(src){ if(!dockArt) return; const finalSrc = src || defaultCover; dockArt.innerHTML = finalSrc ? ('<img src="'+finalSrc.replace(/"/g,'"')+'" alt="">') : 'AC'; }
|
||||
|
||||
function setActive(btn,title){
|
||||
document.querySelectorAll('.track-play-btn').forEach((b)=>{ setPlayState(b,false); const row=b.closest('.track-row'); if(row) row.classList.remove('is-active'); });
|
||||
activeBtn = btn;
|
||||
if(btn){ setPlayState(btn,true); const row=btn.closest('.track-row'); if(row) row.classList.add('is-active'); }
|
||||
titleEl.textContent = title || 'Track sample';
|
||||
if(status) status.textContent = title ? ('Now Playing: '+title) : 'Select a track to play sample';
|
||||
}
|
||||
|
||||
function openAndPlay(src,title,btn,cover){
|
||||
if(!src) return;
|
||||
if(dock.hidden) dock.hidden = false;
|
||||
setDockArt(cover || '');
|
||||
if(audio.getAttribute('src') === src){ if(audio.paused) audio.play().catch(()=>{}); else audio.pause(); return; }
|
||||
audio.setAttribute('src', src);
|
||||
setActive(btn,title);
|
||||
audio.play().catch(()=>{ if(btn) setPlayState(btn,false); });
|
||||
}
|
||||
|
||||
document.querySelectorAll('.track-play-btn').forEach((btn)=>{
|
||||
btn.addEventListener('click', ()=>{
|
||||
openAndPlay(btn.getAttribute('data-src') || '', btn.getAttribute('data-title') || 'Track sample', btn, btn.getAttribute('data-cover') || '');
|
||||
});
|
||||
});
|
||||
|
||||
toggle.addEventListener('click', ()=>{ if(!audio.getAttribute('src')) return; if(audio.paused) audio.play().catch(()=>{}); else audio.pause(); });
|
||||
closeBtn.addEventListener('click', ()=>{ audio.pause(); audio.removeAttribute('src'); seek.value='0'; current.textContent='0:00'; duration.textContent='0:00'; setPlayState(toggle,false); setActive(null,''); setDockArt(''); dock.hidden=true; });
|
||||
volume.addEventListener('input', ()=>{ audio.volume = Number(volume.value); });
|
||||
seek.addEventListener('input', ()=>{ if(!isFinite(audio.duration)||audio.duration<=0) return; audio.currentTime = (Number(seek.value)/100)*audio.duration; });
|
||||
|
||||
audio.addEventListener('loadedmetadata', ()=>{ duration.textContent = fmt(audio.duration); });
|
||||
audio.addEventListener('timeupdate', ()=>{ current.textContent = fmt(audio.currentTime); if(isFinite(audio.duration)&&audio.duration>0){ seek.value = String((audio.currentTime/audio.duration)*100); } });
|
||||
audio.addEventListener('play', ()=>{ setPlayState(toggle,true); if(activeBtn) setPlayState(activeBtn,true); });
|
||||
audio.addEventListener('pause', ()=>{ setPlayState(toggle,false); if(activeBtn) setPlayState(activeBtn,false); });
|
||||
audio.addEventListener('ended', ()=>{ setPlayState(toggle,false); if(activeBtn) setPlayState(activeBtn,false); seek.value='0'; current.textContent='0:00'; });
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require __DIR__ . '/../../../../views/site/layout.php';
|
||||
Reference in New Issue
Block a user