Initial commit

This commit is contained in:
2025-12-10 22:47:38 +01:00
parent e98d8c5c1b
commit f78c4d389d
2870 changed files with 641720 additions and 0 deletions

391
templates/edit_board.html Normal file
View File

@@ -0,0 +1,391 @@
{% extends 'base.html' %}
{% block title %}Bewerk Liturgiebord - Digitale Liturgie{% endblock %}
{% block content %}
<style>
.edit-board-container {
padding: 1rem 1rem;
background: white;
border-radius: 0.5rem;
margin: 0 auto 1rem auto;
}
.board-title {
font-size: 2rem;
font-weight: 600;
margin-bottom: 1rem;
color: #000000;
text-align: left;
}
.lines-section {
margin-bottom: 2.5rem;
}
.line-input-group {
margin-bottom: 1.25rem;
}
.line-input-group label {
display: block;
margin-bottom: 0.75rem;
font-weight: 700;
color: #374151;
}
.line-input-group input {
width: 100%;
padding: 0.75rem 1rem;
font-size: 1.125rem;
border-radius: 0.5rem;
border: 1px solid #d1d5db;
box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.line-input-group input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 5px #93c5fd;
}
.button-group {
display: flex;
gap: 1rem;
justify-content: flex-start;
margin-top: 1.5rem;
}
.button-group button {
padding: 0.75rem 1.75rem;
font-size: 1rem;
border-radius: 0.5rem;
font-weight: 700;
transition: background-color 0.15s ease-in-out;
}
.schedule-section {
max-width: 100%;
margin-bottom: 2.5rem;
}
.schedule-section h3 {
margin-bottom: 1rem;
}
.schedule-section table {
width: 100%;
}
.preview-section {
max-width: 100%;
text-align: center;
font-family: 'Lora', serif;
}
.preview-wrapper {
width: 250px;
height: 444px; /* 9:16 aspect ratio */
margin: 0 auto;
background: #222;
border-radius: 0.5rem;
overflow: hidden;
}
iframe#live-preview-iframe {
width: 1080px;
height: 1920px;
border: 0;
pointer-events: none;
transform-origin: top left;
transform: scale(0.23);
margin: 0;
display: block;
}
@media (max-width: 767px) {
.edit-board-container {
padding: 15px;
margin-bottom: 1rem;
border-radius: 0.5rem;
}
.board-title {
font-size: 1.75rem;
margin-bottom: 1.5rem;
}
.button-group {
flex-direction: column;
gap: 8px;
}
.button-group button {
width: 100%;
}
.preview-wrapper {
width: 180px;
height: 320px;
margin: 0 auto;
border-radius: 0.5rem;
background: #222;
overflow: hidden;
}
iframe#live-preview-iframe {
transform: scale(0.23);
position: static;
width: 1080px;
height: 1920px;
margin: 0 auto;
display: block;
}
}
/* Highlight for drag-over between lines in preview */
.liturgie-line.line-over {
background: rgba(255,255,255,0.22);
border: 2px dashed #fff;
box-sizing: border-box;
outline: none;
transition: background 0.12s, border 0.12s;
}
.liturgie-line.swap-animate {
animation: swap-pulse 0.35s;
}
@keyframes swap-pulse {
0% { background: #ffe066; }
60% { background: #fff3cd; }
100% { background: none; }
}
</style>
<div class="edit-board-container">
<div style="display: flex; gap: 2rem; align-items: flex-start; flex-wrap: wrap;">
<!-- Left: Live (editable) preview -->
<form method="post" id="boardForm" style="flex: 1 1 300px; min-width: 250px; max-width:250px;">
<div class="preview-section">
<div class="preview-wrapper"
style="width:250px; height:444px; background: #222; border-radius: 0.5rem; overflow: hidden; position:relative;
background-image: {% if board.background_image %}url('{% if board.background_image.startswith('default_') %}{{ url_for('static', filename=board.background_image) }}{% else %}{{ url_for('uploaded_file', filename=board.background_image) }}{% endif %}'){% else %}none{% endif %}; background-size:cover; background-position:center;">
{% if board.background_image %}
<div class="liturgie-bg-overlay" style="
content:''; position:absolute; top:0; left:0; right:0; bottom:0;
background:rgba(0,0,0,0.3); z-index:1; pointer-events:none;">
</div>
{% endif %}
{% if schedule_active %}
{% set matched_schedules = schedules|selectattr('content', 'equalto', schedule_content)|list %}
<div style="position:absolute; top:0; left:0; right:0; bottom:0; z-index:10; display:flex; justify-content:center; align-items:center; background: rgba(0,0,0,0.7); color:white; font-weight:bold; font-size:1rem; text-align:center; padding: 1rem;">
Geplande dienst ({{ matched_schedules[0].name if matched_schedules|length > 0 else 'Onbekend' }}) is nu actief
</div>
{% endif %}
<div class="liturgie-board"
style="width:100%; height:100%; display:flex; flex-direction:column; justify-content:center; align-items:flex-start; gap:0.6vh; padding-left:5%; position:relative; z-index:2;">
{% if schedule_active %}
{% set lines = schedule_content.split('\\n') %}
{% for i in range(10) %}
<div class="liturgie-line" style="width:90%; font-size:28px; line-height:1.2; text-shadow:2px 2px 8px #000, 0 0 4px #000; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin:0; padding:0; color:#fff; text-align:left;">
{{ lines[i] if lines|length > i else ' ' }}
</div>
{% endfor %}
{% else %}
{% for i in range(1, 11) %}
<div class="liturgie-line"
contenteditable="true"
data-line="line{{ i }}"
draggable="true"
style="width:90%; font-size:28px; line-height:1.2; text-shadow:2px 2px 8px #000, 0 0 4px #000; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin:0; padding:0; color:#fff; text-align:left;"
>{{ board['line' ~ i] or ' ' }}</div>
{% endfor %}
{% endif %}
</div>
</div>
{% if schedule_active %}
<p style="color: red;">Er is een dienst actief! Pas deze aan via de planning.</p>
{% endif %}
</div>
</form>
<!-- Right: Schedule section -->
<div class="schedule-section" style="flex: 1 1 380px; min-width:330px;">
<form method="post" style="margin-bottom: 1rem;">
<input type="hidden" name="form_type" value="name">
<div style="display:flex; gap:8px; align-items:center;">
<input id="boardNameInput" name="name" type="text" value="{{ board.name }}" style="font-size:1.1rem; padding:0.3em 0.6em; border-radius:0.3em; border:1px solid #aaa; width:60%;">
<button type="submit" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded hover:bg-yellow-300 font-semibold transition" style="margin-left:5px;">Opslaan</button>
</div>
</form>
<h3 class="text-xl font-semibold mb-3">Geplande diensten</h3>
<div class="overflow-x-auto rounded-lg border border-gray-300">
{% if schedules|length == 0 %}
<p class="p-4">Geen geplande inhoud.</p>
{% else %}
<table class="min-w-full divide-y divide-gray-200 bg-white">
<thead class="bg-gray-100">
<tr>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Naam</th>
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Datum</th>
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Starttijd</th>
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Eindtijd</th>
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Acties</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{% for s in schedules %}
<tr>
<td class="whitespace-nowrap px-6 py-4 text-gray-900 flex items-center gap-2">
<span style="display:inline-block; width:0.75rem; height:0.75rem; border-radius:9999px;"
title="{% if schedule_active and active_schedule and s.id == active_schedule.id %}Actief{% else %}Niet actief{% endif %}"
class="{% if schedule_active and active_schedule and s.id == active_schedule.id %}bg-green-500{% else %}bg-red-500{% endif %}">
</span>
{{ s.name }}
</td>
<td class="px-6 py-4 text-center text-gray-900">{{ s.date.strftime('%Y-%m-%d') }}</td>
<td class="px-6 py-4 text-center text-gray-900">{{ s.start_time.strftime('%H:%M') }}</td>
<td class="px-6 py-4 text-center text-gray-900">{{ s.end_time.strftime('%H:%M') }}</td>
<td class="px-6 py-4 text-center">
<!-- Removed delete button here: schedules can't be deleted from board edit page -->
<a href="{{ url_for('edit_global_schedule', schedule_id=s.id) }}" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded hover:bg-yellow-300 font-semibold transition">Bewerken</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<a href="{{ url_for('add_global_schedule') }}" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded hover:bg-yellow-300 font-semibold inline-block mt-3 transition">Dienst toevoegen</a>
<div class="button-group mt-3">
<button type="button" class="bg-white text-black border border-black px-6 py-3 rounded font-semibold hover:bg-gray-100 transition"
id="save-and-upload-bg"
data-edit-url="{{ url_for('edit_board', board_id=board.id) }}"
data-upload-url="{{ url_for('upload_background', board_id=board.id) }}">
Achtergrondafbeelding wijzigen
</button>
<!-- Removed Opslaan button as live save is implemented -->
</div>
</div>
</div>
</div>
{% if not schedule_active %}
<script>
// --- Line drag-and-swap logic ---
document.addEventListener('DOMContentLoaded', function () {
let dragSrc = null;
const lines = document.querySelectorAll('.liturgie-line[data-line]');
lines.forEach(line => {
line.addEventListener('dragstart', function (e) {
dragSrc = this;
this.classList.add('dragging');
});
line.addEventListener('dragend', function (e) {
this.classList.remove('dragging');
dragSrc = null;
lines.forEach(l => l.classList.remove('line-over'));
});
line.addEventListener('dragover', function (e) {
e.preventDefault();
if (this !== dragSrc) this.classList.add('line-over');
});
line.addEventListener('dragleave', function (e) {
this.classList.remove('line-over');
});
line.addEventListener('drop', function (e) {
e.preventDefault();
this.classList.remove('line-over');
if (dragSrc && dragSrc !== this) {
// Only swap textContent, not the whole element
const tmp = this.textContent;
this.textContent = dragSrc.textContent;
dragSrc.textContent = tmp;
// animation for both lines
this.classList.add('swap-animate');
dragSrc.classList.add('swap-animate');
setTimeout(() => {
this.classList.remove('swap-animate');
dragSrc.classList.remove('swap-animate');
}, 400);
// trigger input event for autosave
this.dispatchEvent(new Event('input'));
dragSrc.dispatchEvent(new Event('input'));
}
});
});
});
// --- End drag-and-swap logic ---
// On submit, copy preview lines to hidden fields
const boardForm = document.getElementById('boardForm');
if (boardForm) {
for (let i = 1; i <= 10; i++) {
// create hidden inputs for each line
let hidden = document.createElement('input');
hidden.type = 'hidden';
hidden.name = 'line'+i;
hidden.id = 'hidden-line'+i;
boardForm.appendChild(hidden);
}
async function saveLines() {
const formData = new FormData();
for (let i = 1; i <= 10; i++) {
const text = document.querySelector('.liturgie-line[data-line="line'+i+'"]').textContent;
formData.append('line'+i, text);
}
const response = await fetch(boardForm.action, {
method: 'POST',
body: formData,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
if (!response.ok) {
console.error('Failed to save lines.');
}
}
// Add auto save event listeners to editable lines with debounce
for (let i = 1; i <= 10; i++) {
const editableLine = document.querySelector('.liturgie-line[data-line="line'+i+'"]');
if (editableLine) {
editableLine.addEventListener('input', () => {
if (window.autoSaveTimeout) clearTimeout(window.autoSaveTimeout);
window.autoSaveTimeout = setTimeout(saveLines, 1000);
});
}
}
// Prevent form's default submit to avoid page reload on autosave
boardForm.addEventListener('submit', function(e) {
if (document.getElementById('redirect_to_upload').value === '0') {
e.preventDefault();
saveLines();
}
});
}
// Added script for wallpaper button
document.addEventListener('DOMContentLoaded', function() {
const btn = document.getElementById('save-and-upload-bg');
if (btn) {
btn.addEventListener('click', function() {
const uploadUrl = this.getAttribute('data-upload-url');
if (uploadUrl) {
window.location.href = uploadUrl;
}
});
}
});
</script>
{% else %}
<script>
// When schedule is active, there's nothing to submit for the lines and nothing to sync.
</script>
{% endif %}
{% endblock %}