feat: add service worker and manifest for PWA support
Browse files- app/main.py +8 -0
- static/icons/icon-192.png +3 -0
- static/icons/icon-512.png +3 -0
- static/icons/icon-maskable-512.png +3 -0
- static/index.html +13 -0
- static/manifest.webmanifest +34 -0
- static/service-worker.js +34 -0
app/main.py
CHANGED
|
@@ -385,6 +385,14 @@ def create_app(scorer: Scorer | None = None, tts: SawtakTTS | None = None,
|
|
| 385 |
html = html.replace(mark, value)
|
| 386 |
return HTMLResponse(html, headers={"Cache-Control": "no-cache"})
|
| 387 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
@app.get("/api/me")
|
| 389 |
def me(user: dict | None = Depends(signed_in)):
|
| 390 |
"""Who is signed in, their classes (each with its lessons, its homework, see `homework_of`, and its daily
|
|
|
|
| 385 |
html = html.replace(mark, value)
|
| 386 |
return HTMLResponse(html, headers={"Cache-Control": "no-cache"})
|
| 387 |
|
| 388 |
+
@app.get("/service-worker.js", include_in_schema=False)
|
| 389 |
+
def service_worker():
|
| 390 |
+
return Response(
|
| 391 |
+
(STATIC / "service-worker.js").read_text(encoding="utf-8"),
|
| 392 |
+
media_type="application/javascript",
|
| 393 |
+
headers={"Cache-Control": "no-cache", "Service-Worker-Allowed": "/"},
|
| 394 |
+
)
|
| 395 |
+
|
| 396 |
@app.get("/api/me")
|
| 397 |
def me(user: dict | None = Depends(signed_in)):
|
| 398 |
"""Who is signed in, their classes (each with its lessons, its homework, see `homework_of`, and its daily
|
static/icons/icon-192.png
ADDED
|
|
Git LFS Details
|
static/icons/icon-512.png
ADDED
|
|
Git LFS Details
|
static/icons/icon-maskable-512.png
ADDED
|
|
Git LFS Details
|
static/index.html
CHANGED
|
@@ -3,6 +3,14 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
<title>אהלא וסהלא</title>
|
| 7 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Assistant:wght@400;600;700&family=Secular+One&family=Noto+Naskh+Arabic:wght@500;700&family=Noto+Sans+Hebrew:wght@500;700&family=Noto+Sans+Mono&display=swap">
|
|
@@ -3538,5 +3546,10 @@ if(AUTH_URL){
|
|
| 3538 |
}
|
| 3539 |
}
|
| 3540 |
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3541 |
</body>
|
| 3542 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
| 6 |
+
<meta name="theme-color" content="#4B6A2C">
|
| 7 |
+
<meta name="mobile-web-app-capable" content="yes">
|
| 8 |
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
| 9 |
+
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
| 10 |
+
<meta name="apple-mobile-web-app-title" content="ihki">
|
| 11 |
+
<link rel="manifest" href="/static/manifest.webmanifest">
|
| 12 |
+
<link rel="icon" href="/static/icons/icon-192.png" sizes="192x192">
|
| 13 |
+
<link rel="apple-touch-icon" href="/static/icons/icon-192.png">
|
| 14 |
<title>אהלא וסהלא</title>
|
| 15 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 16 |
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Assistant:wght@400;600;700&family=Secular+One&family=Noto+Naskh+Arabic:wght@500;700&family=Noto+Sans+Hebrew:wght@500;700&family=Noto+Sans+Mono&display=swap">
|
|
|
|
| 3546 |
}
|
| 3547 |
}
|
| 3548 |
</script>
|
| 3549 |
+
<script>
|
| 3550 |
+
if("serviceWorker" in navigator){
|
| 3551 |
+
addEventListener("load",()=>navigator.serviceWorker.register("/service-worker.js").catch(()=>{}));
|
| 3552 |
+
}
|
| 3553 |
+
</script>
|
| 3554 |
</body>
|
| 3555 |
</html>
|
static/manifest.webmanifest
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "/",
|
| 3 |
+
"name": "אהלא וסהלא · ihki",
|
| 4 |
+
"short_name": "ihki",
|
| 5 |
+
"description": "תרגול ערבית מדוברת",
|
| 6 |
+
"lang": "he",
|
| 7 |
+
"dir": "rtl",
|
| 8 |
+
"start_url": "/",
|
| 9 |
+
"scope": "/",
|
| 10 |
+
"display": "standalone",
|
| 11 |
+
"orientation": "portrait-primary",
|
| 12 |
+
"background_color": "#EEF0E8",
|
| 13 |
+
"theme_color": "#4B6A2C",
|
| 14 |
+
"icons": [
|
| 15 |
+
{
|
| 16 |
+
"src": "/static/icons/icon-192.png",
|
| 17 |
+
"sizes": "192x192",
|
| 18 |
+
"type": "image/png",
|
| 19 |
+
"purpose": "any"
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"src": "/static/icons/icon-512.png",
|
| 23 |
+
"sizes": "512x512",
|
| 24 |
+
"type": "image/png",
|
| 25 |
+
"purpose": "any"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"src": "/static/icons/icon-maskable-512.png",
|
| 29 |
+
"sizes": "512x512",
|
| 30 |
+
"type": "image/png",
|
| 31 |
+
"purpose": "maskable"
|
| 32 |
+
}
|
| 33 |
+
]
|
| 34 |
+
}
|
static/service-worker.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const CACHE = "ihki-shell-v1";
|
| 2 |
+
const SHELL = [
|
| 3 |
+
"/static/manifest.webmanifest",
|
| 4 |
+
"/static/icons/icon-192.png",
|
| 5 |
+
"/static/icons/icon-512.png",
|
| 6 |
+
"/static/icons/icon-maskable-512.png",
|
| 7 |
+
"/static/logo-intro.svg",
|
| 8 |
+
"/static/assets/arabic-calligraphy-path.webp"
|
| 9 |
+
];
|
| 10 |
+
|
| 11 |
+
self.addEventListener("install", event => {
|
| 12 |
+
event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(SHELL)));
|
| 13 |
+
self.skipWaiting();
|
| 14 |
+
});
|
| 15 |
+
|
| 16 |
+
self.addEventListener("activate", event => {
|
| 17 |
+
event.waitUntil(
|
| 18 |
+
caches.keys()
|
| 19 |
+
.then(keys => Promise.all(keys.filter(key => key !== CACHE).map(key => caches.delete(key))))
|
| 20 |
+
.then(() => self.clients.claim())
|
| 21 |
+
);
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
self.addEventListener("fetch", event => {
|
| 25 |
+
if (event.request.method !== "GET") return;
|
| 26 |
+
const url = new URL(event.request.url);
|
| 27 |
+
if (url.origin !== self.location.origin || !url.pathname.startsWith("/static/")) return;
|
| 28 |
+
event.respondWith(
|
| 29 |
+
caches.match(event.request).then(cached => cached || fetch(event.request).then(response => {
|
| 30 |
+
if (response.ok) caches.open(CACHE).then(cache => cache.put(event.request, response.clone()));
|
| 31 |
+
return response;
|
| 32 |
+
}))
|
| 33 |
+
);
|
| 34 |
+
});
|