what the fuck

This commit is contained in:
boris committed 2026-04-02 13:46:07 +01:00
1 parent c1b1c2f372
commit 72a99a5bcd
69 files changed
+7519 -558

No files matched your search

+261
View File
@@ -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">&times;</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">&times;</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 %}