b2af866cf0
Add persistent German/English switching, translated UI messages and English 12-hour times. Align desktop event actions while preserving mobile layouts and record bilingual project guidance. Include pending diary deletion exclusions. Validate both languages with nine tests.
214 lines
12 KiB
HTML
214 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="{{ language() }}">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>{{ concert.artist }} {{ _('bearbeiten · MetalCircle') }}</title>
|
||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||
<link rel="stylesheet" href="/static/css/style.css">
|
||
<link rel="stylesheet" href="/static/css/language.css">
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<div class="header-inner">
|
||
<a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>
|
||
{% include '_user_menu.html' %}
|
||
</div>
|
||
{% include '_language_switch.html' %}
|
||
</header>
|
||
<main>
|
||
<div class="page-title">
|
||
<h1>{{ _('✏️ Veranstaltung bearbeiten') }}</h1>
|
||
<p>{{ _('Änderungen werden direkt bei der Veranstaltung gespeichert.') }}</p>
|
||
</div>
|
||
<section class="empty">
|
||
<form method="post" action="/concerts/{{ concert.id }}/edit" enctype="multipart/form-data">
|
||
{% if can_edit_details %}
|
||
<p>
|
||
<label>{{ _('Kategorie') }}<br>
|
||
<select name="event_type" id="event-type" required {% if not can_manage_access %}disabled{% endif %}>
|
||
<option value="concert" {% if concert.event_type == 'concert' %}selected{% endif %}>{{ _('Konzert') }}</option>
|
||
<option value="festival" {% if concert.event_type == 'festival' %}selected{% endif %}>Festival</option>
|
||
<option value="other" {% if concert.event_type == 'other' %}selected{% endif %}>{{ _('Sonstiges') }}</option>
|
||
</select>
|
||
</label>
|
||
</p>
|
||
<p id="parent-event-field" {% if concert.event_type != 'other' %}hidden{% endif %}>
|
||
<label>{{ _('Gehört zu') }}<br>
|
||
<select name="parent_event_id" id="parent-event-id">
|
||
<option value="">{{ _('Keine Verknüpfung – eigenständige Karte') }}</option>
|
||
{% for event in linkable_events %}
|
||
<option value="{{ event.id }}" {% if concert.parent_event and concert.parent_event.id == event.id %}selected{% endif %}>{{ event.event_type_label | t }} · {{ event.title }} · {{ event.date }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</label>
|
||
</p>
|
||
{% if can_manage_access %}<fieldset id="event-visibility-field" {% if concert.event_type != 'other' %}hidden{% endif %}>
|
||
<legend>{{ _('Sichtbarkeit') }}</legend>
|
||
<label><input type="radio" name="visibility" value="public" {% if concert.visibility == 'public' %}checked{% endif %}> {{ _('Öffentlich') }}</label><br>
|
||
<label><input type="radio" name="visibility" value="friends" {% if concert.visibility == 'friends' %}checked{% endif %}> {{ _('Nur Freunde') }}</label><br>
|
||
<label><input type="radio" name="visibility" value="private" {% if concert.visibility == 'private' %}checked{% endif %}> {{ _('Privat – nur eingeladene Mitglieder') }}</label>
|
||
</fieldset>
|
||
<fieldset id="event-invitations-field" {% if concert.event_type != 'other' or concert.visibility != 'private' %}hidden{% endif %}>
|
||
<legend>{{ _('Mitglieder einladen') }}</legend>
|
||
{% for member in invitable_users %}
|
||
<label style="display:block;margin:7px 0"><input type="checkbox" name="invited_user_ids" value="{{ member.id }}" {% if member.id in invited_user_ids %}checked{% endif %}> {{ member.display_name }} <small>@{{ member.username }}</small></label>
|
||
{% else %}<p>{{ _('Noch keine weiteren Mitglieder vorhanden.') }}</p>{% endfor %}
|
||
</fieldset>
|
||
{% endif %}
|
||
{% endif %}
|
||
<p>
|
||
<label>{{ _('Titel / Künstler / Band') }}<br>
|
||
<input type="text" name="artist" value="{{ concert.artist }}" {% if not can_edit_title %}disabled{% endif %} required>
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label>{{ _('Bands / Line-up') }} <small>{{ _('(eine Band pro Zeile)') }}</small><br>
|
||
<textarea name="band_names" rows="3" maxlength="4000" {% if not can_edit_title %}disabled{% endif %}>{{ band_names }}</textarea>
|
||
</label>
|
||
<small>{{ _('Diese Namen werden einzeln für die Folgen-Funktion verwendet.') }}</small>
|
||
</p>
|
||
{% if can_edit_details %}
|
||
<p>
|
||
<label>{{ _('Veranstaltungsort') }}<br>
|
||
<input type="text" name="venue_name" value="{{ concert.venue.name if concert.venue.id else '' }}" placeholder="{{ _('Name des Veranstaltungsorts') }}">
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label>{{ _('Straße') }}<br>
|
||
<input type="text" name="street" value="{{ concert.venue.street or '' }}">
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label>{{ _('PLZ und Ort') }}<br>
|
||
<input type="text" name="postal_code" value="{{ concert.venue.postal_code or '' }}" placeholder="{{ _('PLZ') }}">
|
||
<input type="text" name="city" value="{{ concert.venue.city or '' }}" placeholder="{{ _('Ort') }}">
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label>{{ _('Land') }}<br>
|
||
<input type="text" name="country" value="{{ concert.venue.country or '' }}" placeholder="{{ _('Land') }}">
|
||
</label>
|
||
</p>
|
||
<input type="hidden" name="latitude" value="{{ concert.venue.latitude or '' }}">
|
||
<input type="hidden" name="longitude" value="{{ concert.venue.longitude or '' }}">
|
||
<p>
|
||
<label>{{ _('Beginn') }}<br>
|
||
<input type="datetime-local" name="start_datetime" value="{{ concert.start_local }}" required>
|
||
</label>
|
||
</p>
|
||
<p id="festival-end-field" {% if concert.event_type != 'festival' %}hidden{% endif %}>
|
||
<label>{{ _('Ende') }}<br>
|
||
<input type="datetime-local" name="end_datetime" id="festival-end-datetime" value="{{ concert.end_local }}" {% if concert.event_type == 'festival' %}required{% endif %}>
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label>{{ _('Beschreibung') }}<br>
|
||
<textarea name="description" rows="5">{{ concert.description or '' }}</textarea>
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label>{{ _('Ticket-Link') }}<br>
|
||
<input type="url" name="ticket_url" value="{{ concert.ticket_url or '' }}" placeholder="https://...">
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label>{{ _('Ticketpreis') }}<br>
|
||
<input type="number" name="ticket_price" value="{{ concert.ticket_price or '' }}" step="0.01" min="0">
|
||
</label>
|
||
</p>
|
||
<p>
|
||
<label>{{ _('Flyer extern verlinken') }} <small>{{ _('(bevorzugt)') }}</small><br>
|
||
<input type="url" name="flyer_url" value="{{ concert.flyer_url or '' }}" placeholder="https://veranstalter.example/flyer.jpg">
|
||
</label>
|
||
<small>{{ _('Bitte möglichst die offizielle Quelle verwenden. Ein externer Link wird gegenüber einem Upload bevorzugt und unter dem Bild genannt. Feld leeren, um wieder den Upload zu verwenden.') }}</small>
|
||
</p>
|
||
<p>
|
||
<label>{{ _('Neuen Flyer hochladen') }}<br>
|
||
<input type="file" name="flyer" accept="image/jpeg,image/png,image/webp">
|
||
</label>
|
||
<small>{{ _('Ohne neue Datei bleibt der bisherige Flyer erhalten.') }}</small>
|
||
</p>
|
||
{% if concert.flyer_url or concert.flyer_path %}
|
||
<p><img src="{{ concert.flyer_url or concert.flyer_path }}" alt="{{ _('Aktueller Flyer') }}" referrerpolicy="no-referrer" style="max-width: 300px; max-height: 400px; border-radius: 10px;"></p>
|
||
{% endif %}
|
||
{% endif %}
|
||
<div>
|
||
<button type="submit" class="button">{{ _('💾 Änderungen speichern') }}</button>
|
||
<a href="/concerts/{{ concert.id }}" class="button button-secondary">{{ _('Abbrechen') }}</a>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
</main>
|
||
{% if can_edit_details %}
|
||
<script>
|
||
const editConcertForm = document.querySelector('form[enctype="multipart/form-data"]');
|
||
const editFlyerInput = editConcertForm?.querySelector('input[name="flyer"]');
|
||
async function optimizeEditFlyer(file) {
|
||
const bitmap = await createImageBitmap(file);
|
||
const maxSide = 1800;
|
||
const scale = Math.min(1, maxSide / Math.max(bitmap.width, bitmap.height));
|
||
const canvas = document.createElement("canvas");
|
||
canvas.width = Math.max(1, Math.round(bitmap.width * scale));
|
||
canvas.height = Math.max(1, Math.round(bitmap.height * scale));
|
||
canvas.getContext("2d").drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
||
bitmap.close();
|
||
let quality = 0.86;
|
||
let blob;
|
||
do {
|
||
blob = await new Promise(resolve => canvas.toBlob(resolve, "image/jpeg", quality));
|
||
quality -= 0.08;
|
||
} while (blob && blob.size > 900 * 1024 && quality >= 0.5);
|
||
if (!blob) throw new Error("Bild konnte nicht verarbeitet werden");
|
||
return new File([blob], "flyer.jpg", {type: "image/jpeg"});
|
||
}
|
||
|
||
editConcertForm?.addEventListener("submit", async event => {
|
||
if (editConcertForm.dataset.flyerOptimized === "true") return;
|
||
const file = editFlyerInput?.files[0];
|
||
if (!file) return;
|
||
event.preventDefault();
|
||
try {
|
||
const transfer = new DataTransfer();
|
||
transfer.items.add(await optimizeEditFlyer(file));
|
||
editFlyerInput.files = transfer.files;
|
||
editConcertForm.dataset.flyerOptimized = "true";
|
||
editConcertForm.requestSubmit();
|
||
} catch (error) {
|
||
console.error("Flyer optimization failed:", error);
|
||
editConcertForm.dataset.flyerOptimized = "true";
|
||
editConcertForm.requestSubmit();
|
||
}
|
||
});
|
||
|
||
const eventType = document.getElementById("event-type");
|
||
const parentEventField = document.getElementById("parent-event-field");
|
||
const parentEventId = document.getElementById("parent-event-id");
|
||
const festivalEndField = document.getElementById("festival-end-field");
|
||
const festivalEndDatetime = document.getElementById("festival-end-datetime");
|
||
const eventVisibilityField = document.getElementById("event-visibility-field");
|
||
const eventInvitationsField = document.getElementById("event-invitations-field");
|
||
const visibilityInputs = [...document.querySelectorAll('input[name="visibility"]')];
|
||
|
||
function updateEventFields() {
|
||
const isFestival = eventType.value === "festival";
|
||
const isOther = eventType.value === "other";
|
||
festivalEndField.hidden = !isFestival;
|
||
festivalEndDatetime.required = isFestival;
|
||
parentEventField.hidden = !isOther;
|
||
if (eventVisibilityField) {
|
||
eventVisibilityField.hidden = !isOther;
|
||
const isPrivate = isOther && document.querySelector('input[name="visibility"]:checked').value === "private";
|
||
eventInvitationsField.hidden = !isPrivate;
|
||
}
|
||
if (!isFestival) festivalEndDatetime.value = "";
|
||
if (!isOther) parentEventId.value = "";
|
||
}
|
||
|
||
eventType.addEventListener("change", updateEventFields);
|
||
visibilityInputs.forEach(input => input.addEventListener("change", updateEventFields));
|
||
</script>
|
||
{% endif %}
|
||
</body>
|
||
</html>
|