Skip to content

Commit cf6d369

Browse files
feat: Implement basic PWA setup
Adds initial Progressive Web App (PWA) capabilities: - Creates `manifest.json` with app name, icons (placeholders), start URL, display mode, and theme colors. - Implements a basic service worker (`sw.js`) that caches core application shell URLs (homepage, create post, offline page, main JS, manifest) and employs a cache-first, then network strategy. - Includes an offline fallback page (`offline.html`) served by a new Django view and URL. - Links the manifest and registers the service worker in the `base.html` template.
1 parent 1f27b6b commit cf6d369

File tree

6 files changed

+179
-0
lines changed

6 files changed

+179
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "Social Platform - OpenPage",
3+
"short_name": "OpenPage",
4+
"description": "A social platform for creating and sharing AI-generated images and posts.",
5+
"start_url": "/",
6+
"display": "standalone",
7+
"background_color": "#ffffff",
8+
"theme_color": "#333333",
9+
"orientation": "portrait-primary",
10+
"icons": [
11+
{
12+
"src": "/static/openpage/icons/icon-192x192.png",
13+
"type": "image/png",
14+
"sizes": "192x192",
15+
"purpose": "any maskable"
16+
},
17+
{
18+
"src": "/static/openpage/icons/icon-512x512.png",
19+
"type": "image/png",
20+
"sizes": "512x512",
21+
"purpose": "any maskable"
22+
},
23+
{
24+
"src": "/static/openpage/icons/icon-1024x1024.png",
25+
"type": "image/png",
26+
"sizes": "1024x1024",
27+
"purpose": "any maskable"
28+
}
29+
]
30+
}

openpage/static/openpage/sw.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
const CACHE_NAME = 'openpage-v1';
2+
const urlsToCache = [
3+
'/', // Corresponds to post_list_view
4+
'/post/create/', // Corresponds to create_post_view
5+
// We should ideally cache specific, versioned static assets like CSS and JS.
6+
// For now, let's add the JS file we know.
7+
// The manifest file is also good to cache.
8+
'/static/openpage/js/create_post.js',
9+
'/static/openpage/manifest.json',
10+
// Add placeholder paths for main site CSS if/when created, e.g., '/static/css/main.css'
11+
// Add placeholder paths for any specific app CSS, e.g., '/static/openpage/css/style.css'
12+
'/offline/' // Our offline fallback page URL
13+
];
14+
15+
// Install event: Cache core assets
16+
self.addEventListener('install', event => {
17+
console.log('[Service Worker] Install event');
18+
event.waitUntil(
19+
caches.open(CACHE_NAME)
20+
.then(cache => {
21+
console.log('[Service Worker] Caching app shell');
22+
// AddAll can fail if any of the resources fail to fetch.
23+
// Consider adding them individually with error handling if some are non-critical.
24+
return cache.addAll(urlsToCache.map(url => new Request(url, { cache: 'reload' })));
25+
})
26+
.catch(error => {
27+
console.error('[Service Worker] Failed to cache app shell:', error);
28+
})
29+
);
30+
});
31+
32+
// Activate event: Clean up old caches
33+
self.addEventListener('activate', event => {
34+
console.log('[Service Worker] Activate event');
35+
event.waitUntil(
36+
caches.keys().then(cacheNames => {
37+
return Promise.all(
38+
cacheNames.map(cache => {
39+
if (cache !== CACHE_NAME) {
40+
console.log('[Service Worker] Clearing old cache:', cache);
41+
return caches.delete(cache);
42+
}
43+
})
44+
);
45+
})
46+
);
47+
return self.clients.claim(); // Ensure new service worker takes control immediately
48+
});
49+
50+
// Fetch event: Serve cached content when offline, or fetch from network
51+
self.addEventListener('fetch', event => {
52+
console.log('[Service Worker] Fetch event for:', event.request.url);
53+
// We only want to handle GET requests for caching strategy
54+
if (event.request.method !== 'GET') {
55+
return;
56+
}
57+
58+
event.respondWith(
59+
caches.match(event.request)
60+
.then(response => {
61+
if (response) {
62+
console.log('[Service Worker] Found in cache:', event.request.url);
63+
return response; // Serve from cache
64+
}
65+
console.log('[Service Worker] Not found in cache, fetching from network:', event.request.url);
66+
return fetch(event.request)
67+
.then(networkResponse => {
68+
// Optional: Cache new requests dynamically if needed
69+
// Be careful with what you cache dynamically, especially for non-static assets
70+
// if (networkResponse && networkResponse.status === 200 && event.request.url.startsWith(self.location.origin)) {
71+
// const responseToCache = networkResponse.clone();
72+
// caches.open(CACHE_NAME)
73+
// .then(cache => {
74+
// cache.put(event.request, responseToCache);
75+
// });
76+
// }
77+
return networkResponse;
78+
})
79+
.catch(error => {
80+
console.error('[Service Worker] Fetch failed for:', event.request.url, error);
81+
// If fetching fails (e.g., user is offline) and it's a navigation request,
82+
// try to return the offline fallback page.
83+
if (event.request.mode === 'navigate') {
84+
console.log('[Service Worker] Serving offline page.');
85+
return caches.match('/offline/');
86+
}
87+
// For non-navigation requests (like images, scripts),
88+
// we don't return the offline page, just let the fetch fail.
89+
// A more robust solution might return placeholder images/data.
90+
return Promise.reject(error); // Ensure the promise chain rejects
91+
});
92+
})
93+
);
94+
});

openpage/templates/openpage/base.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
<!DOCTYPE html>
22
<html lang="en">
3+
{% load static %}
34
<head>
45
<meta charset="UTF-8">
56
<meta name="viewport" content="width=device-width, initial-scale=1.0">
67
<title>{% block title %}Social Platform{% endblock %}</title>
8+
9+
<!-- PWA Manifest and Theme Color -->
10+
<link rel="manifest" href="{% static 'openpage/manifest.json' %}">
11+
<meta name="theme-color" content="#333333"> <!-- Should match manifest.json theme_color -->
12+
713
<style>
814
body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
915
header { background-color: #333; color: #fff; padding: 10px 0; text-align: center; }
@@ -53,5 +59,20 @@ <h1>Social Platform</h1>
5359
{% endblock %}
5460
</div>
5561

62+
<script>
63+
if ('serviceWorker' in navigator) {
64+
window.addEventListener('load', () => {
65+
navigator.serviceWorker.register("{% static 'openpage/sw.js' %}")
66+
.then(registration => {
67+
console.log('Service Worker registered successfully with scope:', registration.scope);
68+
})
69+
.catch(error => {
70+
console.error('Service Worker registration failed:', error);
71+
});
72+
});
73+
} else {
74+
console.log('Service Worker not supported in this browser.');
75+
}
76+
</script>
5677
</body>
5778
</html>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{% extends "openpage/base.html" %}
2+
{% load static %}
3+
4+
{% block title %}Offline{% endblock %}
5+
6+
{% block content %}
7+
<h2>You are Offline</h2>
8+
<p>We're sorry, but it seems you are currently offline and we couldn't load the page you requested.</p>
9+
<p>Please check your internet connection.</p>
10+
11+
<p>Some previously visited pages might still be accessible:</p>
12+
<ul>
13+
<li><a href="{% url 'openpage:post_list' %}">Home / Post List</a></li>
14+
{# Add links to other key pages if they are likely to be cached #}
15+
</ul>
16+
17+
<style>
18+
.offline-notice {
19+
text-align: center;
20+
padding: 20px;
21+
background-color: #fff3cd;
22+
border: 1px solid #ffeeba;
23+
color: #856404;
24+
border-radius: 4px;
25+
}
26+
</style>
27+
<div class="offline-notice">
28+
This page is a fallback for when you're offline.
29+
</div>
30+
{% endblock %}

openpage/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
path('', views.post_list_view, name='post_list'),
88
path('post/create/', views.create_post_view, name='create_post'),
99
path('post/<int:pk>/', views.post_detail_view, name='post_detail'),
10+
path('offline/', views.offline_view, name='offline'),
1011
]

openpage/views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,6 @@ def post_list_view(request):
4343
def post_detail_view(request, pk):
4444
post = get_object_or_404(Post, pk=pk)
4545
return render(request, 'openpage/post_detail.html', {'post': post})
46+
47+
def offline_view(request):
48+
return render(request, 'openpage/offline.html')

0 commit comments

Comments
 (0)