const CONFIG = {
SPREADSHEET_ID: '1qUdpLyskNR8rrb43TwbSx47S6hncQM_RzNGSiPbThno',
SENTENCE_SHEET: 'Zinnen',
SETTINGS_SHEET: 'Settings',
FAVORITES_SHEET: 'Favorieten'
};
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate()
.setTitle('1000 Zinnen · Presentator')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
function getSpreadsheet_() {
return SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID);
}
function setupSheets() {
const ss = getSpreadsheet_();
let sentenceSheet = ss.getSheetByName(CONFIG.SENTENCE_SHEET);
if (!sentenceSheet) {
sentenceSheet = ss.insertSheet(CONFIG.SENTENCE_SHEET);
}
if (sentenceSheet.getLastRow() === 0) {
sentenceSheet.getRange(1, 1, 1, 7).setValues([[
'ID',
'Zin',
'Actief',
'Favoriet',
'Notitie',
'Aangemaakt',
'Gewijzigd'
]]);
}
let settingsSheet = ss.getSheetByName(CONFIG.SETTINGS_SHEET);
if (!settingsSheet) {
settingsSheet = ss.insertSheet(CONFIG.SETTINGS_SHEET);
settingsSheet.getRange(1, 1, 1, 3).setValues([[
'Key',
'Value',
'Gewijzigd'
]]);
}
let favoritesSheet = ss.getSheetByName(CONFIG.FAVORITES_SHEET);
if (!favoritesSheet) {
favoritesSheet = ss.insertSheet(CONFIG.FAVORITES_SHEET);
favoritesSheet.getRange(1, 1, 1, 6).setValues([[
'ID',
'Zin',
'Notitie',
'Toegevoegd',
'Naam',
'Telefoon'
]]);
}
return {
ok: true,
message: 'Sheets gecontroleerd en klaargezet.'
};
}
function getSentences() {
setupSheets();
const sheet = getSpreadsheet_().getSheetByName(CONFIG.SENTENCE_SHEET);
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return [];
}
const values = sheet.getRange(2, 1, lastRow - 1, 7).getValues();
return values
.map(function(row, index) {
return {
id: Number(row[0]) || index + 1,
text: String(row[1] || '').trim(),
active: row[2] !== false,
favorite: row[3] === true,
note: String(row[4] || ''),
created: row[5] || '',
modified: row[6] || ''
};
})
.filter(function(item) {
return item.text;
});
}
function saveSentence(sentence) {
setupSheets();
const sheet = getSpreadsheet_().getSheetByName(CONFIG.SENTENCE_SHEET);
const lastRow = sheet.getLastRow();
if (!sentence.id) {
sentence.id = lastRow;
}
const data = sheet.getRange(2, 1, Math.max(1, lastRow - 1), 7).getValues();
let targetRow = -1;
for (let i = 0; i < data.length; i++) {
if (Number(data[i][0]) === Number(sentence.id)) {
targetRow = i + 2;
break;
}
}
const now = new Date();
if (targetRow === -1) {
targetRow = sheet.getLastRow() + 1;
sheet.getRange(targetRow, 1, 1, 7).setValues([[
Number(sentence.id),
sentence.text,
sentence.active !== false,
sentence.favorite === true,
sentence.note || '',
now,
now
]]);
} else {
const existing = sheet.getRange(targetRow, 1, 1, 7).getValues()[0];
sheet.getRange(targetRow, 1, 1, 7).setValues([[
Number(sentence.id),
sentence.text,
sentence.active !== false,
sentence.favorite === true,
sentence.note || '',
existing[5] || now,
now
]]);
}
return {
ok: true,
sentence: sentence
};
}
function updateSentenceText(id, text) {
const sheet = getSpreadsheet_().getSheetByName(CONFIG.SENTENCE_SHEET);
if (!sheet) {
setupSheets();
return updateSentenceText(id, text);
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return false;
}
const ids = sheet.getRange(2, 1, lastRow - 1, 1).getValues();
for (let i = 0; i < ids.length; i++) {
if (Number(ids[i][0]) === Number(id)) {
sheet.getRange(i + 2, 2).setValue(text);
sheet.getRange(i + 2, 7).setValue(new Date());
return true;
}
}
return false;
}
function setFavorite(id, favorite) {
const sheet = getSpreadsheet_().getSheetByName(CONFIG.SENTENCE_SHEET);
if (!sheet) {
setupSheets();
return setFavorite(id, favorite);
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return false;
}
const ids = sheet.getRange(2, 1, lastRow - 1, 1).getValues();
for (let i = 0; i < ids.length; i++) {
if (Number(ids[i][0]) === Number(id)) {
sheet.getRange(i + 2, 4).setValue(Boolean(favorite));
sheet.getRange(i + 2, 7).setValue(new Date());
syncFavorite_(id, favorite);
return true;
}
}
return false;
}
function syncFavorite_(id, favorite) {
const ss = getSpreadsheet_();
const source = ss.getSheetByName(CONFIG.SENTENCE_SHEET);
const target = ss.getSheetByName(CONFIG.FAVORITES_SHEET);
if (!target) {
setupSheets();
return syncFavorite_(id, favorite);
}
const sourceLast = source.getLastRow();
if (sourceLast < 2) {
return;
}
const values = source.getRange(2, 1, sourceLast - 1, 7).getValues();
let sentence = null;
for (let i = 0; i < values.length; i++) {
if (Number(values[i][0]) === Number(id)) {
sentence = values[i];
break;
}
}
if (!sentence) {
return;
}
const targetLast = target.getLastRow();
if (targetLast >= 2) {
const favorites = target.getRange(2, 1, targetLast - 1, 6).getValues();
for (let i = 0; i < favorites.length; i++) {
if (Number(favorites[i][0]) === Number(id)) {
if (!favorite) {
target.deleteRow(i + 2);
}
return;
}
}
}
if (favorite) {
target.appendRow([
sentence[0],
sentence[1],
sentence[4],
new Date(),
'',
''
]);
}
}
function getSettings() {
setupSheets();
const sheet = getSpreadsheet_().getSheetByName(CONFIG.SETTINGS_SHEET);
const lastRow = sheet.getLastRow();
const defaults = {
name: '',
phone: '',
site: '',
slogan: 'Accepteer nu wat is, verander op jouw manier wat kan.',
note: '',
speed: 650,
repeats: 3,
fontSize: 58,
wordColor: '#ffffff',
background: '#111827',
animation: 'fade',
fullscreen: false
};
if (lastRow < 2) {
return defaults;
}
const values = sheet.getRange(2, 1, lastRow - 1, 2).getValues();
values.forEach(function(row) {
const key = String(row[0] || '');
if (key) {
defaults[key] = row[1];
}
});
return defaults;
}
function saveSettings(settings) {
setupSheets();
const sheet = getSpreadsheet_().getSheetByName(CONFIG.SETTINGS_SHEET);
const keys = Object.keys(settings);
if (sheet.getLastRow() > 1) {
sheet.getRange(
2,
1,
sheet.getLastRow() - 1,
3
).clearContent();
}
const rows = keys.map(function(key) {
return [
key,
settings[key],
new Date()
];
});
if (rows.length) {
sheet.getRange(2, 1, rows.length, 3).setValues(rows);
}
return true;
}
function seedStructure() {
setupSheets();
const sheet = getSpreadsheet_().getSheetByName(CONFIG.SENTENCE_SHEET);
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return {
ok: false,
count: 0,
message: 'De sheet Zinnen is nog leeg. Plak de 1000 zinnen in kolom B en voer daarna normalizeSentences() uit.'
};
}
return normalizeSentences();
}
function normalizeSentences() {
setupSheets();
const sheet = getSpreadsheet_().getSheetByName(CONFIG.SENTENCE_SHEET);
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return {
ok: false,
count: 0,
message: 'Geen zinnen gevonden.'
};
}
const values = sheet.getRange(2, 1, lastRow - 1, 7).getValues();
const output = [];
values.forEach(function(row, index) {
const text = String(row[1] || '').trim();
if (!text) {
return;
}
output.push([
index + 1,
text,
row[2] === false ? false : true,
row[3] === true,
row[4] || '',
row[5] || new Date(),
new Date()
]);
});
if (output.length) {
sheet.getRange(2, 1, output.length, 7).setValues(output);
}
if (output.length >= 1000) {
sheet.getRange(1001, 2).setValue(
'Accepteer nu wat is, verander op jouw manier wat kan.'
);
}
return {
ok: true,
count: output.length,
message: output.length + ' zinnen verwerkt.'
};
}
function importSentencesFromText(text) {
setupSheets();
const sheet = getSpreadsheet_().getSheetByName(CONFIG.SENTENCE_SHEET);
const lines = String(text || '')
.split(/\r?\n/)
.map(function(line) {
return line.trim();
})
.filter(function(line) {
return line;
});
if (!lines.length) {
return {
ok: false,
count: 0
};
}
const rows = lines.map(function(line, index) {
return [
index + 1,
line.replace(/^\d+[\.\)]\s*/, ''),
true,
false,
'',
new Date(),
new Date()
];
});
sheet.clearContents();
sheet.getRange(1, 1, 1, 7).setValues([[
'ID',
'Zin',
'Actief',
'Favoriet',
'Notitie',
'Aangemaakt',
'Gewijzigd'
]]);
sheet.getRange(2, 1, rows.length, 7).setValues(rows);
if (rows.length >= 1000) {
sheet.getRange(1001, 2).setValue(
'Accepteer nu wat is, verander op jouw manier wat kan.'
);
}
return {
ok: true,
count: rows.length
};
}
function getFavorites() {
const sheet = getSpreadsheet_().getSheetByName(CONFIG.FAVORITES_SHEET);
if (!sheet || sheet.getLastRow() < 2) {
return [];
}
return sheet
.getRange(2, 1, sheet.getLastRow() - 1, 6)
.getValues()
.map(function(row) {
return {
id: Number(row[0]),
text: String(row[1] || ''),
note: String(row[2] || ''),
added: row[3] || '',
name: String(row[4] || ''),
phone: String(row[5] || '')
};
});
}
```html
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>GTKTP – Zinnenplayer Mobile</title>
<style>
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
background: #f5f7fa;
color: #172033;
}
.app {
width: 100%;
max-width: 760px;
margin: 0 auto;
padding: 10px 12px 30px;
}
header {
padding: 8px 4px 12px;
}
.brand {
font-size: 25px;
font-weight: 800;
line-height: 1.1;
}
.subtitle {
margin-top: 4px;
font-size: 13px;
color: #64748b;
}
.status {
margin-top: 8px;
padding: 8px 10px;
border-radius: 10px;
background: #e9eef5;
font-size: 13px;
}
.player {
background: white;
border-radius: 18px;
padding: 18px;
box-shadow: 0 4px 18px rgba(0,0,0,.08);
scroll-margin-top: 8px;
}
.counter {
text-align: center;
color: #64748b;
font-size: 13px;
margin-bottom: 12px;
}
.sentence {
min-height: 145px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-size: clamp(25px, 7vw, 42px);
line-height: 1.2;
font-weight: 700;
padding: 15px 5px 25px;
}
.favorite {
width: 100%;
border: 0;
background: transparent;
font-size: 30px;
padding: 5px;
cursor: pointer;
}
.favorite.active {
transform: scale(1.08);
}
.startButton {
width: 100%;
min-height: 64px;
border: 0;
border-radius: 15px;
font-size: 21px;
font-weight: 800;
cursor: pointer;
margin: 8px 0;
background: #172033;
color: white;
}
.startButton.pause {
background: #b42318;
}
.controls {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 9px;
margin-top: 12px;
}
button.control {
min-height: 48px;
border: 0;
border-radius: 12px;
background: #e8edf3;
color: #172033;
font-size: 15px;
font-weight: 700;
cursor: pointer;
padding: 8px;
}
button.control:active,
.startButton:active {
transform: scale(.98);
}
.settings {
margin-top: 14px;
background: white;
border-radius: 16px;
padding: 15px;
box-shadow: 0 3px 14px rgba(0,0,0,.06);
}
.settings h2 {
margin: 0 0 12px;
font-size: 18px;
}
.setting {
margin-bottom: 13px;
}
label {
display: block;
font-size: 13px;
font-weight: 700;
margin-bottom: 5px;
color: #475569;
}
select,
input[type="number"] {
width: 100%;
min-height: 45px;
border: 1px solid #cbd5e1;
border-radius: 10px;
padding: 8px 10px;
font-size: 16px;
background: white;
}
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-top: 14px;
}
.stat {
background: white;
border-radius: 12px;
padding: 11px 6px;
text-align: center;
box-shadow: 0 2px 9px rgba(0,0,0,.05);
}
.statValue {
font-size: 21px;
font-weight: 800;
}
.statLabel {
font-size: 11px;
color: #64748b;
margin-top: 2px;
}
.favoritesList {
margin-top: 14px;
background: white;
border-radius: 16px;
padding: 15px;
}
.favoritesList h2 {
margin: 0 0 10px;
font-size: 18px;
}
.favoriteItem {
border-bottom: 1px solid #e2e8f0;
padding: 10px 0;
cursor: pointer;
}
.favoriteItem:last-child {
border-bottom: 0;
}
.favoriteId {
font-size: 11px;
color: #64748b;
margin-bottom: 3px;
}
.favoriteText {
font-size: 14px;
line-height: 1.35;
}
.empty {
color: #64748b;
font-size: 14px;
padding: 5px 0;
}
.footer {
text-align: center;
margin-top: 20px;
font-size: 12px;
color: #94a3b8;
}
@media (min-width: 600px) {
.app {
padding-top: 18px;
}
.sentence {
min-height: 190px;
}
}
</style>
</head>
<body>
<div class="app">
<header>
<div class="brand">GTKTP</div>
<div class="subtitle">
Together we make life easier · Zinnenplayer
</div>
<div id="status" class="status">
Laden...
</div>
</header>
<!-- START / PAUZE BOVEN -->
<button
id="startTop"
class="startButton"
onclick="togglePlayer()">
▶ Start
</button>
<section id="player" class="player">
<div id="counter" class="counter">
0 / 0
</div>
<div id="sentence" class="sentence">
Zinnen worden geladen...
</div>
<button
id="favoriteButton"
class="favorite"
onclick="toggleFavorite()"
aria-label="Favoriet">
☆
</button>
</section>
<!-- START / PAUZE ONDER -->
<button
id="startBottom"
class="startButton"
onclick="togglePlayer()">
▶ Start
</button>
<!-- BEDIENING -->
<div class="controls">
<button class="control" onclick="previousSentence()">
◀ Vorige
</button>
<button class="control" onclick="nextSentence()">
Volgende ▶
</button>
<button class="control" onclick="showRandomSentence()">
🔀 Random
</button>
<button class="control" onclick="showFavorites()">
★ Favorieten
</button>
<button class="control" onclick="exportJSON()">
📤 Export JSON
</button>
<button class="control" onclick="document.getElementById('jsonFile').click()">
📥 Import JSON
</button>
<input
id="jsonFile"
type="file"
accept=".json,application/json"
style="display:none"
onchange="importJSON(event)">
</div>
<!-- INSTELLINGEN -->
<section class="settings">
<h2>Instellingen</h2>
<div class="setting">
<label for="playMode">Afspelen</label>
<select id="playMode" onchange="changeMode()">
<option value="all">Alle zinnen</option>
<option value="random">Random</option>
<option value="favorites">Alleen favorieten</option>
</select>
</div>
<div class="setting">
<label for="repeatCount">Herhalen per zin</label>
<input
id="repeatCount"
type="number"
min="1"
max="100"
value="1"
onchange="changeRepeat()">
</div>
</section>
<!-- STATISTIEKEN -->
<section class="stats">
<div class="stat">
<div id="totalCount" class="statValue">0</div>
<div class="statLabel">Zinnen</div>
</div>
<div class="stat">
<div id="favoriteCount" class="statValue">0</div>
<div class="statLabel">Favorieten</div>
</div>
<div class="stat">
<div id="playedCount" class="statValue">0</div>
<div class="statLabel">Gespeeld</div>
</div>
</section>
<!-- FAVORIETEN -->
<section class="favoritesList">
<h2>Mijn favorieten</h2>
<div id="favoritesContainer">
<div class="empty">
Nog geen favorieten.
</div>
</div>
</section>
<div class="footer">
GTKTP · We keep it simple.
</div>
</div>
<script>
let sentences = [];
let currentIndex = 0;
let playing = false;
let timer = null;
let repeatCount = 1;
let currentRepeat = 0;
let mode = "all";
let favorites = new Set();
let playedCount = 0;
/*
* ---------------------------------------------------------
* START
* ---------------------------------------------------------
*/
window.addEventListener("load", function() {
loadLocalSettings();
loadSentences();
});
/*
* ---------------------------------------------------------
* ZINNEN LADEN
* ---------------------------------------------------------
*/
function loadSentences() {
setStatus("Zinnen laden...");
if (
typeof google !== "undefined" &&
google.script &&
google.script.run
) {
google.script.run
.withSuccessHandler(function(data) {
if (!data) {
setStatus("Geen zinnen ontvangen.");
return;
}
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (e) {
console.error(e);
}
}
if (Array.isArray(data)) {
sentences = normalizeSentences(data);
} else if (data.sentences) {
sentences = normalizeSentences(data.sentences);
}
finishLoading();
})
.withFailureHandler(function(error) {
console.error(error);
setStatus(
"Zinnen konden niet worden geladen."
);
})
.getSentences();
} else {
setStatus(
"Deze HTML is niet gekoppeld aan Apps Script."
);
}
}
/*
* ---------------------------------------------------------
* NORMALISEREN
* ---------------------------------------------------------
*/
function normalizeSentences(data) {
return data
.map(function(item, index) {
if (typeof item === "string") {
return {
id: index + 1,
text: item
};
}
return {
id:
item.id ??
item.ID ??
index + 1,
text:
item.text ??
item.zin ??
item.Zin ??
item.sentence ??
""
};
})
.filter(function(item) {
return String(item.text).trim() !== "";
});
}
/*
* ---------------------------------------------------------
* KLAAR
* ---------------------------------------------------------
*/
function finishLoading() {
if (!sentences.length) {
setStatus(
"Geen zinnen gevonden."
);
return;
}
document.getElementById(
"totalCount"
).textContent = sentences.length;
updateFavoriteCount();
updateFavoriteList();
showCurrentSentence();
setStatus(
sentences.length +
" zinnen geladen."
);
}
/*
* ---------------------------------------------------------
* DISPLAY
* ---------------------------------------------------------
*/
function showCurrentSentence() {
if (!sentences.length) return;
const item = sentences[currentIndex];
document.getElementById(
"sentence"
).textContent = item.text;
document.getElementById(
"counter"
).textContent =
(currentIndex + 1) +
" / " +
sentences.length;
updateFavoriteButton();
}
/*
* ---------------------------------------------------------
* FAVORIET
* ---------------------------------------------------------
*/
function toggleFavorite() {
if (!sentences.length) return;
const id = String(
sentences[currentIndex].id
);
if (favorites.has(id)) {
favorites.delete(id);
} else {
favorites.add(id);
}
saveLocalSettings();
updateFavoriteButton();
updateFavoriteCount();
updateFavoriteList();
}
function updateFavoriteButton() {
if (!sentences.length) return;
const id = String(
sentences[currentIndex].id
);
const button =
document.getElementById(
"favoriteButton"
);
if (favorites.has(id)) {
button.textContent = "★";
button.classList.add("active");
} else {
button.textContent = "☆";
button.classList.remove("active");
}
}
function updateFavoriteCount() {
document.getElementById(
"favoriteCount"
).textContent = favorites.size;
}
/*
* ---------------------------------------------------------
* FAVORIETEN LIJST
* ---------------------------------------------------------
*/
function updateFavoriteList() {
const container =
document.getElementById(
"favoritesContainer"
);
if (!favorites.size) {
container.innerHTML =
'<div class="empty">Nog geen favorieten.</div>';
return;
}
let html = "";
sentences.forEach(function(item, index) {
if (!favorites.has(String(item.id))) {
return;
}
html +=
'<div class="favoriteItem" onclick="openFavorite(' +
index +
')">' +
'<div class="favoriteId">' +
"ID " +
item.id +
"</div>" +
'<div class="favoriteText">' +
escapeHTML(item.text) +
"</div>" +
"</div>";
});
container.innerHTML = html;
}
function openFavorite(index) {
currentIndex = index;
showCurrentSentence();
scrollToPlayer();
}
/*
* ---------------------------------------------------------
* VOLGENDE / VORIGE
* ---------------------------------------------------------
*/
function nextSentence() {
if (!sentences.length) return;
const list = getPlaybackList();
if (!list.length) {
setStatus(
"Er zijn geen zinnen beschikbaar in deze selectie."
);
pausePlayer();
return;
}
let position =
list.indexOf(currentIndex);
if (position === -1) {
position = 0;
} else {
position++;
}
if (position >= list.length) {
position = 0;
}
currentIndex = list[position];
currentRepeat = 0;
playedCount++;
document.getElementById(
"playedCount"
).textContent = playedCount;
showCurrentSentence();
scrollToPlayer();
}
function previousSentence() {
if (!sentences.length) return;
const list = getPlaybackList();
if (!list.length) return;
let position =
list.indexOf(currentIndex);
if (position <= 0) {
position = list.length - 1;
} else {
position--;
}
currentIndex = list[position];
currentRepeat = 0;
showCurrentSentence();
scrollToPlayer();
}
/*
* ---------------------------------------------------------
* PLAYBACK
* ---------------------------------------------------------
*/
function getPlaybackList() {
if (mode === "favorites") {
return sentences
.map(function(item, index) {
return favorites.has(
String(item.id)
)
? index
: null;
})
.filter(function(index) {
return index !== null;
});
}
return sentences.map(function(item, index) {
return index;
});
}
function togglePlayer() {
if (playing) {
pausePlayer();
} else {
startPlayer();
}
}
function startPlayer() {
if (!sentences.length) {
setStatus(
"Er zijn nog geen zinnen geladen."
);
return;
}
if (
mode === "favorites" &&
favorites.size === 0
) {
setStatus(
"Voeg eerst favorieten toe."
);
return;
}
playing = true;
updateStartButtons();
setStatus(
"Afspelen actief."
);
playCurrent();
}
function pausePlayer() {
playing = false;
if (timer) {
clearTimeout(timer);
timer = null;
}
updateStartButtons();
setStatus(
"Gepauzeerd. Instellingen kunnen nu worden gewijzigd."
);
}
function playCurrent() {
if (!playing) return;
showCurrentSentence();
speakCurrent();
}
/*
* ---------------------------------------------------------
* SPRAAK
* ---------------------------------------------------------
*/
function speakCurrent() {
if (!("speechSynthesis" in window)) {
setStatus(
"Tekst-naar-spraak wordt niet ondersteund."
);
return;
}
window.speechSynthesis.cancel();
const item = sentences[currentIndex];
const utterance =
new SpeechSynthesisUtterance(
item.text
);
utterance.lang = "nl-NL";
utterance.rate = 0.9;
utterance.onend = function() {
if (!playing) return;
currentRepeat++;
if (currentRepeat < repeatCount) {
timer = setTimeout(
playCurrent,
500
);
} else {
currentRepeat = 0;
timer = setTimeout(
nextSentenceAndPlay,
700
);
}
};
window.speechSynthesis.speak(
utterance
);
}
function nextSentenceAndPlay() {
if (!playing) return;
nextSentence();
playCurrent();
}
/*
* ---------------------------------------------------------
* RANDOM
* ---------------------------------------------------------
*/
function showRandomSentence() {
if (!sentences.length) return;
const list = getPlaybackList();
if (!list.length) {
setStatus(
"Geen zinnen in deze selectie."
);
return;
}
const randomPosition =
Math.floor(
Math.random() * list.length
);
currentIndex =
list[randomPosition];
currentRepeat = 0;
showCurrentSentence();
scrollToPlayer();
}
/*
* ---------------------------------------------------------
* FAVORIETEN MODE
* ---------------------------------------------------------
*/
function showFavorites() {
mode = "favorites";
document.getElementById(
"playMode"
).value = "favorites";
saveLocalSettings();
if (!favorites.size) {
setStatus(
"Nog geen favorieten."
);
return;
}
const list = getPlaybackList();
currentIndex = list[0];
showCurrentSentence();
setStatus(
favorites.size +
" favorieten geselecteerd."
);
scrollToPlayer();
}
/*
* ---------------------------------------------------------
* INSTELLINGEN
* ---------------------------------------------------------
*/
function changeMode() {
mode =
document.getElementById(
"playMode"
).value;
saveLocalSettings();
if (
mode === "favorites" &&
favorites.size === 0
) {
setStatus(
"Favorietenmodus gekozen, maar er zijn nog geen favorieten."
);
return;
}
setStatus(
"Modus: " +
document.getElementById(
"playMode"
).selectedOptions[0].text
);
}
function changeRepeat() {
let value =
parseInt(
document.getElementById(
"repeatCount"
).value,
10
);
if (!Number.isFinite(value)) {
value = 1;
}
value =
Math.max(
1,
Math.min(
100,
value
)
);
repeatCount = value;
document.getElementById(
"repeatCount"
).value = value;
saveLocalSettings();
}
/*
* ---------------------------------------------------------
* START BUTTONS
* ---------------------------------------------------------
*/
function updateStartButtons() {
const buttons = [
document.getElementById("startTop"),
document.getElementById("startBottom")
];
buttons.forEach(function(button) {
if (playing) {
button.textContent =
"⏸ Pauze";
button.classList.add("pause");
} else {
button.textContent =
"▶ Start";
button.classList.remove("pause");
}
});
}
/*
* ---------------------------------------------------------
* LOCAL OPSLAG
* ---------------------------------------------------------
*/
function saveLocalSettings() {
const data = {
version: "GTKTP-MOBILE-1.0",
favorites:
Array.from(favorites),
mode: mode,
repeatCount: repeatCount
};
localStorage.setItem(
"GTKTP_MOBILE_SETTINGS",
JSON.stringify(data)
);
}
function loadLocalSettings() {
const raw =
localStorage.getItem(
"GTKTP_MOBILE_SETTINGS"
);
if (!raw) return;
try {
const data =
JSON.parse(raw);
if (Array.isArray(data.favorites)) {
favorites =
new Set(
data.favorites.map(String)
);
}
if (data.mode) {
mode = data.mode;
const select =
document.getElementById(
"playMode"
);
if (select) {
select.value = mode;
}
}
if (
data.repeatCount &&
Number.isFinite(
Number(data.repeatCount)
)
) {
repeatCount =
Math.max(
1,
Math.min(
100,
Number(data.repeatCount)
)
);
document.getElementById(
"repeatCount"
).value =
repeatCount;
}
} catch (error) {
console.error(
"Instellingen konden niet worden geladen.",
error
);
}
}
/*
* ---------------------------------------------------------
* JSON EXPORT
* ---------------------------------------------------------
*/
function exportJSON() {
const data = {
gtkp: "GTKTP",
version: "1.0",
exported:
new Date().toISOString(),
settings: {
mode: mode,
repeatCount: repeatCount
},
favorites:
Array.from(favorites),
favoriteSentences:
sentences
.filter(function(item) {
return favorites.has(
String(item.id)
);
})
.map(function(item) {
return {
id: item.id,
zin: item.text
};
})
};
const blob =
new Blob(
[
JSON.stringify(
data,
null,
2
)
],
{
type: "application/json"
}
);
const url =
URL.createObjectURL(blob);
const a =
document.createElement("a");
a.href = url;
a.download =
"GTKTP-favorieten-" +
dateForFilename() +
".json";
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
setStatus(
favorites.size +
" favorieten geëxporteerd."
);
}
/*
* ---------------------------------------------------------
* JSON IMPORT
* ---------------------------------------------------------
*/
function importJSON(event) {
const file =
event.target.files[0];
if (!file) return;
const reader =
new FileReader();
reader.onload =
function(e) {
try {
const data =
JSON.parse(
e.target.result
);
if (
data.favorites &&
Array.isArray(
data.favorites
)
) {
favorites =
new Set(
data.favorites.map(String)
);
}
if (
data.settings &&
data.settings.mode
) {
mode =
data.settings.mode;
document.getElementById(
"playMode"
).value =
mode;
}
if (
data.settings &&
data.settings.repeatCount
) {
repeatCount =
Number(
data.settings.repeatCount
);
document.getElementById(
"repeatCount"
).value =
repeatCount;
}
saveLocalSettings();
updateFavoriteCount();
updateFavoriteList();
updateFavoriteButton();
setStatus(
favorites.size +
" favorieten geïmporteerd."
);
} catch (error) {
console.error(error);
setStatus(
"Ongeldig JSON-bestand."
);
}
};
reader.readAsText(file);
event.target.value = "";
}
/*
* ---------------------------------------------------------
* HULPFUNCTIES
* ---------------------------------------------------------
*/
function scrollToPlayer() {
document
.getElementById("player")
.scrollIntoView({
behavior: "smooth",
block: "start"
});
}
function setStatus(text) {
document.getElementById(
"status"
).textContent = text;
}
function dateForFilename() {
const d = new Date();
return d
.toISOString()
.slice(0, 10);
}
function escapeHTML(text) {
return String(text)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
</script>
</body>
</html>