what the fuck
This commit is contained in:
1 parent
c1b1c2f372
commit
72a99a5bcd
69 files changed
+7519
-558
No files matched your search
@@ -0,0 +1,261 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title %}{{ isEdit ? 'Edit Album' : 'Create Album' }}{% endblock %}
|
||||
{% block body %}
|
||||
<h1 class="h4 mb-3">{{ isEdit ? 'Edit album' : 'Create album' }}</h1>
|
||||
<style>
|
||||
.album-editor-cover {
|
||||
aspect-ratio: 1 / 1;
|
||||
width: 100%;
|
||||
border: 2px dashed #d29ab2;
|
||||
border-radius: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #faf6f8;
|
||||
color: #8a4f69;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.album-editor-cover:hover {
|
||||
border-color: #b55b80;
|
||||
background: #f6eef2;
|
||||
}
|
||||
|
||||
.album-editor-cover__preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.track-col-disc,
|
||||
.track-col-number {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.track-col-duration {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.track-meta-input {
|
||||
min-height: 2.8rem;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.track-title-input,
|
||||
.track-duration-input {
|
||||
min-height: 2.8rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
{{ form_start(form, {attr: {class: 'vstack gap-4', novalidate: 'novalidate'}}) }}
|
||||
{{ form_errors(form) }}
|
||||
|
||||
<div class="row g-4 mb-2">
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body vstack gap-3">
|
||||
<div>
|
||||
{{ form_widget(form.coverUpload, {attr: {class: 'd-none', accept: 'image/*'}}) }}
|
||||
{% set has_existing_cover = existingCoverUrl is defined and existingCoverUrl %}
|
||||
<label for="{{ form.coverUpload.vars.id }}" class="album-editor-cover" id="cover-upload-trigger">
|
||||
<img
|
||||
id="cover-upload-preview"
|
||||
class="album-editor-cover__preview{{ has_existing_cover ? '' : ' d-none' }}"
|
||||
{% if has_existing_cover %}src="{{ existingCoverUrl }}"{% endif %}
|
||||
alt="Album cover preview"
|
||||
>
|
||||
<span id="cover-upload-placeholder" class="{{ has_existing_cover ? 'd-none' : '' }}">Upload Image</span>
|
||||
</label>
|
||||
{{ form_errors(form.coverUpload) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form_label(form.name) }}
|
||||
{{ form_widget(form.name, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.name) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form_label(form.artistsCsv) }}
|
||||
{{ form_widget(form.artistsCsv, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.artistsCsv) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form_label(form.releaseDate) }}
|
||||
{{ form_widget(form.releaseDate, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.releaseDate) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form_label(form.genresCsv) }}
|
||||
{{ form_widget(form.genresCsv, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.genresCsv) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ form_label(form.externalUrl) }}
|
||||
{{ form_widget(form.externalUrl, {attr: {class: 'form-control', placeholder: 'https://...'}}) }}
|
||||
{{ form_errors(form.externalUrl) }}
|
||||
</div>
|
||||
<div class="d-none">
|
||||
{{ form_widget(form.totalTracks) }}
|
||||
{{ form_errors(form.totalTracks) }}
|
||||
</div>
|
||||
{% if isEdit and albumOwner %}
|
||||
<small class="text-secondary">
|
||||
Owner: {{ albumOwner.displayName ?? albumOwner.userIdentifier }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="h5 mb-0">Tracklist</h2>
|
||||
<span class="text-secondary"><span id="track-count">{{ trackRows|filter(t => t.title is not empty)|length }}</span> tracks</span>
|
||||
</div>
|
||||
<div class="mui-table-wrapper w-100">
|
||||
<table class="mui-table mui-table--compact mui-table--striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Disc</th>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col" class="text-end">Duration</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="track-editor-body">
|
||||
{% for track in trackRows %}
|
||||
<tr>
|
||||
<td class="mui-table__number track-col-disc">
|
||||
<input class="form-control track-meta-input" type="text" inputmode="numeric" name="track_disc[]" value="{{ track.disc }}">
|
||||
</td>
|
||||
<td class="mui-table__number track-col-number">
|
||||
<input class="form-control track-meta-input" type="text" inputmode="numeric" name="track_number[]" value="{{ track.track }}">
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control track-title-input" type="text" name="track_title[]" value="{{ track.title }}" maxlength="512" placeholder="Track title">
|
||||
</td>
|
||||
<td class="text-end track-col-duration">
|
||||
<input class="form-control track-duration-input text-end" type="text" inputmode="numeric" name="track_duration[]" value="{{ track.duration }}" placeholder="m:ss">
|
||||
</td>
|
||||
<td class="text-end" style="width:1%">
|
||||
<button type="button" class="btn btn-outline-danger btn-sm track-remove-btn" title="Remove track">×</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button type="button" id="add-track-row" class="btn btn-outline-secondary btn-sm">Add track</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-success" type="submit">{{ isEdit ? 'Save changes' : 'Create album' }}</button>
|
||||
<a class="btn btn-link" href="{{ isEdit and albumId ? path('album_show', {id: albumId}) : path('album_search') }}">Cancel</a>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
|
||||
<template id="track-row-template">
|
||||
<tr>
|
||||
<td class="mui-table__number track-col-disc">
|
||||
<input class="form-control track-meta-input" type="text" inputmode="numeric" name="track_disc[]" value="1">
|
||||
</td>
|
||||
<td class="mui-table__number track-col-number">
|
||||
<input class="form-control track-meta-input" type="text" inputmode="numeric" name="track_number[]" value="1">
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control track-title-input" type="text" name="track_title[]" value="" maxlength="512" placeholder="Track title">
|
||||
</td>
|
||||
<td class="text-end track-col-duration">
|
||||
<input class="form-control track-duration-input text-end" type="text" inputmode="numeric" name="track_duration[]" value="" placeholder="m:ss">
|
||||
</td>
|
||||
<td class="text-end" style="width:1%">
|
||||
<button type="button" class="btn btn-outline-danger btn-sm track-remove-btn" title="Remove track">×</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const trackBody = document.getElementById('track-editor-body');
|
||||
const addBtn = document.getElementById('add-track-row');
|
||||
const rowTemplate = document.getElementById('track-row-template');
|
||||
const countNode = document.getElementById('track-count');
|
||||
const totalTracksInput = document.getElementById('{{ form.totalTracks.vars.id }}');
|
||||
const coverInput = document.getElementById('{{ form.coverUpload.vars.id }}');
|
||||
const coverPreview = document.getElementById('cover-upload-preview');
|
||||
const coverPlaceholder = document.getElementById('cover-upload-placeholder');
|
||||
if (!trackBody || !addBtn || !rowTemplate || !countNode || !totalTracksInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recalcTrackCount = () => {
|
||||
const titles = Array.from(trackBody.querySelectorAll('.track-title-input'));
|
||||
const count = titles.filter((input) => input.value.trim() !== '').length;
|
||||
countNode.textContent = String(count);
|
||||
totalTracksInput.value = String(count);
|
||||
};
|
||||
|
||||
const bindRow = (row) => {
|
||||
const removeBtn = row.querySelector('.track-remove-btn');
|
||||
const titleInput = row.querySelector('.track-title-input');
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener('click', () => {
|
||||
row.remove();
|
||||
recalcTrackCount();
|
||||
});
|
||||
}
|
||||
if (titleInput) {
|
||||
titleInput.addEventListener('input', recalcTrackCount);
|
||||
}
|
||||
};
|
||||
|
||||
Array.from(trackBody.querySelectorAll('tr')).forEach((row) => bindRow(row));
|
||||
|
||||
addBtn.addEventListener('click', () => {
|
||||
const fragment = rowTemplate.content.cloneNode(true);
|
||||
const row = fragment.querySelector('tr');
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
const currentRows = trackBody.querySelectorAll('tr').length;
|
||||
const trackNumberInput = row.querySelector('input[name="track_number[]"]');
|
||||
if (trackNumberInput) {
|
||||
trackNumberInput.value = String(currentRows + 1);
|
||||
}
|
||||
bindRow(row);
|
||||
trackBody.appendChild(row);
|
||||
});
|
||||
|
||||
if (coverInput && coverPreview && coverPlaceholder) {
|
||||
coverInput.addEventListener('change', () => {
|
||||
const file = coverInput.files && coverInput.files.length > 0 ? coverInput.files[0] : null;
|
||||
if (!file) {
|
||||
coverPreview.src = '';
|
||||
coverPreview.classList.add('d-none');
|
||||
coverPlaceholder.classList.remove('d-none');
|
||||
return;
|
||||
}
|
||||
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
coverPreview.src = objectUrl;
|
||||
coverPreview.classList.remove('d-none');
|
||||
coverPlaceholder.classList.add('d-none');
|
||||
coverPreview.onload = () => URL.revokeObjectURL(objectUrl);
|
||||
});
|
||||
}
|
||||
|
||||
recalcTrackCount();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user