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"],
|
||||
"soundEnabled" => $userdata["soundEnabled"],
|
||||
"repeatEnabled" => $userdata["repeatEnabled"],
|
||||
"currentServerTime" => floor(microtime(true) * 1000),
|
||||
)
|
||||
)));
|
||||
} 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['timerOf'] = "Timer of";
|
||||
|
||||
|
||||
$translations['results']['permissionDenied'] = "Access denied";
|
||||
$translations['results']['loginFailed'] = "Invalid credentials";
|
||||
$translations['results']['loginRequired'] = "Please sign in first!";
|
||||
|
@ -38,3 +37,11 @@ $translations['changePassword']['currentPasswordLabel'] = "Current password";
|
|||
$translations['changePassword']['newPasswordLabel'] = "New password";
|
||||
$translations['changePassword']['repeatNewPasswordLabel'] = "Repeat new 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)";
|
180
src/theme.php
180
src/theme.php
|
@ -165,7 +165,7 @@ class LandingpageTheme
|
|||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<body x-data>
|
||||
<main>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
|
@ -190,11 +190,23 @@ class LandingpageTheme
|
|||
</nav>
|
||||
|
||||
<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-header w-100"><?= $this->_trId("currentTimer") ?></div>
|
||||
<div class="card-body">
|
||||
<h3 id="header"><?= $this->_trId("loading") ?></h3>
|
||||
<h1 class="card-title" id="timer">00:00:00</h1>
|
||||
<h3 id="header" x-text="$store.siteData.header"></h3>
|
||||
<h1 class="card-title" id="timer" x-text="$store.siteData.time">00:00:00</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -219,7 +231,7 @@ class LandingpageTheme
|
|||
<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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -227,7 +239,6 @@ class LandingpageTheme
|
|||
</main>
|
||||
</body>
|
||||
<?php
|
||||
$this->_printTimerJs($_SESSION["auth"]["username"]);
|
||||
$this->_printFooter();
|
||||
}
|
||||
|
||||
|
@ -239,8 +250,13 @@ class LandingpageTheme
|
|||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<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>
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
|
@ -264,21 +280,72 @@ class LandingpageTheme
|
|||
.cover-container {
|
||||
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>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<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>
|
||||
<title><?= $this->_trId("timerOf") . " " . $username ?></title>
|
||||
<title><?= $this->_trId("timerOf") . " " . $username ?></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name=”apple-mobile-web-app-capable” content=”yes”>
|
||||
</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">
|
||||
<main class="px-3 h-100 mt-auto w-100">
|
||||
<h4 id="header"><?= $this->_trId("loading") ?></h4>
|
||||
<div class="h-80" style="justify-content:center; align-items:center; display:flex;">
|
||||
<h4 id="timer">00:00:00</h4>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</main>
|
||||
|
||||
|
@ -289,12 +356,13 @@ class LandingpageTheme
|
|||
</body>
|
||||
|
||||
<footer>
|
||||
<?= $this->_printTimerJs($username); ?>
|
||||
<script>
|
||||
const username = "<?= $username ?>";
|
||||
var textElem = document.getElementById("timer");
|
||||
var headerTextElem = document.getElementById("header");
|
||||
var targetWidth = 0.9; // Proportion of full screen width
|
||||
var curFontSize = 20; // Do not change
|
||||
|
||||
function updateTextSize() {
|
||||
for (var i = 0; 3 > i; i++) { // Iterate for better better convergence
|
||||
curFontSize *= targetWidth / (textElem.offsetWidth / textElem.parentNode.offsetWidth);
|
||||
|
@ -302,9 +370,17 @@ class LandingpageTheme
|
|||
headerTextElem.style.fontSize = curFontSize * 0.3 + "pt"
|
||||
}
|
||||
}
|
||||
updateTextSize();
|
||||
|
||||
document.addEventListener('alpine:initialized', () => {
|
||||
updateTextSize()
|
||||
})
|
||||
|
||||
window.onresize = updateTextSize;
|
||||
</script>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="/static/js/lib/alpine.min.js" defer></script>
|
||||
<script src="/static/js/synced-timer.js"></script>
|
||||
</footer>
|
||||
|
||||
</html>
|
||||
|
@ -318,69 +394,6 @@ class LandingpageTheme
|
|||
<?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 -
|
||||
// -----------
|
||||
|
@ -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">
|
||||
<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>
|
||||
:root {
|
||||
--primary_500: 255, 0, 0;
|
||||
|
@ -459,6 +477,7 @@ class LandingpageTheme
|
|||
{
|
||||
?>
|
||||
<script>
|
||||
const username = "<?= $_SESSION["auth"]["username"] ?>";
|
||||
var forms = document.getElementsByTagName('form')
|
||||
|
||||
for (const form of forms) {
|
||||
|
@ -481,6 +500,9 @@ class LandingpageTheme
|
|||
}
|
||||
</script>
|
||||
|
||||
<script src="/static/js/lib/alpine.min.js" defer></script>
|
||||
<script src="/static/js/synced-timer.js"></script>
|
||||
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue