Skip to content

Commit 29b1fce

Browse files
committed
Add PWA install support
1 parent f489668 commit 29b1fce

6 files changed

Lines changed: 153 additions & 4 deletions

File tree

‎icons/icon-192.png‎

6.98 KB
Loading

‎icons/icon-512.png‎

20.2 KB
Loading

‎index.html‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<link rel="icon" type="image/svg+xml" href="./src/logo.svg" />
7+
<link rel="manifest" href="./manifest.webmanifest" />
8+
<link rel="apple-touch-icon" href="./icons/icon-192.png" />
79
<title>Open Brick | Parametric Brick STL Generator for 3D Printing</title>
810
<meta
911
name="description"
10-
content="Open Brick is a browser-based parametric brick generator. Create classic brick, plate, tile, round brick, round tile, and mechanical axle STL files for 3D printing from OpenSCAD templates."
12+
content="Open Brick is a browser-based parametric brick generator. Create classic brick, plate, tile, and mechanical axle STL files for 3D printing from OpenSCAD templates."
1113
/>
1214
<meta
1315
name="keywords"
14-
content="brick STL generator, parametric brick, OpenSCAD, 3D printing, classic brick, classic plate, classic tile, round brick, round tile, mechanical axle"
16+
content="brick STL generator, parametric brick, OpenSCAD, 3D printing, classic brick, classic plate, classic tile, mechanical axle, PWA"
1517
/>
1618
<meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" />
1719
<meta name="theme-color" content="#0b1424" />
20+
<meta name="mobile-web-app-capable" content="yes" />
21+
<meta name="apple-mobile-web-app-capable" content="yes" />
22+
<meta name="apple-mobile-web-app-title" content="Open Brick" />
23+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
1824
<link rel="canonical" href="https://openbrick.cogan.dev/" />
1925
<link rel="alternate" type="text/plain" href="/llms.txt" title="LLM quick rules" />
2026
<link rel="alternate" type="text/plain" href="/llms-full.txt" title="LLM full context" />
@@ -33,7 +39,7 @@
3339
<meta name="twitter:title" content="Open Brick | Parametric Brick STL Generator for 3D Printing" />
3440
<meta
3541
name="twitter:description"
36-
content="Browser-based brick, plate, tile, round brick, round tile, and mechanical axle STL generator powered by OpenSCAD templates."
42+
content="Browser-based brick, plate, tile, and mechanical axle STL generator powered by OpenSCAD templates."
3743
/>
3844
<meta name="twitter:image" content="https://openbrick.cogan.dev/src/logo.svg" />
3945

@@ -128,6 +134,6 @@ <h1>Open Brick</h1>
128134
</section>
129135
</main>
130136

131-
<script type="module" src="./src/app.js?v=20260208b"></script>
137+
<script type="module" src="./src/app.js?v=20260211a"></script>
132138
</body>
133139
</html>

‎manifest.webmanifest‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "Open Brick",
3+
"short_name": "Open Brick",
4+
"description": "Browser-based parametric brick STL generator for 3D printing.",
5+
"id": "./",
6+
"start_url": "./",
7+
"scope": "./",
8+
"display": "standalone",
9+
"orientation": "any",
10+
"background_color": "#0b1424",
11+
"theme_color": "#0b1424",
12+
"lang": "en",
13+
"categories": ["utilities", "productivity", "graphics"],
14+
"icons": [
15+
{
16+
"src": "./icons/icon-192.png",
17+
"sizes": "192x192",
18+
"type": "image/png",
19+
"purpose": "any"
20+
},
21+
{
22+
"src": "./icons/icon-512.png",
23+
"sizes": "512x512",
24+
"type": "image/png",
25+
"purpose": "any maskable"
26+
}
27+
]
28+
}

‎src/app.js‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ const state = {
116116
};
117117

118118
init().catch((error) => setStatus(error.message, true));
119+
registerServiceWorker();
119120

120121
async function init() {
121122
state.renderer = createPreviewRenderer(elements.canvas);
@@ -176,6 +177,18 @@ function bindEvents() {
176177
bindCanvasRotation();
177178
}
178179

180+
function registerServiceWorker() {
181+
if (!("serviceWorker" in navigator)) {
182+
return;
183+
}
184+
185+
window.addEventListener("load", () => {
186+
navigator.serviceWorker.register("./sw.js").catch((error) => {
187+
console.warn("Service worker registration failed:", error);
188+
});
189+
});
190+
}
191+
179192
function bindCanvasRotation() {
180193
const canvas = elements.canvas;
181194

‎sw.js‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
const CACHE_NAME = "open-brick-shell-v20260211d";
2+
const APP_SHELL_ASSETS = [
3+
"./",
4+
"./index.html",
5+
"./manifest.webmanifest",
6+
"./src/styles.css",
7+
"./src/app.js?v=20260211a",
8+
"./src/scad-engine.mjs",
9+
"./src/share-query.mjs",
10+
"./src/logo.svg",
11+
"./src/logo-light.svg",
12+
"./src/logo-wordmark.svg",
13+
"./src/logo-wordmark-light.svg",
14+
"./src/github.svg",
15+
"./scad/classic_brick.scad",
16+
"./scad/classic_plate.scad",
17+
"./scad/classic_tile.scad",
18+
"./scad/mechanical_axle.scad",
19+
"./icons/icon-192.png",
20+
"./icons/icon-512.png",
21+
];
22+
23+
self.addEventListener("install", (event) => {
24+
event.waitUntil(
25+
(async () => {
26+
const cache = await caches.open(CACHE_NAME);
27+
await cache.addAll(APP_SHELL_ASSETS);
28+
})()
29+
);
30+
31+
self.skipWaiting();
32+
});
33+
34+
self.addEventListener("activate", (event) => {
35+
event.waitUntil(
36+
(async () => {
37+
const cacheNames = await caches.keys();
38+
await Promise.all(
39+
cacheNames.map((cacheName) => {
40+
if (cacheName !== CACHE_NAME && cacheName.startsWith("open-brick-shell-v")) {
41+
return caches.delete(cacheName);
42+
}
43+
return Promise.resolve(false);
44+
})
45+
);
46+
await self.clients.claim();
47+
})()
48+
);
49+
});
50+
51+
self.addEventListener("fetch", (event) => {
52+
const request = event.request;
53+
if (request.method !== "GET") {
54+
return;
55+
}
56+
57+
const url = new URL(request.url);
58+
if (url.origin !== self.location.origin) {
59+
return;
60+
}
61+
62+
if (request.mode === "navigate") {
63+
event.respondWith(
64+
(async () => {
65+
try {
66+
return await fetch(request);
67+
} catch (_error) {
68+
const fallback = await caches.match("./index.html", { ignoreSearch: true });
69+
if (fallback) {
70+
return fallback;
71+
}
72+
return new Response("Offline", { status: 503, statusText: "Offline" });
73+
}
74+
})()
75+
);
76+
return;
77+
}
78+
79+
event.respondWith(
80+
(async () => {
81+
const cached = await caches.match(request);
82+
if (cached) {
83+
return cached;
84+
}
85+
86+
try {
87+
const response = await fetch(request);
88+
if (response.ok && (response.type === "basic" || response.type === "default")) {
89+
const cache = await caches.open(CACHE_NAME);
90+
cache.put(request, response.clone());
91+
}
92+
return response;
93+
} catch (_error) {
94+
const fallback = await caches.match("./index.html", { ignoreSearch: true });
95+
if (request.destination === "document" && fallback) {
96+
return fallback;
97+
}
98+
return new Response("Offline", { status: 503, statusText: "Offline" });
99+
}
100+
})()
101+
);
102+
});

0 commit comments

Comments
 (0)