Feat: audioPlayer, repeating timer

This commit is contained in:
Dorian Zedler 2022-05-11 23:42:27 +02:00
parent a0cf1c4f8a
commit d7fcfa64dc
Signed by: dorian
GPG key ID: 989DE36109AFA354
5 changed files with 112 additions and 90 deletions

View file

@ -124,6 +124,7 @@ class SyncedTimer
"header" => $userdata["header"], "header" => $userdata["header"],
"soundEnabled" => $userdata["soundEnabled"], "soundEnabled" => $userdata["soundEnabled"],
"repeatEnabled" => $userdata["repeatEnabled"], "repeatEnabled" => $userdata["repeatEnabled"],
"currentServerTime" => floor(microtime(true) * 1000),
) )
))); )));
} else { } else {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -15,7 +15,6 @@ $translations['startTimer'] = "Start timer";
$translations['loading'] = "Loading..."; $translations['loading'] = "Loading...";
$translations['timerOf'] = "Timer of"; $translations['timerOf'] = "Timer of";
$translations['results']['permissionDenied'] = "Access denied"; $translations['results']['permissionDenied'] = "Access denied";
$translations['results']['loginFailed'] = "Invalid credentials"; $translations['results']['loginFailed'] = "Invalid credentials";
$translations['results']['loginRequired'] = "Please sign in first!"; $translations['results']['loginRequired'] = "Please sign in first!";
@ -37,4 +36,12 @@ $translations['login']['footnote'] = "Login for the itsblue synced timer. <a hre
$translations['changePassword']['currentPasswordLabel'] = "Current password"; $translations['changePassword']['currentPasswordLabel'] = "Current password";
$translations['changePassword']['newPasswordLabel'] = "New password"; $translations['changePassword']['newPasswordLabel'] = "New password";
$translations['changePassword']['repeatNewPasswordLabel'] = "Repeat new password"; $translations['changePassword']['repeatNewPasswordLabel'] = "Repeat new password";
$translations['changePassword']['submitLabel'] = "Change password"; $translations['changePassword']['submitLabel'] = "Change password";
$translations['audioModal']['title'] = "Audio playback";
$translations['audioModal']['body'] = "Please confirm to enable audio playback";
$translations['audioModal']['enable'] = "Enable audio playback";
$translations['audioModal']['disable'] = "Disable audio playback";
$translations['audioAlert']['body'] = "Audio playback is disabled";
$translations['fullscreenAlert']['body'] = "Doubletap anywhere on the screen to enable fullscreen mode. (does not work on apple devices)";

View file

@ -165,7 +165,7 @@ class LandingpageTheme
} }
</style> </style>
<body> <body x-data>
<main> <main>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid"> <div class="container-fluid">
@ -190,11 +190,23 @@ class LandingpageTheme
</nav> </nav>
<div class="container mt-3"> <div class="container mt-3">
<template x-if="(!$store.audioPlayback.isAvailable || $store.audioPlayback.isDisabledByUser) && !$store.audioPlayback.isDisabledByServer">
<div class="alert alert-danger alert-dismissible d-flex align-items-center" role="alert" style="position: relative;">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg>
<div>
<?= $this->_trId("audioAlert.body") ?>
<a href="#" class="alert-link" x-bind="audioPlaybackEnableButton"><?= $this->_trId("audioModal.enable") ?></a>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</template>
<div class="card text-white bg-dark mb-3"> <div class="card text-white bg-dark mb-3">
<div class="card-header w-100"><?= $this->_trId("currentTimer") ?></div> <div class="card-header w-100"><?= $this->_trId("currentTimer") ?></div>
<div class="card-body"> <div class="card-body">
<h3 id="header"><?= $this->_trId("loading") ?></h3> <h3 id="header" x-text="$store.siteData.header"></h3>
<h1 class="card-title" id="timer">00:00:00</h1> <h1 class="card-title" id="timer" x-text="$store.siteData.time">00:00:00</h1>
</div> </div>
</div> </div>
@ -219,7 +231,7 @@ class LandingpageTheme
<input class="form-check-input" type="checkbox" role="switch" name="repeatEnabled" id="repeatEnabledSwitch" <?= $userData["repeatEnabled"] ? "checked":"" ?>> <input class="form-check-input" type="checkbox" role="switch" name="repeatEnabled" id="repeatEnabledSwitch" <?= $userData["repeatEnabled"] ? "checked":"" ?>>
<label class="form-check-label" for="repeatEnabledSwitch">Repeat</label> <label class="form-check-label" for="repeatEnabledSwitch">Repeat</label>
</div> </div>
<button type="submit" class="btn btn-primary"><?= $this->_trId("startTimer") ?></button> <button type="submit" x-bind="audioPlaybackDisableButton" class="btn btn-primary"><?= $this->_trId("startTimer") ?></button>
</form> </form>
</div> </div>
</div> </div>
@ -227,7 +239,6 @@ class LandingpageTheme
</main> </main>
</body> </body>
<?php <?php
$this->_printTimerJs($_SESSION["auth"]["username"]);
$this->_printFooter(); $this->_printFooter();
} }
@ -239,8 +250,13 @@ class LandingpageTheme
} }
?> ?>
<!DOCTYPE html>
<html lang="en" class="h-100"> <html lang="en" class="h-100">
<!-- Bootstrap -->
<link href="/static/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="/static/js/lib/bootstrap.min.js" crossorigin="anonymous" defer></script>
<style> <style>
.bd-placeholder-img { .bd-placeholder-img {
font-size: 1.125rem; font-size: 1.125rem;
@ -264,21 +280,72 @@ class LandingpageTheme
.cover-container { .cover-container {
max-width: 42em; max-width: 42em;
} }
.modal-header {
border-bottom: 1px solid #414549;
}
.modal-footer {
border-top: 1px solid #414549;
}
.btn-close.btn-close-dark {
filter: invert(100%)
}
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
</style> </style>
<!-- Bootstrap --> <title><?= $this->_trId("timerOf") . " " . $username ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> <meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script> <meta name=”apple-mobile-web-app-capable” content=”yes”>
<title><?= $this->_trId("timerOf") . " " . $username ?></title>
</head> </head>
<body class="d-flex h-100 text-center text-white bg-dark"> <body x-data class="d-flex h-100 text-center text-white bg-dark">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
</symbol>
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</symbol>
</svg>
<div class="d-flex w-100 h-100 p-3 mx-auto flex-column"> <div class="d-flex w-100 h-100 p-3 mx-auto flex-column">
<main class="px-3 h-100 mt-auto w-100">
<h4 id="header"><?= $this->_trId("loading") ?></h4> <template x-if="(!$store.audioPlayback.isAvailable || $store.audioPlayback.isDisabledByUser) && !$store.audioPlayback.isDisabledByServer">
<div class="h-80" style="justify-content:center; align-items:center; display:flex;"> <div class="alert alert-danger alert-dismissible d-flex align-items-center" role="alert" style="position: relative;">
<h4 id="timer">00:00:00</h4> <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg>
<div>
<?= $this->_trId("audioAlert.body") ?>
<a href="#" class="alert-link" x-bind="audioPlaybackEnableButton"><?= $this->_trId("audioModal.enable") ?></a>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</template>
<template x-if="!$store.fullscreen.enabled">
<div class="alert alert-info alert-dismissible d-flex align-items-center" role="alert" style="position: relative;">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#info-fill"/></svg>
<div>
<?= $this->_trId("fullscreenAlert.body") ?>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</template>
<main class="px-3 h-100 mt-auto w-100" @click.prevent @dblclick="$store.fullscreen.toggle()">
<h4 id="header" class="unselectable" x-text="$store.siteData.header"></h4>
<div class="h-80 unselectable" style="justify-content:center; align-items:center; display:flex;">
<h4 id="timer" x-text="$store.siteData.time"></h4>
</div> </div>
</main> </main>
@ -289,12 +356,13 @@ class LandingpageTheme
</body> </body>
<footer> <footer>
<?= $this->_printTimerJs($username); ?>
<script> <script>
const username = "<?= $username ?>";
var textElem = document.getElementById("timer"); var textElem = document.getElementById("timer");
var headerTextElem = document.getElementById("header"); var headerTextElem = document.getElementById("header");
var targetWidth = 0.9; // Proportion of full screen width var targetWidth = 0.9; // Proportion of full screen width
var curFontSize = 20; // Do not change var curFontSize = 20; // Do not change
function updateTextSize() { function updateTextSize() {
for (var i = 0; 3 > i; i++) { // Iterate for better better convergence for (var i = 0; 3 > i; i++) { // Iterate for better better convergence
curFontSize *= targetWidth / (textElem.offsetWidth / textElem.parentNode.offsetWidth); curFontSize *= targetWidth / (textElem.offsetWidth / textElem.parentNode.offsetWidth);
@ -302,9 +370,17 @@ class LandingpageTheme
headerTextElem.style.fontSize = curFontSize * 0.3 + "pt" headerTextElem.style.fontSize = curFontSize * 0.3 + "pt"
} }
} }
updateTextSize();
document.addEventListener('alpine:initialized', () => {
updateTextSize()
})
window.onresize = updateTextSize; window.onresize = updateTextSize;
</script> </script>
<!-- JavaScript -->
<script src="/static/js/lib/alpine.min.js" defer></script>
<script src="/static/js/synced-timer.js"></script>
</footer> </footer>
</html> </html>
@ -318,69 +394,6 @@ class LandingpageTheme
<?php <?php
} }
// --------------
// - JavaScript -
// --------------
private function _printTimerJs($username)
{
?>
<script>
var currentData = {};
const zeroPad = (num, places) => String(num).padStart(places, '0')
var processData = function() {
if (this.readyState === 4 && this.status === 200) {
currentData = JSON.parse(this.responseText);
if (currentData["status"] === 200) {
currentData = currentData["data"]
} else {
document.getElementById("timer").innerHTML = "error: " + this.status
}
} else if (this.readyState === 4 && this.status !== 0) {
document.getElementById("timer").innerHTML = "error: " + this.status
}
}
function loadData() {
xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = processData;
xmlhttp.open("GET", "<?= $this->_globalConfig["basePath"] ?>/api/<?= $username ?>", true);
xmlhttp.send();
}
function setTimerText() {
time = currentData["time"]
header = currentData["header"]
startedAt = currentData["startedAt"]
passedSeconds = (Date.now() / 1000) - startedAt;
remaningSeconds = parseInt(time * 60 - passedSeconds);
if (remaningSeconds < 0) {
remaningSeconds = 0
}
var remaningHours = zeroPad(parseInt(remaningSeconds / 60 / 60) % (60 * 60), 2)
var remaningMinutes = zeroPad(parseInt(remaningSeconds / 60) % 60, 2)
var remaningSeconds = zeroPad(remaningSeconds % 60, 2)
document.getElementById("timer").innerHTML = remaningHours + ":" + remaningMinutes + ":" + remaningSeconds
document.getElementById("header").innerHTML = header
}
loadData();
var dataLoader = setInterval(function() {
loadData();
}, 5000)
var timerRefresher = setInterval(function() {
setTimerText()
}, 1000)
</script>
<?php
}
// ----------- // -----------
// - Helpers - // - Helpers -
// ----------- // -----------
@ -398,6 +411,11 @@ class LandingpageTheme
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
<!-- JavaScript -->
<script src="/static/js/lib/apline.min.js" defer></script>
<script src="/static/js/lib/purify.min.js" defer></script>
<script src="/static/js/synced-timer.js" defer></script>
<style> <style>
:root { :root {
--primary_500: 255, 0, 0; --primary_500: 255, 0, 0;
@ -459,6 +477,7 @@ class LandingpageTheme
{ {
?> ?>
<script> <script>
const username = "<?= $_SESSION["auth"]["username"] ?>";
var forms = document.getElementsByTagName('form') var forms = document.getElementsByTagName('form')
for (const form of forms) { for (const form of forms) {
@ -481,6 +500,9 @@ class LandingpageTheme
} }
</script> </script>
<script src="/static/js/lib/alpine.min.js" defer></script>
<script src="/static/js/synced-timer.js"></script>
</html> </html>
<?php <?php
} }