From 8c5fa82b2ea2ee87cb64fd25e0f39f06ea7aea9a Mon Sep 17 00:00:00 2001 From: Valentin Korenjak Date: Mon, 16 Mar 2026 21:52:38 +0100 Subject: [PATCH] predelava web vmesnika - zaslonska tipkovnica + drugi gumbi --- web/static/script.js | 310 ++++++++++++++++++++++---------- web/static/styles.css | 373 ++++++++++++++++++++++++--------------- web/templates/index.html | 74 ++++---- 3 files changed, 488 insertions(+), 269 deletions(-) diff --git a/web/static/script.js b/web/static/script.js index 1bb387d..ff5274d 100644 --- a/web/static/script.js +++ b/web/static/script.js @@ -3,90 +3,130 @@ console.log('JavaScript se izvaja...'); // DOM elementi const songNumberInput = document.getElementById('song-number'); const loadBtn = document.getElementById('load-btn'); -const searchQueryInput = document.getElementById('search-query'); -const searchBtn = document.getElementById('search-btn'); -const capsBtn = document.getElementById('caps-btn'); const displayArea = document.getElementById('display-area'); const prevBtn = document.getElementById('prev-btn'); const nextBtn = document.getElementById('next-btn'); const darkBtn = document.getElementById('dark-btn'); const pageInfo = document.getElementById('page-info'); - -console.log('DOM elementi najdeni:', { songNumberInput: !!songNumberInput, loadBtn: !!loadBtn, searchQueryInput: !!searchQueryInput, searchBtn: !!searchBtn, capsBtn: !!capsBtn, displayArea: !!displayArea, prevBtn: !!prevBtn, nextBtn: !!nextBtn, darkBtn: !!darkBtn, pageInfo: !!pageInfo }); +const clearBtn = document.getElementById('clear-btn'); +const keypadButtons = document.querySelectorAll('.btn-key'); +const keypadWrapper = document.getElementById('keypad-wrapper'); +const toggleKeypadBtn = document.getElementById('toggle-keypad-btn'); let capsMode = false; +let wakeLock = null; +let lastStateSignature = ""; -// Naloži trenutne podatke -async function updateState() { - console.log('updateState() se kliče...'); +// vibracija telefona +function vibrate() { + if (navigator.vibrate && /Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) { + navigator.vibrate([25, 30, 25]); + } +} + +// poskus, da se zaslon ne ugaša +async function requestWakeLock() { try { - console.log('Pošiljam fetch za /api/state...'); - const response = await fetch('/api/state'); - console.log('Response status:', response.status); + if ('wakeLock' in navigator) { + wakeLock = await navigator.wakeLock.request('screen'); + console.log('Wake lock aktiviran'); + + wakeLock.addEventListener('release', () => { + console.log('Wake lock sproščen'); + }); + } + } catch (err) { + console.log('Wake lock ni uspel:', err); + } +} + +// ob vrnitvi v zavihek ponovno zahtevaj wake lock +document.addEventListener('visibilitychange', async () => { + if (document.visibilityState === 'visible') { + await requestWakeLock(); + } +}); + +// posodobi stanje +async function updateState(force = false) { + try { + const response = await fetch('/api/state', { cache: 'no-store' }); const data = await response.json(); - console.log('Response data:', data); - + + const signature = JSON.stringify({ + current_text: data.current_text || '', + page_info: data.page_info || '', + caps_mode: data.caps_mode || false, + can_prev: !!data.can_prev + }); + + if (!force && signature === lastStateSignature) { + return; + } + + lastStateSignature = signature; + displayArea.textContent = data.current_text || 'Pripravljeno. Vpiši številko pesmi.'; pageInfo.textContent = data.page_info || ''; + capsMode = data.caps_mode || false; - + if (capsMode) { - capsBtn.classList.add('active'); + darkBtn.classList.add('active'); } else { - capsBtn.classList.remove('active'); + darkBtn.classList.remove('active'); } - + prevBtn.disabled = !data.can_prev; - nextBtn.disabled = !data.can_next; - console.log('updateState() uspešno zaključeno'); } catch (error) { console.error('Napaka pri posodabljanju stanja:', error); displayArea.innerHTML = 'Napaka: ni povezave do strežnika'; - prevBtn.disabled = true; nextBtn.disabled = true; loadBtn.disabled = true; searchBtn.disabled = true; capsBtn.disabled = true; darkBtn.disabled = true; } } -// Naloži pesem +// dodaj številko +function addDigit(digit) { + songNumberInput.value += digit; +} + +// počisti vnos +function clearInput() { + songNumberInput.value = ''; +} + +// Enter: +// - če je številka -> naloži pesem +// - če ni številke -> naslednja kitica async function loadSong() { - console.log('loadSong() se kliče'); const songNumber = songNumberInput.value.trim(); - console.log('Song number:', songNumber); - if (!songNumber) return; - + try { - console.log('Pošiljam POST za /api/load_song...'); - const response = await fetch('/api/load_song', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({song_number: songNumber}) }); - console.log('Response status:', response.status); - const data = await response.json(); - console.log('Response data:', data); - - songNumberInput.value = ''; - updateState(); + if (songNumber) { + await fetch('/api/load_song', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ song_number: songNumber }) + }); + + songNumberInput.value = ''; + } else { + await fetch('/api/next_page', { method: 'POST' }); + } + + await updateState(true); } catch (error) { - console.error('Napaka pri nalaganju pesmi:', error); + console.error('Napaka:', error); displayArea.innerHTML = 'Napaka: ni povezave do strežnika'; } } -// Naslednja stran -async function nextPage() { - try { - await fetch('/api/next_page', {method: 'POST'}); - updateState(); - } catch (error) { - console.error('Napaka pri navigaciji:', error); - displayArea.innerHTML = 'Napaka: ni povezave do strežnika'; - } -} - -// Prejšnja stran +// prejšnja kitica async function prevPage() { try { - await fetch('/api/prev_page', {method: 'POST'}); - updateState(); + await fetch('/api/prev_page', { method: 'POST' }); + await updateState(true); } catch (error) { console.error('Napaka pri navigaciji:', error); - displayArea.innerHTML = 'Napaka: ni povezave do strežnika'; } } @@ -94,7 +134,7 @@ async function prevPage() { async function clearScreen() { try { await fetch('/api/clear_screen', {method: 'POST'}); - updateState(); + await updateState(true); } catch (error) { console.error('Napaka pri zatamnitvi ekrana:', error); displayArea.innerHTML = 'Napaka: ni povezave do strežnika'; @@ -104,54 +144,140 @@ async function clearScreen() { // Preklop VELIKIH ČRK async function toggleCaps() { try { - const response = await fetch('/api/toggle_caps', {method: 'POST'}); - updateState(); + await fetch('/api/toggle_caps', { method: 'POST' }); + await updateState(true); } catch (error) { - console.error('Napaka pri preklopa velikih črk:', error); displayArea.innerHTML = 'Napaka: ni povezave do strežnika'; } -} - -// Iskanje pesmi -async function searchSongs() { - const query = searchQueryInput.value.trim(); - if (!query) return; - - try { - const response = await fetch('/api/search_songs', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({query: query}) }); - const data = await response.json(); - - if (data.results && data.results.length > 0) { - const resultList = data.results.map(item => `${item[0]}: ${item[1]}`).join('\n'); - displayArea.innerHTML = '' + resultList.replace(//g, '>').replace(/\n/g, '
') + '
'; - } else { - displayArea.innerHTML = 'Ni zadetkov.'; - } - searchQueryInput.value = ''; - } catch (error) { - console.error('Napaka pri iskanju:', error); - displayArea.innerHTML = 'Napaka: ni povezave do strežnika'; + console.error('Napaka pri preklopu velikih črk:', error); } } -// Dodaj poslušalce -console.log('Dodajam event listenerje...'); -loadBtn.addEventListener('click', loadSong); -console.log('loadBtn listener dodan'); -songNumberInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') loadSong(); }); -console.log('songNumberInput listener dodan'); +// skrij/pokaži tipkovnico +function toggleKeypad() { + if (!keypadWrapper || !toggleKeypadBtn) return; -/* searchBtn.addEventListener('click', searchSongs); -searchQueryInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') searchSongs(); }); */ + keypadWrapper.classList.toggle('hidden'); -capsBtn.addEventListener('click', toggleCaps); -prevBtn.addEventListener('click', prevPage); -nextBtn.addEventListener('click', nextPage); -darkBtn.addEventListener('click', clearScreen); -console.log('Vsi event listenerji dodani'); + if (keypadWrapper.classList.contains('hidden')) { + toggleKeypadBtn.textContent = 'Pokaži tipkovnico'; + } else { + toggleKeypadBtn.textContent = 'Skrij tipkovnico'; + } +} -// Začetna inicijalizacija -console.log('Začenjam začetno inicijalizacijo...'); -updateState(); +// ============================ +// EVENT LISTENERJI +// ============================ + +// številke na zaslonu +keypadButtons.forEach(btn => { + btn.addEventListener('click', () => { + vibrate(); + const key = btn.dataset.key; + if (key !== undefined) { + addDigit(key); + } + }); +}); + +// Enter +loadBtn.addEventListener('click', () => { + vibrate(); + loadSong(); +}); + +// C +clearBtn.addEventListener('click', () => { + vibrate(); + clearInput(); +}); + +// Nazaj +prevBtn.addEventListener('click', () => { + vibrate(); + prevPage(); +}); + +// Zastri +nextBtn.addEventListener('click', () => { + vibrate(); + clearScreen(); +}); + +// AAaa +darkBtn.addEventListener('click', () => { + vibrate(); + toggleCaps(); +}); + +// Skrij/Pokaži tipkovnico +if (toggleKeypadBtn) { + toggleKeypadBtn.addEventListener('click', toggleKeypad); +} + +// fizična tipkovnica +document.addEventListener('keydown', (e) => { + // na telefonu ni potrebe; na velikih ekranih pa naj dela + if (window.innerWidth < 901) return; + + // da ne ponavlja pri držanju tipke + if (e.repeat) return; + + // številke + if (e.key >= '0' && e.key <= '9') { + e.preventDefault(); + addDigit(e.key); + return; + } + + // Enter + if (e.key === 'Enter' || e.code === 'NumpadEnter') { + e.preventDefault(); + loadSong(); + return; + } + + // C + if (e.key === 'Backspace' || e.key === 'Delete') { + e.preventDefault(); + clearInput(); + return; + } + + // Zastri + if (e.key === '+' || e.code === 'NumpadAdd') { + e.preventDefault(); + clearScreen(); + return; + } + + // Nazaj + if (e.key === '-' || e.code === 'NumpadSubtract') { + e.preventDefault(); + prevPage(); + return; + } + + // AAaa + if (e.key === '*' || e.code === 'NumpadMultiply') { + e.preventDefault(); + toggleCaps(); + return; + } + + // numpad številke + if (e.code.startsWith('Numpad') && /\d/.test(e.key)) { + e.preventDefault(); + addDigit(e.key); + } +}); + +// začetno stanje +updateState(true); +requestWakeLock(); + +// osveževanje za sinhronizacijo med več napravami +setInterval(() => { + updateState(false); +}, 1000); -// Osveži stanje vsako sekundo (za sinhronizacijo s tipkovnico) -// setInterval(updateState, 1000); console.log('JavaScript inicializacija zaključena'); \ No newline at end of file diff --git a/web/static/styles.css b/web/static/styles.css index 76f2016..37c22c6 100644 --- a/web/static/styles.css +++ b/web/static/styles.css @@ -4,101 +4,109 @@ box-sizing: border-box; } +:root { + --vh: 100vh; + --container-min-h: 100vh; + --keypad-gap: 10px; + --panel-bg: #111111; + --body-bg: #000000; + --text-main: #ffffff; + --text-muted: #aaaaaa; +} + +@supports (height: 100svh) { + :root { + --vh: 100svh; + --container-min-h: 100svh; + } +} + +@supports (height: 100dvh) { + :root { + --vh: 100dvh; + } +} + body { font-family: 'Times New Roman', serif; background-color: #0a0a0a; color: #ffffff; - display: flex; - flex-direction: column; - height: 100vh; - overflow: hidden; /* Prevent body scroll */ + min-height: var(--container-min-h); + overflow: auto; } .container { display: flex; flex-direction: column; - height: 100%; - padding: 0; + min-height: var(--vh); + height: var(--vh); overflow: hidden; + background-color: var(--body-bg); } -/* Gornja vrstica - Vnos pesmi */ -.top-bar { - background-color: #1a1a1a; - padding: 15px; - border-bottom: 2px solid #333; +/* skrito vnosno polje - script.js ga še vedno uporablja */ +#song-number { + display: none; +} + +/* vrstica s stanjem */ +.status-bar { + background-color: #111111; + border-bottom: 1px solid #222; + color: #dddddd; + padding: 8px 12px; + flex-shrink: 0; + min-height: 40px; display: flex; - gap: 10px; align-items: center; - flex-wrap: wrap; - flex-shrink: 0; /* Keep fixed size */ + justify-content: space-between; + gap: 12px; } -.top-bar label { - font-size: 16px; - font-weight: bold; +.page-info { + color: #cccccc; + font-size: 20px; + line-height: 1.2; + white-space: nowrap; } -.top-bar input { - padding: 10px 15px; - font-size: 16px; - background-color: #2a2a2a; - border: 1px solid #444; - color: #ffffff; - border-radius: 4px; - width: 100px; -} - -.top-bar button { - padding: 10px 20px; - font-size: 16px; - background-color: #3a7ca5; +.toggle-keypad-btn { + display: none; + background-color: #333; + color: #fff; border: none; - color: white; + border-radius: 8px; + padding: 8px 12px; + font-size: 15px; cursor: pointer; - border-radius: 4px; - transition: background-color 0.3s; } -.top-bar button:hover { - background-color: #2d6183; +.toggle-keypad-btn:hover { + filter: brightness(1.08); } -.caps-toggle { - padding: 10px 15px; - font-size: 14px; - background-color: #4a4a4a; - border: 1px solid #666; - color: #ffffff; - cursor: pointer; - border-radius: 4px; - transition: background-color 0.3s; -} - -.caps-toggle.active { - background-color: #3a7ca5; -} - -/* Sredenska vrstica - Prikaz besedila */ +/* glavni prikaz besedila */ .content { flex: 1; display: flex; - align-items: flex-start; /* Better for scrolling long text */ + align-items: flex-start; justify-content: center; - padding: 20px; + padding: 20px 16px; overflow-y: auto; background-color: #000000; - -webkit-overflow-scrolling: touch; /* Smooth scroll on iOS */ + -webkit-overflow-scrolling: touch; } .lyrics-display { text-align: center; - font-size: 24px; /* Adjusted for mobile */ - line-height: 1.4; + font-size: 24px; + line-height: 1.45; white-space: pre-wrap; word-wrap: break-word; max-width: 100%; + width: 100%; padding-bottom: 20px; + color: #ffffff; } .status-message { @@ -106,125 +114,198 @@ body { font-size: 20px; } -/* Donja vrstica - Navigacijski gumbi */ -.bottom-bar { - background-color: #1a1a1a; - padding: 15px; - border-top: 2px solid #333; - display: flex; - justify-content: space-around; - gap: 10px; - align-items: center; - flex-shrink: 0; /* Keep fixed size */ +/* ovijalec tipkovnice */ +.keypad-wrapper { + flex-shrink: 0; + background-color: #111111; + border-top: 1px solid #222; } -.bottom-bar button { - padding: 12px 10px; - font-size: 16px; +.keypad-wrapper.hidden { + display: none; +} + +/* numerična tipkovnica */ +.keypad { + background-color: #111111; + padding: 12px; + padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--keypad-gap); + width: 100%; +} + +/* osnovni videz gumbov */ +.keypad button { + min-height: 58px; border: none; - color: white; + border-radius: 10px; cursor: pointer; - border-radius: 4px; - transition: background-color 0.3s; - flex: 1; - min-width: 0; /* Allow shrinking */ - max-width: 120px; + font-family: Arial, sans-serif; + font-size: 28px; + font-weight: bold; + transition: transform 0.05s ease, filter 0.2s ease, opacity 0.2s ease, box-shadow 0.05s ease; + -webkit-tap-highlight-color: transparent; + box-shadow: 0 3px 0 rgba(0, 0, 0, 0.35); } -.btn-nav { - background-color: #556b79; +.keypad button:active { + transform: scale(0.80); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); } -.btn-nav:hover { - background-color: #3d4a52; -} - -.btn-nav:disabled { - background-color: #2a2a2a; +.keypad button:disabled { + opacity: 0.45; cursor: not-allowed; - opacity: 0.5; } +/* številke */ +.btn-key { + background-color: #f2f2f2; + color: #111111; +} + +/* Zastri / Nazaj */ +.btn-action { + background-color: #c28b24; + color: #ffffff; + font-size: 20px; +} + +/* AAaa */ .btn-dark { background-color: #8b4513; + color: #ffffff; + font-size: 20px; } -.btn-dark:hover { - background-color: #6b3410; +.btn-dark.active { + outline: 2px solid #ffffff; + outline-offset: -2px; } -.page-info { - color: #999; - font-size: 16px; - min-width: 60px; +/* C */ +.btn-clear { + background-color: #555555; + color: #ffffff; + font-size: 20px; } -/* ====== Viewport & varni odmik (mobilni) ====== */ - -/* Fallback: najprej uporabimo 100vh, potem nove enote (svh/dvh) kjer so podprte */ -:root { - --vh: 100vh; /* fallback */ - --container-min-h: 100vh; /* fallback */ - --bottom-bar-h: 60px; /* realna višina spodnje vrstice (glej .bottom-bar padding) */ +/* Enter čez dve vrstici */ +.btn-enter { + background-color: #1f8a46; + color: #ffffff; + grid-row: span 2; + font-size: 24px; } -/* Novejši brskalniki – stabilna višina toolbara */ -@supports (height: 100svh) { - :root { --vh: 100svh; --container-min-h: 100svh; } -} -/* Alternativa: dinamična višina (ko toolbar skrije/prikaže) */ -@supports (height: 100dvh) { - :root { --vh: 100dvh; } +/* hover samo za naprave z miško */ +@media (hover: hover) and (pointer: fine) { + .btn-key:hover { + filter: brightness(0.92); + } + + .btn-action:hover, + .btn-dark:hover, + .btn-clear:hover, + .btn-enter:hover, + .toggle-keypad-btn:hover { + filter: brightness(1.08); + } } -/* Odstrani zaklepanje scrolla – to pogosto povzroči prekrivanje na mobilnih */ -body { - /* odstrani prejšnje overflow: hidden; */ - overflow: auto !important; - min-height: var(--container-min-h); +/* tablica / manjši laptop */ +@media (max-width: 900px) { + .lyrics-display { + font-size: 22px; + } + + .page-info { + font-size: 18px; + } + + .keypad button { + min-height: 54px; + font-size: 24px; + } } -/* Glavni vsebnik naj razpne celoten pogled in upošteva varne robove */ -.container { - min-height: var(--vh); - padding-bottom: calc(var(--bottom-bar-h) + env(safe-area-inset-bottom, 0px)); - /* ostalo iz tvojega CSS lahko ostane */ +/* telefon */ +@media (max-width: 600px) { + .content { + padding: 14px 10px; + } + + .lyrics-display { + font-size: 20px; + line-height: 1.4; + } + + .status-message { + font-size: 18px; + } + + .page-info { + font-size: 18px; + } + + .keypad { + gap: 8px; + padding: 10px; + padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); + } + + .keypad button { + min-height: 52px; + font-size: 22px; + border-radius: 9px; + } } -/* Vsebinski del naj dopušča scroll, kot že imaš */ -.content { - /* ostane: flex:1; overflow-y:auto; ... */ - /* vendar dodamo malo dna, da zadnja vrstica besedila ne trči v panel */ - padding-bottom: 12px; +/* zelo majhen telefon */ +@media (max-width: 400px) { + .lyrics-display { + font-size: 18px; + } + + .page-info { + font-size: 16px; + } + + .keypad button { + min-height: 48px; + font-size: 20px; + } } -/* Spodnja vrstica – naj bo vedno vidna in nad UI brskalnika */ -.bottom-bar { - position: sticky; /* ostane z dokumentom, a se prilepi na dno ob scrollu */ - bottom: 0; - z-index: 1000; /* nad vsebino */ - /* ozadje + robovi ostanejo */ - padding-bottom: calc(15px + env(safe-area-inset-bottom, 0px)); - /* poravnaj navidezno višino: */ - min-height: var(--bottom-bar-h); -} +/* veliki ekrani */ +@media (min-width: 901px) { + .toggle-keypad-btn { + display: inline-block; + } -/* Gumbi v spodnjem panelu: malenkost večja višina za lažji tap */ -.bottom-bar button { - min-height: 44px; /* Apple/Google priporočilo za ‘tap targets’ */ -} + .keypad { + width: fit-content; + margin: 0 auto; + grid-template-columns: repeat(4, 120px); + gap: 10px; + } -/* Top bar: zadrži stabilno višino, a brez vpliva na dno */ -.top-bar { - position: sticky; - top: 0; - z-index: 1000; -} + .keypad button { + height: 80px; + min-height: 80px; + font-size: 22px; + } -/* --- (Opcijsko) tema: prilagodi spodnji panel za boljši kontrast nad browser UI --- */ -@supports (backdrop-filter: blur(4px)) { - .bottom-bar { - background-color: rgba(26, 26, 26, 0.95); - backdrop-filter: saturate(120%) blur(4px); - } + .btn-action, + .btn-dark, + .btn-clear { + font-size: 18px; + } + + .btn-enter { + font-size: 22px; + height: auto; + } } \ No newline at end of file diff --git a/web/templates/index.html b/web/templates/index.html index 372c0cc..67b06cf 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -3,41 +3,53 @@ - Projektor pesmi - Web vmesnik + Projektor pesmi + -
- -
- - - - - - - -
- - -
-
- Pripravljeno. Vpiši številko pesmi. -
-
- - -
- - - - - +
+ + + +
+ + +
+ +
+
+ Pripravljeno. Vpiši številko pesmi.
- - + +
+
+ + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + \ No newline at end of file