This commit is contained in:
+4
-4
@@ -5,9 +5,9 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<meta name="theme-color" content="#315d3a">
|
<meta name="theme-color" content="#315d3a">
|
||||||
<title>Wegwichtel</title>
|
<title>Wegwichtel</title>
|
||||||
<link rel="stylesheet" href="css/bootstrap.css?v=0.12.15">
|
<link rel="stylesheet" href="css/bootstrap.css?v=0.12.14">
|
||||||
<link rel="stylesheet" href="vendor/jquery-ui/jquery-ui-1.14.2.min.css?v=0.12.14">
|
<link rel="stylesheet" href="vendor/jquery-ui/jquery-ui-1.14.2.min.css?v=0.12.14">
|
||||||
<link rel="stylesheet" href="css/app.css?v=0.12.15">
|
<link rel="stylesheet" href="css/app.css?v=0.12.14">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="info-control">
|
<div class="info-control">
|
||||||
@@ -308,8 +308,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<script src="js/privacy-storage.js?v=0.12.15"></script>
|
<script src="js/privacy-storage.js?v=0.12.14"></script>
|
||||||
<script src="js/legal-ui.js?v=0.12.15"></script>
|
<script src="js/legal-ui.js?v=0.12.14"></script>
|
||||||
<script src="js/bootstrap-loader.js?v=0.12.14"></script>
|
<script src="js/bootstrap-loader.js?v=0.12.14"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+106
-31
@@ -22,6 +22,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
function readJson(key, fallback) {
|
function readJson(key, fallback) {
|
||||||
const store = storage();
|
const store = storage();
|
||||||
if (!store) return fallback;
|
if (!store) return fallback;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const raw = store.getItem(key);
|
const raw = store.getItem(key);
|
||||||
if (!raw) return fallback;
|
if (!raw) return fallback;
|
||||||
@@ -35,6 +36,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
function writeJson(key, value) {
|
function writeJson(key, value) {
|
||||||
const store = storage();
|
const store = storage();
|
||||||
if (!store) return false;
|
if (!store) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
store.setItem(key, JSON.stringify(value));
|
store.setItem(key, JSON.stringify(value));
|
||||||
return true;
|
return true;
|
||||||
@@ -46,6 +48,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
function removeItem(key) {
|
function removeItem(key) {
|
||||||
const store = storage();
|
const store = storage();
|
||||||
if (!store) return false;
|
if (!store) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
store.removeItem(key);
|
store.removeItem(key);
|
||||||
return true;
|
return true;
|
||||||
@@ -67,7 +70,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
}
|
}
|
||||||
|
|
||||||
function writeSettings(settings) {
|
function writeSettings(settings) {
|
||||||
return writeJson(PRIVACY_KEY, normalizeSettings({ ...settings, updatedAt: now() }));
|
const value = normalizeSettings({ ...settings, updatedAt: now() });
|
||||||
|
return writeJson(PRIVACY_KEY, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isProgressStorageEnabled() {
|
function isProgressStorageEnabled() {
|
||||||
@@ -87,12 +91,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
|
|
||||||
function routeId(route) {
|
function routeId(route) {
|
||||||
const id = route?.id ?? route?.routeId ?? route;
|
const id = route?.id ?? route?.routeId ?? route;
|
||||||
return id === null || id === undefined ? null : String(id);
|
if (id === null || id === undefined || id === '') return null;
|
||||||
}
|
return String(id);
|
||||||
|
|
||||||
function asNumberOrNull(value) {
|
|
||||||
const number = Number(value);
|
|
||||||
return Number.isFinite(number) ? number : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function numericIdSet(values) {
|
function numericIdSet(values) {
|
||||||
@@ -100,10 +100,17 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
return [...new Set(values.map(Number).filter(Number.isFinite))];
|
return [...new Set(values.map(Number).filter(Number.isFinite))];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addNumericId(values, value) {
|
||||||
|
return numericIdSet([...values, value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function progressEnvelope(value) {
|
||||||
|
const routes = value?.routes && typeof value.routes === 'object' ? value.routes : value;
|
||||||
|
return routes && typeof routes === 'object' ? routes : {};
|
||||||
|
}
|
||||||
|
|
||||||
function readProgressStore() {
|
function readProgressStore() {
|
||||||
const data = readJson(PROGRESS_KEY, {});
|
return progressEnvelope(readJson(PROGRESS_KEY, {}));
|
||||||
const entries = data?.routes && typeof data.routes === 'object' ? data.routes : data;
|
|
||||||
return entries && typeof entries === 'object' ? entries : {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeProgressStore(routes) {
|
function writeProgressStore(routes) {
|
||||||
@@ -114,10 +121,12 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSavedRouteState(value) {
|
function normalizeRouteState(value) {
|
||||||
if (!value || typeof value !== 'object') return null;
|
if (!value || typeof value !== 'object') return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
routeId: value.routeId === null || value.routeId === undefined ? null : String(value.routeId),
|
routeId: value.routeId === null || value.routeId === undefined ? null : String(value.routeId),
|
||||||
|
routeName: String(value.routeName ?? ''),
|
||||||
routeState: value.routeState === 'running' || value.routeState === 'paused' ? value.routeState : 'idle',
|
routeState: value.routeState === 'running' || value.routeState === 'paused' ? value.routeState : 'idle',
|
||||||
routeProgressIndex: Number.isInteger(value.routeProgressIndex) ? value.routeProgressIndex : null,
|
routeProgressIndex: Number.isInteger(value.routeProgressIndex) ? value.routeProgressIndex : null,
|
||||||
visitedPoiIds: numericIdSet(value.visitedPoiIds),
|
visitedPoiIds: numericIdSet(value.visitedPoiIds),
|
||||||
@@ -128,17 +137,81 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function freshEmptyRouteState(id, route = null) {
|
||||||
|
const timestamp = now();
|
||||||
|
return {
|
||||||
|
routeId: id,
|
||||||
|
routeName: route?.name ?? '',
|
||||||
|
routeState: 'idle',
|
||||||
|
routeProgressIndex: null,
|
||||||
|
visitedPoiIds: [],
|
||||||
|
viewedPoiIds: [],
|
||||||
|
playedAudioPoiIds: [],
|
||||||
|
savedAt: timestamp,
|
||||||
|
updatedAt: timestamp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentRouteState(routes, id, route = null) {
|
||||||
|
const normalized = normalizeRouteState(routes[id]);
|
||||||
|
return normalized ?? freshEmptyRouteState(id, route);
|
||||||
|
}
|
||||||
|
|
||||||
function isExpired(value, referenceTime = now()) {
|
function isExpired(value, referenceTime = now()) {
|
||||||
const saved = Number(value?.updatedAt) || Number(value?.savedAt) || 0;
|
const timestamp = Number(value?.updatedAt) || Number(value?.savedAt) || 0;
|
||||||
return saved <= 0 || referenceTime - saved > MAX_PROGRESS_AGE_MS;
|
return timestamp <= 0 || referenceTime - timestamp > MAX_PROGRESS_AGE_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportCleanupProgress(callback, processed, total, removed) {
|
||||||
|
if (typeof callback !== 'function') return;
|
||||||
|
callback({ processed, total, removed });
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRouteState(route, updater) {
|
||||||
|
if (!isProgressStorageEnabled()) return false;
|
||||||
|
|
||||||
|
const id = routeId(route);
|
||||||
|
if (!id || typeof updater !== 'function') return false;
|
||||||
|
|
||||||
|
const routes = readProgressStore();
|
||||||
|
const entry = currentRouteState(routes, id, typeof route === 'object' ? route : null);
|
||||||
|
const updated = updater(entry) ?? entry;
|
||||||
|
updated.routeId = id;
|
||||||
|
updated.updatedAt = now();
|
||||||
|
if (!updated.savedAt) updated.savedAt = updated.updatedAt;
|
||||||
|
routes[id] = normalizeRouteState(updated);
|
||||||
|
return writeProgressStore(routes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function markVisited(route, poiId) {
|
||||||
|
return updateRouteState(route, entry => ({
|
||||||
|
...entry,
|
||||||
|
visitedPoiIds: addNumericId(entry.visitedPoiIds, poiId)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function markViewed(route, poiId) {
|
||||||
|
return updateRouteState(route, entry => ({
|
||||||
|
...entry,
|
||||||
|
viewedPoiIds: addNumericId(entry.viewedPoiIds, poiId)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function markPlayedAudio(route, poiId) {
|
||||||
|
return updateRouteState(route, entry => ({
|
||||||
|
...entry,
|
||||||
|
playedAudioPoiIds: addNumericId(entry.playedAudioPoiIds, poiId)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveRouteState(route, routeState, routeProgressIndex, visitedPoiIds = [], viewedPoiIds = [], playedAudioPoiIds = []) {
|
function saveRouteState(route, routeState, routeProgressIndex, visitedPoiIds = [], viewedPoiIds = [], playedAudioPoiIds = []) {
|
||||||
if (!isProgressStorageEnabled()) return false;
|
if (!isProgressStorageEnabled()) return false;
|
||||||
|
|
||||||
const id = routeId(route);
|
const id = routeId(route);
|
||||||
if (!id) return false;
|
if (!id) return false;
|
||||||
const routes = readProgressStore();
|
|
||||||
const timestamp = now();
|
const timestamp = now();
|
||||||
|
const routes = readProgressStore();
|
||||||
routes[id] = {
|
routes[id] = {
|
||||||
routeId: id,
|
routeId: id,
|
||||||
routeName: route?.name ?? '',
|
routeName: route?.name ?? '',
|
||||||
@@ -153,16 +226,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
return writeProgressStore(routes);
|
return writeProgressStore(routes);
|
||||||
}
|
}
|
||||||
|
|
||||||
function activateRoute(route) {
|
function cleanupExpired(progressCallback) {
|
||||||
if (!isProgressStorageEnabled()) return null;
|
|
||||||
const id = routeId(route);
|
|
||||||
if (!id) return null;
|
|
||||||
const routes = cleanupExpiredEntries().routes;
|
|
||||||
const saved = normalizeSavedRouteState(routes[id]);
|
|
||||||
return saved && !isExpired(saved) ? saved : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanupExpiredEntries(progressCallback) {
|
|
||||||
const referenceTime = now();
|
const referenceTime = now();
|
||||||
const routes = readProgressStore();
|
const routes = readProgressStore();
|
||||||
const entries = Object.entries(routes);
|
const entries = Object.entries(routes);
|
||||||
@@ -170,7 +234,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
let removed = 0;
|
let removed = 0;
|
||||||
|
|
||||||
entries.forEach(([id, value], index) => {
|
entries.forEach(([id, value], index) => {
|
||||||
const normalized = normalizeSavedRouteState(value);
|
const normalized = normalizeRouteState(value);
|
||||||
if (normalized && !isExpired(normalized, referenceTime)) kept[id] = normalized;
|
if (normalized && !isExpired(normalized, referenceTime)) kept[id] = normalized;
|
||||||
else removed += 1;
|
else removed += 1;
|
||||||
reportCleanupProgress(progressCallback, index + 1, entries.length, removed);
|
reportCleanupProgress(progressCallback, index + 1, entries.length, removed);
|
||||||
@@ -180,13 +244,19 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
return { removed, total: entries.length, routes: kept };
|
return { removed, total: entries.length, routes: kept };
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportCleanupProgress(callback, processed, total, removed) {
|
async function cleanupExpiredProgress(progressCallback) {
|
||||||
if (typeof callback !== 'function') return;
|
return cleanupExpired(progressCallback);
|
||||||
callback({ processed, total, removed });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanupExpiredProgress(progressCallback) {
|
function activateRoute(route) {
|
||||||
return cleanupExpiredEntries(progressCallback);
|
if (!isProgressStorageEnabled()) return null;
|
||||||
|
|
||||||
|
const id = routeId(route);
|
||||||
|
if (!id) return null;
|
||||||
|
|
||||||
|
const routes = cleanupExpired().routes;
|
||||||
|
const saved = normalizeRouteState(routes[id]);
|
||||||
|
return saved && !isExpired(saved) ? saved : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ns.PrivacyStorage = Object.freeze({
|
ns.PrivacyStorage = Object.freeze({
|
||||||
@@ -195,10 +265,15 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
MAX_PROGRESS_AGE_MS,
|
MAX_PROGRESS_AGE_MS,
|
||||||
activateRoute,
|
activateRoute,
|
||||||
cleanExpiredProgress: cleanupExpiredProgress,
|
cleanExpiredProgress: cleanupExpiredProgress,
|
||||||
|
cleanupExpired,
|
||||||
cleanupExpiredProgress,
|
cleanupExpiredProgress,
|
||||||
cookiesEnabled: () => false,
|
cookiesEnabled,
|
||||||
getPrivacySettings: readSettings,
|
getPrivacySettings: readSettings,
|
||||||
isProgressStorageEnabled,
|
isProgressStorageEnabled,
|
||||||
|
markPlayed: markPlayedAudio,
|
||||||
|
markPlayedAudio,
|
||||||
|
markVisited,
|
||||||
|
markViewed,
|
||||||
removeExpiredProgress: cleanupExpiredProgress,
|
removeExpiredProgress: cleanupExpiredProgress,
|
||||||
removeExpiredRouteProgress: cleanupExpiredProgress,
|
removeExpiredRouteProgress: cleanupExpiredProgress,
|
||||||
saveRouteState,
|
saveRouteState,
|
||||||
|
|||||||
Reference in New Issue
Block a user