Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abc0766f16 | |||
| 4e9fe2de6a |
@@ -0,0 +1,15 @@
|
|||||||
|
Mood-Board
|
||||||
|
Copyright (c) 2026 HNZIO
|
||||||
|
|
||||||
|
Licensed under the PolyForm Noncommercial License 1.0.0.
|
||||||
|
|
||||||
|
You may use, copy, modify, and distribute this software only for permitted
|
||||||
|
noncommercial purposes under the terms of that license.
|
||||||
|
|
||||||
|
Commercial use is not allowed without a separate written agreement from the
|
||||||
|
copyright holder.
|
||||||
|
|
||||||
|
Required Notice: Copyright (c) 2026 HNZIO
|
||||||
|
|
||||||
|
Full license text:
|
||||||
|
https://polyformproject.org/licenses/noncommercial/1.0.0/
|
||||||
@@ -32,3 +32,10 @@ Dateibasierter Stimmungstracker für LAMP/Cloudron ohne Datenbank.
|
|||||||
- Die Inhalte liegen absichtlich nicht in einer Datenbank, sondern in menschenlesbaren TXT-Dateien.
|
- Die Inhalte liegen absichtlich nicht in einer Datenbank, sondern in menschenlesbaren TXT-Dateien.
|
||||||
- Mehrere Accounts sind möglich und verursachen hier wenig Overhead, weil jeder Nutzer nur einen eigenen Unterordner mit Tagen und Einstellungen bekommt.
|
- Mehrere Accounts sind möglich und verursachen hier wenig Overhead, weil jeder Nutzer nur einen eigenen Unterordner mit Tagen und Einstellungen bekommt.
|
||||||
- Wenn du später Reverse Proxy oder HTTPS über Cloudron nutzt, bleiben die Daten weiterhin nur über die App erreichbar.
|
- Wenn du später Reverse Proxy oder HTTPS über Cloudron nutzt, bleiben die Daten weiterhin nur über die App erreichbar.
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
- Dieses Projekt steht unter der `PolyForm Noncommercial License 1.0.0`.
|
||||||
|
- Nicht-kommerzielle Nutzung ist erlaubt.
|
||||||
|
- Kommerzielle Nutzung ist ohne separate schriftliche Freigabe nicht erlaubt.
|
||||||
|
- Details siehe [LICENSE](/home/hnzio/Projekte/mood/LICENSE).
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 655 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
@@ -128,6 +128,34 @@ body {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pull-refresh-indicator {
|
||||||
|
position: fixed;
|
||||||
|
top: max(0.85rem, env(safe-area-inset-top));
|
||||||
|
left: 50%;
|
||||||
|
z-index: 30;
|
||||||
|
padding: 0.72rem 1rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translate(-50%, -0.9rem) scale(0.96);
|
||||||
|
transition: opacity 160ms ease, transform 160ms ease;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.is-pull-refreshing .pull-refresh-indicator,
|
||||||
|
body.is-pull-refresh-ready .pull-refresh-indicator,
|
||||||
|
body.is-pull-refresh-reloading .pull-refresh-indicator {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, 0) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.is-pull-refresh-ready .pull-refresh-indicator,
|
||||||
|
body.is-pull-refresh-reloading .pull-refresh-indicator {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
@@ -992,6 +992,125 @@
|
|||||||
return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
|
return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAppleTouchDevice() {
|
||||||
|
return /iPhone|iPad|iPod/i.test(window.navigator.userAgent)
|
||||||
|
|| (window.navigator.platform === "MacIntel" && window.navigator.maxTouchPoints > 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPullToRefresh() {
|
||||||
|
if (!isStandaloneMode() || !isAppleTouchDevice()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indicator = document.querySelector("[data-pull-refresh-indicator]");
|
||||||
|
const body = document.body;
|
||||||
|
const threshold = 96;
|
||||||
|
let isTracking = false;
|
||||||
|
let isReady = false;
|
||||||
|
let startY = 0;
|
||||||
|
|
||||||
|
const setIndicator = message => {
|
||||||
|
if (indicator) {
|
||||||
|
indicator.textContent = message;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetState = () => {
|
||||||
|
body.classList.remove("is-pull-refreshing", "is-pull-refresh-ready");
|
||||||
|
isTracking = false;
|
||||||
|
isReady = false;
|
||||||
|
startY = 0;
|
||||||
|
setIndicator("Zum Aktualisieren ziehen");
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollTop = () => Math.max(
|
||||||
|
window.scrollY || 0,
|
||||||
|
document.documentElement.scrollTop || 0,
|
||||||
|
document.body.scrollTop || 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const canStart = target => {
|
||||||
|
if (scrollTop() > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(target instanceof Element)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !target.closest("input, textarea, select, button");
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("touchstart", event => {
|
||||||
|
if (event.touches.length !== 1 || !canStart(event.target)) {
|
||||||
|
resetState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTracking = true;
|
||||||
|
startY = event.touches[0].clientY;
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
window.addEventListener("touchmove", event => {
|
||||||
|
if (!isTracking || event.touches.length !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scrollTop() > 0) {
|
||||||
|
resetState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = event.touches[0].clientY - startY;
|
||||||
|
|
||||||
|
if (delta <= 0) {
|
||||||
|
body.classList.remove("is-pull-refreshing", "is-pull-refresh-ready");
|
||||||
|
isReady = false;
|
||||||
|
setIndicator("Zum Aktualisieren ziehen");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta > 18) {
|
||||||
|
body.classList.add("is-pull-refreshing");
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta >= threshold) {
|
||||||
|
if (!isReady) {
|
||||||
|
body.classList.add("is-pull-refresh-ready");
|
||||||
|
setIndicator("Loslassen zum Aktualisieren");
|
||||||
|
isReady = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReady) {
|
||||||
|
body.classList.remove("is-pull-refresh-ready");
|
||||||
|
isReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIndicator("Zum Aktualisieren ziehen");
|
||||||
|
}, { passive: false });
|
||||||
|
|
||||||
|
window.addEventListener("touchend", () => {
|
||||||
|
if (!isTracking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReady) {
|
||||||
|
body.classList.remove("is-pull-refreshing", "is-pull-refresh-ready");
|
||||||
|
body.classList.add("is-pull-refresh-reloading");
|
||||||
|
setIndicator("Wird aktualisiert ...");
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetState();
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
window.addEventListener("touchcancel", resetState, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
function initPushControls() {
|
function initPushControls() {
|
||||||
const panel = document.querySelector("[data-push-panel]");
|
const panel = document.querySelector("[data-push-panel]");
|
||||||
if (!panel) {
|
if (!panel) {
|
||||||
@@ -1147,5 +1266,6 @@
|
|||||||
initDashboardCharts();
|
initDashboardCharts();
|
||||||
initSportTypeManager();
|
initSportTypeManager();
|
||||||
initPwaShell();
|
initPwaShell();
|
||||||
|
initPullToRefresh();
|
||||||
initPushControls();
|
initPushControls();
|
||||||
})();
|
})();
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -12,15 +12,15 @@
|
|||||||
"theme_color": "#0b1e2e",
|
"theme_color": "#0b1e2e",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/assets/branding/logo-mark.svg",
|
"src": "/assets/branding/icon-192.png",
|
||||||
"sizes": "any",
|
"sizes": "192x192",
|
||||||
"type": "image/svg+xml",
|
"type": "image/png",
|
||||||
"purpose": "any maskable"
|
"purpose": "any maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/assets/branding/apple-touch-icon.svg",
|
"src": "/assets/branding/icon-512.png",
|
||||||
"sizes": "any",
|
"sizes": "512x512",
|
||||||
"type": "image/svg+xml",
|
"type": "image/png",
|
||||||
"purpose": "any"
|
"purpose": "any"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -27,9 +27,11 @@ $brandSubtitle = match ($page) {
|
|||||||
<meta name="mood-push-public-key" content="<?= e((string) $pushPublicKey) ?>">
|
<meta name="mood-push-public-key" content="<?= e((string) $pushPublicKey) ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<title><?= e($pageTitle) ?> · Mood</title>
|
<title><?= e($pageTitle) ?> · Mood</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/assets/branding/favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="/assets/branding/favicon.svg?v=20260412">
|
||||||
<link rel="shortcut icon" href="/assets/branding/favicon.svg">
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/branding/favicon-32.png?v=20260412">
|
||||||
<link rel="apple-touch-icon" href="/assets/branding/apple-touch-icon.svg">
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/branding/favicon-16.png?v=20260412">
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico?v=20260412">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/branding/apple-touch-icon.png?v=20260412">
|
||||||
<link rel="manifest" href="/manifest.webmanifest">
|
<link rel="manifest" href="/manifest.webmanifest">
|
||||||
<link rel="stylesheet" href="/assets/css/app.css">
|
<link rel="stylesheet" href="/assets/css/app.css">
|
||||||
<script defer src="/assets/js/app.js"></script>
|
<script defer src="/assets/js/app.js"></script>
|
||||||
@@ -37,6 +39,7 @@ $brandSubtitle = match ($page) {
|
|||||||
<body class="app-body page-<?= e($page) ?><?= $authUser !== null ? ' is-authenticated' : '' ?>" data-authenticated="<?= $authUser !== null ? '1' : '0' ?>"<?= isset($trackMood) ? ' data-track-mood="' . e($trackMood) . '"' : '' ?>>
|
<body class="app-body page-<?= e($page) ?><?= $authUser !== null ? ' is-authenticated' : '' ?>" data-authenticated="<?= $authUser !== null ? '1' : '0' ?>"<?= isset($trackMood) ? ' data-track-mood="' . e($trackMood) . '"' : '' ?>>
|
||||||
<div class="aurora aurora-one"></div>
|
<div class="aurora aurora-one"></div>
|
||||||
<div class="aurora aurora-two"></div>
|
<div class="aurora aurora-two"></div>
|
||||||
|
<div class="pull-refresh-indicator glass-panel" data-pull-refresh-indicator aria-hidden="true">Zum Aktualisieren ziehen</div>
|
||||||
<div class="shell">
|
<div class="shell">
|
||||||
<?php if ($authUser !== null): ?>
|
<?php if ($authUser !== null): ?>
|
||||||
<aside class="sidebar glass-panel">
|
<aside class="sidebar glass-panel">
|
||||||
|
|||||||
Reference in New Issue
Block a user