Add admin sound preferences

This commit is contained in:
rajchales-monito
2026-07-24 14:52:20 +02:00
parent 1d6d478993
commit 3f709f3a4e
3 changed files with 183 additions and 65 deletions
+33 -2
View File
@@ -124,8 +124,7 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; }
}
.logout-compact {
height: 32px;
padding: 0 8px;
font-size: 11px;
padding: 0;
}
.online-dot-toggle {
width: 28px;
@@ -919,6 +918,38 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; }
.settings h2 { margin: 0 0 12px; }
.settings h3 { margin: 20px 0 8px; }
.settings form { display: grid; gap: 12px; }
.settings-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px;
margin: 0 0 14px;
padding: 4px;
border: 1px solid var(--line);
border-radius: 8px;
background: #f7faf8;
}
.settings-tabs button {
padding: 9px 10px;
background: transparent;
color: var(--muted);
}
.settings-tabs button.active {
background: var(--brand);
color: white;
}
.settings-tab-panel { display: none; }
.settings-tab-panel.active { display: block; }
.admin-preferences {
display: grid;
gap: 14px;
}
.admin-preferences label:not(.setting-switch) {
gap: 8px;
}
.admin-preferences input[type="range"] {
padding: 0;
accent-color: var(--brand);
}
pre {
white-space: pre-wrap;
overflow: auto;
+62 -37
View File
@@ -44,7 +44,11 @@
</button>
<input id="adminSoundVolume" type="range" min="0" max="100" step="5" aria-label="Hlasitost notifikaci">
</div>
<button id="logoutButton" class="logout-compact" type="button">Odhlasit</button>
<button id="logoutButton" class="logout-compact icon-button" type="button" title="Odhlasit" aria-label="Odhlasit">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M10 3h9v18h-9v-2h7V5h-7V3Zm-1.2 5.2 1.4 1.4L7.8 12l2.4 2.4-1.4 1.4L4 11l4.8-2.8ZM5.4 11h8.8v2H5.4v-2Z"></path>
</svg>
</button>
</div>
</div>
<div class="list-tools">
@@ -101,44 +105,65 @@
<aside id="settingsPanel" class="settings hidden">
<div class="settings-head">
<h2>Nastaveni widgetu</h2>
<h2>Nastaveni</h2>
<button id="closeSettingsButton" type="button">Zavrit</button>
</div>
<form id="settingsForm">
<label>Titulek CS <input name="titleCs"></label>
<label>Intro CS <textarea name="introCs" rows="2"></textarea></label>
<label>Titulek EN <input name="titleEn"></label>
<label>Intro EN <textarea name="introEn" rows="2"></textarea></label>
<label class="color-field">Barva
<span class="color-row">
<input name="brand" type="color">
<input name="brandHex" class="hex-input" type="text" inputmode="text" maxlength="7" placeholder="#0f8f6f" pattern="#?[0-9a-fA-F]{6}">
</span>
</label>
<label>Pozice desktop <select name="desktopSide"><option>right</option><option>left</option></select></label>
<label>Desktop bottom px <input name="desktopBottom" type="number"></label>
<label>Desktop offset px <input name="desktopOffset" type="number"></label>
<label>Pozice mobil <select name="mobileSide"><option>right</option><option>left</option></select></label>
<label>Mobil bottom px <input name="mobileBottom" type="number"></label>
<label>Mobil offset px <input name="mobileOffset" type="number"></label>
<fieldset class="effect-settings">
<legend>Online efekt tlacitka</legend>
<label><input name="launcherBreathe" type="checkbox"> jemne dychani</label>
<label><input name="launcherRing" type="checkbox"> svetelny prstenec</label>
<label><input name="launcherHoverLabel" type="checkbox"> napis pri najeti</label>
<label><input name="launcherAvatar" type="checkbox"> mini avatar styl</label>
</fieldset>
<fieldset class="effect-settings">
<legend>Efekt pri nove zprave</legend>
<label><input name="messagePulse" type="checkbox"> pulzovani</label>
<label><input name="messageBadge" type="checkbox"> badge s poctem</label>
<label><input name="messageLabel" type="checkbox"> kratky stitek</label>
<label><input name="messageWiggle" type="checkbox"> zhoupnuti</label>
</fieldset>
<button type="submit">Ulozit</button>
</form>
<h3>GTM snippet</h3>
<pre id="snippet"></pre>
<div class="settings-tabs" role="tablist" aria-label="Nastaveni">
<button class="active" type="button" data-settings-tab="widget">Widget</button>
<button type="button" data-settings-tab="admin">BO</button>
</div>
<section class="settings-tab-panel active" data-settings-panel="widget">
<form id="settingsForm">
<label>Titulek CS <input name="titleCs"></label>
<label>Intro CS <textarea name="introCs" rows="2"></textarea></label>
<label>Titulek EN <input name="titleEn"></label>
<label>Intro EN <textarea name="introEn" rows="2"></textarea></label>
<label class="color-field">Barva
<span class="color-row">
<input name="brand" type="color">
<input name="brandHex" class="hex-input" type="text" inputmode="text" maxlength="7" placeholder="#0f8f6f" pattern="#?[0-9a-fA-F]{6}">
</span>
</label>
<label>Pozice desktop <select name="desktopSide"><option>right</option><option>left</option></select></label>
<label>Desktop bottom px <input name="desktopBottom" type="number"></label>
<label>Desktop offset px <input name="desktopOffset" type="number"></label>
<label>Pozice mobil <select name="mobileSide"><option>right</option><option>left</option></select></label>
<label>Mobil bottom px <input name="mobileBottom" type="number"></label>
<label>Mobil offset px <input name="mobileOffset" type="number"></label>
<fieldset class="effect-settings">
<legend>Online efekt tlacitka</legend>
<label><input name="launcherBreathe" type="checkbox"> jemne dychani</label>
<label><input name="launcherRing" type="checkbox"> svetelny prstenec</label>
<label><input name="launcherHoverLabel" type="checkbox"> napis pri najeti</label>
<label><input name="launcherAvatar" type="checkbox"> mini avatar styl</label>
</fieldset>
<fieldset class="effect-settings">
<legend>Efekt pri nove zprave</legend>
<label><input name="messagePulse" type="checkbox"> pulzovani</label>
<label><input name="messageBadge" type="checkbox"> badge s poctem</label>
<label><input name="messageLabel" type="checkbox"> kratky stitek</label>
<label><input name="messageWiggle" type="checkbox"> zhoupnuti</label>
</fieldset>
<button type="submit">Ulozit</button>
</form>
<h3>GTM snippet</h3>
<pre id="snippet"></pre>
</section>
<section class="settings-tab-panel" data-settings-panel="admin">
<div class="admin-preferences">
<label class="setting-switch">Zvuk notifikaci <input id="adminSoundToggleSettings" type="checkbox"></label>
<label>Hlasitost <input id="adminSoundVolumeSettings" type="range" min="0" max="100" step="5"></label>
<label>Styl zvuku
<select id="adminSoundStyle">
<option value="bright">Jasny</option>
<option value="soft">Jemny</option>
<option value="chime">Cinknuti</option>
<option value="urgent">Vyrazny</option>
</select>
</label>
<button id="adminSoundTest" type="button">Otestovat zvuk</button>
</div>
</section>
</aside>
</section>
</main>
+88 -26
View File
@@ -11,6 +11,7 @@ const state = {
hideBots: localStorage.getItem("mf_admin_hide_bots") !== "0",
soundMuted: localStorage.getItem("mf_admin_sound_muted") === "1",
soundVolume: Number(localStorage.getItem("mf_admin_sound_volume") || 70),
soundStyle: localStorage.getItem("mf_admin_sound_style") || "bright",
replyFiles: [],
attentionTimer: null,
attentionOn: false,
@@ -48,22 +49,35 @@ $("#logoutButton").addEventListener("click", async () => {
location.reload();
});
$("#settingsButton").addEventListener("click", () => $("#settingsPanel").classList.remove("hidden"));
$("#settingsButton").addEventListener("click", () => $("#settingsPanel").classList.toggle("hidden"));
$("#closeSettingsButton").addEventListener("click", () => $("#settingsPanel").classList.add("hidden"));
$("#adminSoundToggle").addEventListener("click", () => {
state.soundMuted = !state.soundMuted;
if (!state.soundMuted && state.soundVolume <= 0) state.soundVolume = 70;
localStorage.setItem("mf_admin_sound_muted", state.soundMuted ? "1" : "0");
localStorage.setItem("mf_admin_sound_volume", String(state.soundVolume));
renderAdminSoundControls();
setAdminSoundMuted(!state.soundMuted);
});
$("#adminSoundVolume").addEventListener("input", (event) => {
state.soundVolume = clamp(Number(event.target.value), 0, 100);
state.soundMuted = state.soundVolume <= 0;
localStorage.setItem("mf_admin_sound_volume", String(state.soundVolume));
localStorage.setItem("mf_admin_sound_muted", state.soundMuted ? "1" : "0");
setAdminSoundVolume(Number(event.target.value));
});
$("#adminSoundToggleSettings").addEventListener("change", (event) => {
setAdminSoundMuted(!event.target.checked);
});
$("#adminSoundVolumeSettings").addEventListener("input", (event) => {
setAdminSoundVolume(Number(event.target.value));
});
$("#adminSoundStyle").addEventListener("change", (event) => {
state.soundStyle = event.target.value;
localStorage.setItem("mf_admin_sound_style", state.soundStyle);
renderAdminSoundControls();
});
$("#adminSoundTest").addEventListener("click", () => {
playNotifySound({ ignoreMuted: true });
});
document.querySelectorAll("[data-settings-tab]").forEach((button) => {
button.addEventListener("click", () => {
const tab = button.dataset.settingsTab;
document.querySelectorAll("[data-settings-tab]").forEach((item) => item.classList.toggle("active", item === button));
document.querySelectorAll("[data-settings-panel]").forEach((panel) => panel.classList.toggle("active", panel.dataset.settingsPanel === tab));
});
});
$("#settingsForm").brand.addEventListener("input", (event) => {
$("#settingsForm").brandHex.value = normalizeHexColor(event.target.value) || event.target.value;
});
@@ -795,33 +809,81 @@ function notify() {
function renderAdminSoundControls() {
const toggle = $("#adminSoundToggle");
const volume = $("#adminSoundVolume");
if (!toggle || !volume) return;
const settingsToggle = $("#adminSoundToggleSettings");
const settingsVolume = $("#adminSoundVolumeSettings");
const settingsStyle = $("#adminSoundStyle");
const safeVolume = clamp(state.soundVolume, 0, 100);
toggle.classList.toggle("muted", state.soundMuted || safeVolume <= 0);
toggle.setAttribute("aria-label", state.soundMuted ? "Zapnout zvuk notifikaci" : "Vypnout zvuk notifikaci");
toggle.title = state.soundMuted ? "Zapnout zvuk notifikaci" : "Vypnout zvuk notifikaci";
volume.value = String(safeVolume);
volume.title = `Hlasitost ${safeVolume}%`;
if (toggle) {
toggle.classList.toggle("muted", state.soundMuted || safeVolume <= 0);
toggle.setAttribute("aria-label", state.soundMuted ? "Zapnout zvuk notifikaci" : "Vypnout zvuk notifikaci");
toggle.title = state.soundMuted ? "Zapnout zvuk notifikaci" : "Vypnout zvuk notifikaci";
}
if (volume) {
volume.value = String(safeVolume);
volume.title = `Hlasitost ${safeVolume}%`;
}
if (settingsToggle) settingsToggle.checked = !state.soundMuted;
if (settingsVolume) settingsVolume.value = String(safeVolume);
if (settingsStyle) settingsStyle.value = state.soundStyle;
}
function playNotifySound() {
function setAdminSoundMuted(isMuted) {
state.soundMuted = Boolean(isMuted);
if (!state.soundMuted && state.soundVolume <= 0) state.soundVolume = 70;
localStorage.setItem("mf_admin_sound_muted", state.soundMuted ? "1" : "0");
localStorage.setItem("mf_admin_sound_volume", String(state.soundVolume));
renderAdminSoundControls();
}
function setAdminSoundVolume(value) {
state.soundVolume = clamp(value, 0, 100);
state.soundMuted = state.soundVolume <= 0;
localStorage.setItem("mf_admin_sound_volume", String(state.soundVolume));
localStorage.setItem("mf_admin_sound_muted", state.soundMuted ? "1" : "0");
renderAdminSoundControls();
}
function playNotifySound(options = {}) {
const volume = clamp(state.soundVolume, 0, 100);
if (state.soundMuted || volume <= 0) return;
if (!options.ignoreMuted && (state.soundMuted || volume <= 0)) return;
const AudioCtor = window.AudioContext || window.webkitAudioContext;
if (!AudioCtor) return;
const audio = new AudioCtor();
const gain = audio.createGain();
gain.gain.value = 0.12 * (volume / 100);
gain.gain.value = 0.12 * ((volume || 70) / 100);
gain.connect(audio.destination);
[0, 0.14].forEach((offset, index) => {
for (const tone of soundPattern(state.soundStyle)) {
const osc = audio.createOscillator();
osc.type = "sine";
osc.frequency.value = index ? 1040 : 820;
osc.type = tone.type;
osc.frequency.value = tone.frequency;
osc.connect(gain);
osc.start(audio.currentTime + offset);
osc.stop(audio.currentTime + offset + 0.11);
});
setTimeout(() => audio.close().catch(() => {}), 520);
osc.start(audio.currentTime + tone.offset);
osc.stop(audio.currentTime + tone.offset + tone.duration);
}
setTimeout(() => audio.close().catch(() => {}), 720);
}
function soundPattern(style) {
const patterns = {
soft: [
{ offset: 0, duration: 0.13, frequency: 520, type: "sine" },
{ offset: 0.16, duration: 0.12, frequency: 680, type: "sine" }
],
chime: [
{ offset: 0, duration: 0.16, frequency: 880, type: "triangle" },
{ offset: 0.18, duration: 0.18, frequency: 1320, type: "triangle" }
],
urgent: [
{ offset: 0, duration: 0.1, frequency: 760, type: "square" },
{ offset: 0.13, duration: 0.1, frequency: 760, type: "square" },
{ offset: 0.26, duration: 0.12, frequency: 980, type: "square" }
],
bright: [
{ offset: 0, duration: 0.11, frequency: 820, type: "sine" },
{ offset: 0.14, duration: 0.11, frequency: 1040, type: "sine" }
]
};
return patterns[style] || patterns.bright;
}
function clamp(value, min, max) {