Files
AudioCore/plugins/releases/views/admin/track_edit.php

296 lines
15 KiB
PHP

<?php
$pageTitle = $title ?? 'Edit Track';
$track = $track ?? [];
$release = $release ?? null;
$error = $error ?? '';
$uploadError = (string)($_GET['upload_error'] ?? '');
$storePluginEnabled = (bool)($store_plugin_enabled ?? false);
$trackId = (int)($track['id'] ?? 0);
$fullSourceUrl = $trackId > 0 ? '/admin/releases/tracks/source?track_id=' . $trackId : '';
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 &amp; 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=".mp3,audio/mpeg,audio/mp3" 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 &amp; 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=".mp3,audio/mpeg,audio/mp3" 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>
<?php if ($trackId > 0 && (string)($track['full_file_url'] ?? '') !== ''): ?>
<div class="admin-card" style="padding:14px; background: rgba(10,10,12,0.6);">
<div class="label">Generate sample from full track</div>
<div id="trackWaveform" style="margin-top:8px; height:96px; border-radius:12px; border:1px solid rgba(255,255,255,.12); background:rgba(0,0,0,.25);"></div>
<div style="display:grid; gap:10px; margin-top:10px;">
<input type="hidden" id="sampleStart" name="sample_start" value="0">
<input type="hidden" id="sampleDuration" name="sample_duration" value="90">
<div style="display:flex; flex-wrap:wrap; align-items:center; gap:10px;">
<button type="button" class="btn outline small" id="previewSampleRegionBtn">Preview</button>
<div class="input" style="display:flex; align-items:center; gap:10px; min-height:42px; width:clamp(240px,42vw,440px);">
<span style="font-size:11px; letter-spacing:.18em; text-transform:uppercase; color:var(--muted);">Range</span>
<span id="sampleRangeText" style="font-family:'IBM Plex Mono',monospace; color:var(--text);">0s -> 90s</span>
<span id="sampleDurationText" style="margin-left:auto; font-family:'IBM Plex Mono',monospace; color:var(--muted);">90s</span>
</div>
<div style="margin-left:auto; display:flex; gap:10px;">
<button type="button" class="btn outline small" id="resetSampleRegionBtn">Reset range</button>
<button type="submit" class="btn small" formaction="/admin/releases/tracks/sample/generate" formmethod="post">Generate sample</button>
</div>
</div>
</div>
</div>
<?php endif; ?>
<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 src="https://unpkg.com/wavesurfer.js@7/dist/wavesurfer.min.js"></script>
<script src="https://unpkg.com/wavesurfer.js@7/dist/plugins/regions.min.js"></script>
<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';
});
}
const waveWrap = document.getElementById('trackWaveform');
const previewRegionBtn = document.getElementById('previewSampleRegionBtn');
const startInput = document.getElementById('sampleStart');
const durationInput = document.getElementById('sampleDuration');
const rangeText = document.getElementById('sampleRangeText');
const durationText = document.getElementById('sampleDurationText');
const resetRegionBtn = document.getElementById('resetSampleRegionBtn');
if (waveWrap && startInput && durationInput && window.WaveSurfer) {
let fixedDuration = 90;
let regionRef = null;
let isPreviewPlaying = false;
const ws = WaveSurfer.create({
container: waveWrap,
waveColor: 'rgba(175,190,220,0.35)',
progressColor: '#22f2a5',
cursorColor: '#22f2a5',
barWidth: 2,
barGap: 2,
height: 92,
});
const regions = ws.registerPlugin(window.WaveSurfer.Regions.create());
ws.load('<?= htmlspecialchars($fullSourceUrl, ENT_QUOTES, 'UTF-8') ?>');
function syncRange(startSec) {
const start = Math.max(0, Math.floor(startSec));
const end = Math.max(start, Math.floor(start + fixedDuration));
startInput.value = String(start);
durationInput.value = String(fixedDuration);
if (rangeText) rangeText.textContent = start + 's → ' + end + 's';
if (durationText) durationText.textContent = fixedDuration + 's';
}
ws.on('ready', () => {
const total = Math.floor(ws.getDuration() || 0);
fixedDuration = Math.max(10, Math.min(90, total > 0 ? total : 90));
syncRange(0);
regionRef = regions.addRegion({
start: 0,
end: fixedDuration,
drag: true,
resize: false,
color: 'rgba(34,242,165,0.20)',
});
const updateRegion = () => {
if (!regionRef) return;
let start = regionRef.start || 0;
const maxStart = Math.max(0, (ws.getDuration() || fixedDuration) - fixedDuration);
if (start < 0) start = 0;
if (start > maxStart) start = maxStart;
regionRef.setOptions({ start: start, end: start + fixedDuration });
syncRange(start);
};
regionRef.on('update-end', updateRegion);
});
ws.on('interaction', () => {});
ws.on('audioprocess', () => {
if (!isPreviewPlaying || !regionRef) return;
const t = ws.getCurrentTime() || 0;
if (t >= regionRef.end) {
ws.pause();
ws.setTime(regionRef.start);
isPreviewPlaying = false;
if (previewRegionBtn) previewRegionBtn.textContent = 'Preview';
}
});
ws.on('pause', () => {
if (isPreviewPlaying) {
isPreviewPlaying = false;
if (previewRegionBtn) previewRegionBtn.textContent = 'Preview';
}
});
if (resetRegionBtn) {
resetRegionBtn.addEventListener('click', () => {
if (!regionRef) return;
regionRef.setOptions({ start: 0, end: fixedDuration });
syncRange(0);
});
}
if (previewRegionBtn) {
previewRegionBtn.addEventListener('click', () => {
if (!regionRef) return;
if (isPreviewPlaying) {
ws.pause();
isPreviewPlaying = false;
previewRegionBtn.textContent = 'Preview';
return;
}
ws.setTime(regionRef.start);
ws.play();
isPreviewPlaying = true;
previewRegionBtn.textContent = 'Stop preview';
});
}
} else if (resetRegionBtn) {
resetRegionBtn.addEventListener('click', () => {
if (startInput) startInput.value = '0';
});
}
})();
</script>
<?php
$content = ob_get_clean();
require __DIR__ . '/../../../../modules/admin/views/layout.php';