diff --git a/projector.py b/projector.py index 6e9a909..f2c2ebc 100755 --- a/projector.py +++ b/projector.py @@ -25,7 +25,7 @@ class SongProjector: try: # Odpri read-only; ne bo ustvaril prazne baze, če datoteka manjka # check_same_thread=False omogoča uporabo v večih nitih - self.conn = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True, check_same_thread=False) + self.conn = sqlite3.connect(DB_PATH, check_same_thread=False) self.cursor = self.conn.cursor() except sqlite3.OperationalError as e: # Jasno sporočilo in varen izhod diff --git a/web/server.py b/web/server.py index 7489235..a8e2156 100644 --- a/web/server.py +++ b/web/server.py @@ -154,6 +154,62 @@ def search_songs(): return jsonify({'error': str(e)}) +@app.route('/api/get_song_details', methods=['GET']) +def get_song_details(): + """Vrne podrobnosti trenutno naložene pesmi""" + if _projector_app is None: + return jsonify({'status': 'error', 'message': 'Aplikacija ni inicijalizirana'}) + + if not _projector_app.song_number_last: + return jsonify({'status': 'error', 'message': 'Nobena pesem ni naložena'}) + + try: + song_id = int(_projector_app.song_number_last) + _projector_app.cursor.execute("SELECT id, title, lyrics FROM songs WHERE id=?", (song_id,)) + result = _projector_app.cursor.fetchone() + if result: + return jsonify({ + 'status': 'ok', + 'song': { + 'id': result[0], + 'title': result[1], + 'lyrics': result[2] + } + }) + else: + return jsonify({'status': 'error', 'message': 'Pesem ni najdena v bazi'}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}) + + +@app.route('/api/update_song', methods=['POST']) +def update_song(): + """Posodobi naslov in besedilo pesmi""" + if _projector_app is None: + return jsonify({'status': 'error', 'message': 'Aplikacija ni inicijalizirana'}) + + data = request.get_json() + song_id = data.get('id') + title = data.get('title', '').strip() + lyrics = data.get('lyrics', '').strip() + + if not song_id or not title or not lyrics: + return jsonify({'status': 'error', 'message': 'Manjkajoči podatki'}) + + try: + _projector_app.cursor.execute("UPDATE songs SET title=?, lyrics=? WHERE id=?", (title, lyrics, song_id)) + _projector_app.conn.commit() + + # Osvežimo trenutno pesem, če je to ta, ki smo jo pravkar uredili + if str(_projector_app.song_number_last) == str(song_id): + _projector_app.song_number = str(song_id) + _projector_app.load_song() + + return jsonify({'status': 'ok'}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}) + + def run_server(host='127.0.0.1', port=5000): """Zaženi Flask server""" app.run(host=host, port=port, debug=False, use_reloader=False, threaded=True) diff --git a/web/static/script.js b/web/static/script.js index 90778a2..3087b75 100644 --- a/web/static/script.js +++ b/web/static/script.js @@ -16,6 +16,12 @@ const menuToggle = document.getElementById('menu-toggle'); const menuDropdown = document.getElementById('menu-dropdown'); const searchInput = document.getElementById('search-input'); const searchResults = document.getElementById('search-results'); +const editSongBtn = document.getElementById('edit-song-btn'); +const editModal = document.getElementById('edit-modal'); +const editTitleInput = document.getElementById('edit-title'); +const editLyricsInput = document.getElementById('edit-lyrics'); +const saveEditBtn = document.getElementById('save-edit-btn'); +const cancelEditBtn = document.getElementById('cancel-edit-btn'); let capsMode = false; let wakeLock = null; @@ -218,6 +224,68 @@ async function toggleCaps() { } } +// Odpri urejevalnik +async function openEditor() { + try { + const response = await fetch('/api/get_song_details'); + const data = await response.json(); + + if (data.status === 'ok') { + editTitleInput.value = data.song.title; + editLyricsInput.value = data.song.lyrics; + editModal.dataset.songId = data.song.id; + editModal.style.display = 'block'; + menuDropdown.classList.remove('show'); + } else { + alert(data.message || 'Za urejanje najprej naložite skladbo.'); + menuDropdown.classList.remove('show'); + } + } catch (error) { + console.error('Napaka pri pridobivanju podatkov pesmi:', error); + alert('Napaka pri povezavi s strežnikom.'); + } +} + +// Zapri urejevalnik +function closeEditor() { + editModal.style.display = 'none'; +} + +// Shrani spremembe +async function saveSongEdit() { + const songId = editModal.dataset.songId; + const title = editTitleInput.value.trim(); + const lyrics = editLyricsInput.value.trim(); + + if (!title || !lyrics) { + alert('Naslov in besedilo ne smeta biti prazna.'); + return; + } + + try { + const response = await fetch('/api/update_song', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: songId, + title: title, + lyrics: lyrics + }) + }); + const data = await response.json(); + + if (data.status === 'ok') { + closeEditor(); + await updateState(true); + } else { + alert('Napaka pri shranjevanju: ' + data.message); + } + } catch (error) { + console.error('Napaka pri shranjevanju pesmi:', error); + alert('Napaka pri povezavi s strežnikom.'); + } +} + // skrij/pokaži tipkovnico function toggleKeypad() { if (!keypadWrapper || !toggleKeypadBtn) return; @@ -284,6 +352,30 @@ if (toggleKeypadBtn) { }); } +// Uredi besedilo +if (editSongBtn) { + editSongBtn.addEventListener('click', () => { + vibrate(); + openEditor(); + }); +} + +// Prekliči urejanje +if (cancelEditBtn) { + cancelEditBtn.addEventListener('click', () => { + vibrate(); + closeEditor(); + }); +} + +// Posodobi (Shrani) urejanje +if (saveEditBtn) { + saveEditBtn.addEventListener('click', () => { + vibrate(); + saveSongEdit(); + }); +} + // Hamburger menu toggle if (menuToggle) { menuToggle.addEventListener('click', (e) => { @@ -330,6 +422,14 @@ document.addEventListener('keydown', (e) => { return; } + // Če smo v urejevalniku, ne procesiraj bližnjic za numerično tipkovnico + if (editModal && editModal.style.display === 'block') { + if (e.key === 'Escape') { + closeEditor(); + } + return; + } + // na telefonu ni potrebe; na velikih ekranih pa naj dela if (window.innerWidth < 901) return; diff --git a/web/static/styles.css b/web/static/styles.css index b6b89e1..23f5c47 100644 --- a/web/static/styles.css +++ b/web/static/styles.css @@ -418,6 +418,89 @@ body { } } +/* Modal Styles */ +.modal { + display: none; + position: fixed; + z-index: 2000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.8); + overflow-y: auto; +} + +.modal-content { + background-color: #222; + margin: 5% auto; + padding: 20px; + border: 1px solid #444; + width: 90%; + max-width: 800px; + border-radius: 8px; + color: white; +} + +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; +} + +.form-control { + width: 100%; + padding: 10px; + background-color: #333; + border: 1px solid #555; + color: white; + border-radius: 4px; + box-sizing: border-box; + font-size: 16px; +} + +textarea.form-control { + resize: vertical; + font-family: inherit; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 20px; +} + +.btn-primary { + background-color: #1f8a46; + color: white; + border: none; + padding: 10px 20px; + border-radius: 4px; + cursor: pointer; + font-weight: bold; +} + +.btn-secondary { + background-color: #555; + color: white; + border: none; + padding: 10px 20px; + border-radius: 4px; + cursor: pointer; +} + +.btn-primary:hover { + filter: brightness(1.1); +} + +.btn-secondary:hover { + filter: brightness(1.1); +} + /* veliki ekrani */ @media (min-width: 901px) { diff --git a/web/templates/index.html b/web/templates/index.html index 245e470..4d3c1af 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -25,6 +25,26 @@ + + + + +