312 lines
13 KiB
HTML
Executable File
312 lines
13 KiB
HTML
Executable File
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Control Panel - Church Announcements</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<!-- SortableJS & Flatpickr CSS -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.0/Sortable.min.js"></script>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
||
<style>
|
||
/* Basic styling */
|
||
body { background: #222; color: #fff; font-family: sans-serif; margin: 0; padding: 0; }
|
||
#pageTitle { text-align: center; margin: 1rem 0; font-family: 'Roboto', sans-serif; text-transform: uppercase; font-weight: bold; letter-spacing: 1px; font-size: 2rem; }
|
||
.preview-container { max-width: 1000px; margin: 0 auto 1rem; text-align: center; }
|
||
.preview-iframe { width: 50%; aspect-ratio: 16/9; border: 3px solid #fff; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.5); }
|
||
.tab-nav { display: flex; justify-content: center; margin-bottom: 1rem; }
|
||
.tab-nav button { background: #444; border: none; color: #fff; padding: .5rem 1rem; margin: 0 .5rem; cursor: pointer; font-size: 1rem; border-radius: 4px; transition: background .3s; }
|
||
.tab-nav button:hover { background: #666; }
|
||
.active-tab { background: #4caf50 !important; }
|
||
#announcementsSection { display: block; }
|
||
#bibleSection { display: none; max-width: 1000px; margin: 0 auto; padding: 1rem 0; }
|
||
#bibleSection h2 { text-align: center; margin-bottom: 1rem; }
|
||
|
||
/* Slider-style toggle */
|
||
#activeFilterContainer { text-align: right; padding: 0 1rem; }
|
||
.toggle-label { position: relative; display: inline-block; width: 60px; height: 34px; }
|
||
.toggle-label input { opacity: 0; width: 0; height: 0; position: absolute; }
|
||
.toggle-slider { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: #888; border-radius: 34px; transition: .4s; cursor: pointer; }
|
||
.toggle-slider:before { content: ""; position: absolute; height: 26px; width: 26px; left: 4px; bottom: 4px; background: #fff; border-radius: 50%; transition: .4s; }
|
||
.toggle-label input:checked + .toggle-slider { background: #4caf50; }
|
||
.toggle-label input:checked + .toggle-slider:before { transform: translateX(26px); }
|
||
.toggle-slider .icon { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 14px; pointer-events: none; opacity: 0; transition: opacity .2s; color: #4caf50; }
|
||
.toggle-label input:checked + .toggle-slider .icon.check { opacity: 1; }
|
||
.toggle-label input:not(:checked) + .toggle-slider .icon.cross { opacity: 1; }
|
||
|
||
/* Slides grid */
|
||
.slides-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(325px,1fr)); gap: 2rem; padding: 1rem; justify-items: center; }
|
||
.slide-item { width: 150px; cursor: pointer; position: relative; transition: outline-color .3s ease; }
|
||
.slide-item img { width: 100%; border-radius: 10px; outline: 3px solid transparent; outline-offset: -3px; box-shadow: 0 0 10px rgba(0,0,0,0.5); transition: outline-color .3s ease; }
|
||
.slide-item.active img { outline-color: lime; }
|
||
@media(min-width:768px){ .slide-item{ width:325px; } }
|
||
|
||
/* Bible search table */
|
||
#bibleSearchContainer { text-align: center; margin-bottom: 1rem; }
|
||
#bibleSearchInput { width: 60%; padding: .5rem; font-size:1rem; background:#333; border:1px solid #555; border-radius:4px; color:#fff; }
|
||
#bibleSearchButton { padding:.5rem 1rem; margin-left:.5rem; background:#4caf50; border:none; border-radius:4px; color:#fff; cursor:pointer; }
|
||
#bibleTable { width:100%; border-collapse:collapse; }
|
||
#bibleTable th, #bibleTable td { border:1px solid #555; padding:.5rem; text-align:left; }
|
||
#bibleTable th { background:#333; }
|
||
|
||
.custom-context-menu { position:absolute; display:none; z-index:9999; background:#333; border:1px solid #444; border-radius:8px; padding:8px; box-shadow:0 2px 5px rgba(0,0,0,0.5); min-width:160px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1 id="pageTitle">Announcements</h1>
|
||
|
||
<div class="preview-container">
|
||
<iframe id="previewIframe" class="preview-iframe" src="http://127.0.0.1:5000/"></iframe>
|
||
</div>
|
||
|
||
<!-- Slider toggle -->
|
||
<div id="activeFilterContainer">
|
||
<label class="toggle-label">
|
||
<input type="checkbox" id="activeFilterToggle">
|
||
<span class="toggle-slider">
|
||
<span class="icon check">✔</span>
|
||
<span class="icon cross">✖</span>
|
||
</span>
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Tabs -->
|
||
<div class="tab-nav">
|
||
<button id="announcementsTab" class="active-tab">Announcements</button>
|
||
<button id="bibleTab">Bible Verses</button>
|
||
</div>
|
||
|
||
<!-- Announcements Section -->
|
||
<div id="announcementsSection">
|
||
<div class="slides-container" id="slidesContainer">
|
||
{% for slide, data in statuses %}
|
||
<div class="slide-item {% if data.active %}active{% endif %}"
|
||
data-slide="{{ slide }}"
|
||
data-duration="{{ data.duration|default(7) }}"
|
||
data-title="{{ data.title|default(slide.split('.')[0]) }}"
|
||
data-end_date="{{ data.end_date|default('') }}">
|
||
<img src="{{ slide_urls[slide] }}" alt="{{ slide }}">
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bible Verses Section -->
|
||
<div id="bibleSection">
|
||
<h2>Bible Verses</h2>
|
||
<div id="bibleSearchContainer">
|
||
<input type="text" id="bibleSearchInput" placeholder="Search verses..." />
|
||
<button id="bibleSearchButton">Search</button>
|
||
</div>
|
||
<table id="bibleTable">
|
||
<thead>
|
||
<tr>
|
||
<th>Book</th><th>Chapter</th><th>Verse</th><th>Text</th><th>Translation</th><th>Add/Remove</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Context Menu -->
|
||
<div class="custom-context-menu" id="customContextMenu">
|
||
<!-- existing context-menu items + submenus -->
|
||
</div>
|
||
|
||
<!-- Scripts -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||
<script>
|
||
// 1) Load persisted registry
|
||
let bibleRegistry = [];
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
fetch('/bible_registry')
|
||
.then(r => r.json())
|
||
.then(data => { bibleRegistry = data; })
|
||
.catch(console.error);
|
||
});
|
||
|
||
// 2) Tab switching
|
||
const announcementsTab = document.getElementById('announcementsTab'),
|
||
bibleTab = document.getElementById('bibleTab'),
|
||
announcementsSec = document.getElementById('announcementsSection'),
|
||
bibleSec = document.getElementById('bibleSection'),
|
||
pageTitle = document.getElementById('pageTitle'),
|
||
activeToggle = document.getElementById('activeFilterToggle'),
|
||
slidesContainer = document.getElementById('slidesContainer');
|
||
|
||
announcementsTab.addEventListener('click', () => {
|
||
announcementsSec.style.display = 'block';
|
||
bibleSec.style.display = 'none';
|
||
announcementsTab.classList.add('active-tab');
|
||
bibleTab.classList.remove('active-tab');
|
||
pageTitle.innerText = 'Announcements';
|
||
activeToggle.parentElement.style.display = 'block';
|
||
});
|
||
bibleTab.addEventListener('click', () => {
|
||
announcementsSec.style.display = 'none';
|
||
bibleSec.style.display = 'block';
|
||
bibleTab.classList.add('active-tab');
|
||
announcementsTab.classList.remove('active-tab');
|
||
pageTitle.innerText = 'Bible Verses';
|
||
activeToggle.parentElement.style.display = 'none';
|
||
});
|
||
|
||
// 3) Drag-and-drop ordering
|
||
let isDragging = false;
|
||
Sortable.create(slidesContainer, {
|
||
animation: 150, delay: 50,
|
||
onStart: () => { isDragging = true; },
|
||
onEnd: () => {
|
||
isDragging = false;
|
||
const order = Array.from(slidesContainer.children).map(c => c.dataset.slide);
|
||
fetch('/update_order', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ order })
|
||
});
|
||
}
|
||
});
|
||
|
||
// 4) Toggle hiding inactive slides
|
||
activeToggle.addEventListener('change', () => {
|
||
slidesContainer.querySelectorAll('.slide-item').forEach(item => {
|
||
item.style.display = activeToggle.checked && !item.classList.contains('active')
|
||
? 'none' : 'block';
|
||
});
|
||
});
|
||
|
||
// 5) Click slide to activate/deactivate
|
||
slidesContainer.addEventListener('click', e => {
|
||
if (isDragging) return;
|
||
const item = e.target.closest('.slide-item');
|
||
if (item && e.button === 0) {
|
||
item.classList.toggle('active');
|
||
fetch('/update_active', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
slide: item.dataset.slide,
|
||
active: item.classList.contains('active')
|
||
})
|
||
});
|
||
}
|
||
});
|
||
|
||
// 6) Bible search & add/remove logic
|
||
const bibleSearchInput = document.getElementById('bibleSearchInput'),
|
||
bibleSearchButton = document.getElementById('bibleSearchButton'),
|
||
bibleTableBody = document.querySelector('#bibleTable tbody');
|
||
|
||
bibleSearchButton.addEventListener('click', () => {
|
||
const q = bibleSearchInput.value.trim();
|
||
bibleTableBody.innerHTML = '';
|
||
|
||
// Empty query → show what's in registry
|
||
if (!q) {
|
||
bibleRegistry.forEach(entry => {
|
||
const idx = entry.reference.lastIndexOf(' '),
|
||
book = entry.reference.slice(0, idx),
|
||
[chapter, verse] = entry.reference.slice(idx + 1).split(':'),
|
||
tr = document.createElement('tr');
|
||
tr.innerHTML = `
|
||
<td>${book}</td>
|
||
<td>${chapter}</td>
|
||
<td>${verse}</td>
|
||
<td>${entry.verse}</td>
|
||
<td></td>
|
||
<td></td>
|
||
`;
|
||
const btn = document.createElement('button');
|
||
btn.textContent = '–';
|
||
btn.style.width = '2rem';
|
||
btn.dataset.id = entry.id;
|
||
btn.addEventListener('click', () => {
|
||
fetch('/remove_bible_verse', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ id: Number(btn.dataset.id) })
|
||
})
|
||
.then(r => r.json())
|
||
.then(resp => {
|
||
if (resp.status === 'success') {
|
||
bibleRegistry = bibleRegistry.filter(e => e.id !== entry.id);
|
||
tr.remove();
|
||
}
|
||
});
|
||
});
|
||
tr.cells[5].appendChild(btn);
|
||
bibleTableBody.appendChild(tr);
|
||
});
|
||
return;
|
||
}
|
||
|
||
// Non-empty → query DB
|
||
fetch('/search_verses?q=' + encodeURIComponent(q))
|
||
.then(r => r.json())
|
||
.then(results => {
|
||
results.forEach(verse => {
|
||
const ref = `${verse.book} ${verse.chapter}:${verse.verse}`,
|
||
exist = bibleRegistry.find(e => e.reference === ref),
|
||
tr = document.createElement('tr');
|
||
tr.innerHTML = `
|
||
<td>${verse.book}</td>
|
||
<td>${verse.chapter}</td>
|
||
<td>${verse.verse}</td>
|
||
<td>${verse.text}</td>
|
||
<td>${verse.translation}</td>
|
||
<td></td>
|
||
`;
|
||
const btn = document.createElement('button');
|
||
btn.style.width = '2rem';
|
||
if (exist) {
|
||
btn.textContent = '–';
|
||
btn.dataset.id = exist.id;
|
||
} else {
|
||
btn.textContent = '+';
|
||
}
|
||
btn.addEventListener('click', () => {
|
||
if (btn.textContent === '+') {
|
||
// Add
|
||
fetch('/add_bible_verse', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ verse: verse.text, reference: ref })
|
||
})
|
||
.then(r => r.json())
|
||
.then(resp => {
|
||
if (resp.status === 'success') {
|
||
btn.textContent = '–';
|
||
btn.dataset.id = resp.entry.id;
|
||
bibleRegistry.push(resp.entry);
|
||
}
|
||
});
|
||
} else {
|
||
// Remove
|
||
const id = Number(btn.dataset.id);
|
||
fetch('/remove_bible_verse', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ id })
|
||
})
|
||
.then(r => r.json())
|
||
.then(resp => {
|
||
if (resp.status === 'success') {
|
||
bibleRegistry = bibleRegistry.filter(e => e.id !== id);
|
||
btn.textContent = '+';
|
||
}
|
||
});
|
||
}
|
||
});
|
||
tr.cells[5].appendChild(btn);
|
||
bibleTableBody.appendChild(tr);
|
||
});
|
||
});
|
||
});
|
||
|
||
bibleSearchInput.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') bibleSearchButton.click();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|