Feat: audioPlayer, repeating timer
This commit is contained in:
parent
a0cf1c4f8a
commit
d7fcfa64dc
5 changed files with 112 additions and 90 deletions
|
@ -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 {
|
||||||
|
|
5
src/static/js/alpine.min.js
vendored
5
src/static/js/alpine.min.js
vendored
File diff suppressed because one or more lines are too long
3
src/static/js/purify.min.js
vendored
3
src/static/js/purify.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -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)";
|
182
src/theme.php
182
src/theme.php
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue