Skip to content

Commit 4d8db7f

Browse files
committed
feat: studio service worker to preview uploaded medias
1 parent 81b25ef commit 4d8db7f

File tree

6 files changed

+100
-1
lines changed

6 files changed

+100
-1
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
"./app/utils": {
1515
"types": "./dist/app/utils.d.ts",
1616
"default": "./dist/app/utils.js"
17+
},
18+
"./app/service-worker": {
19+
"types": "./dist/app/service-worker.d.ts",
20+
"default": "./dist/app/service-worker.js"
1721
}
1822
},
1923
"main": "./dist/module/module.mjs",

src/app/src/service-worker.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
export const serviceWorker = () => `
2+
self.addEventListener('fetch', event => {
3+
const url = new URL(event.request.url);
4+
const isSameDomain = url.origin === self.location.origin;
5+
6+
if (!isSameDomain) {
7+
return event.respondWith(fetch(event.request));
8+
}
9+
10+
if (url.pathname.startsWith('/_ipx/_/') || ['jpg', 'png', 'jpeg', 'gif', 'webp'].includes(url.pathname.split('.').pop())) {
11+
console.log('Fetching from IndexedDB:', url.pathname);
12+
return event.respondWith(fetchFromIndexedDB(url));
13+
}
14+
15+
event.respondWith(fetch(event.request))
16+
})
17+
18+
function fetchFromIndexedDB(url) {
19+
const dbKey = ['public-assets:', url.pathname.replace('/_ipx/_/', '').replace('/', ':')].join('')
20+
return getData(dbKey).then(data => {
21+
if (!data) {
22+
return fetch(event.request);
23+
}
24+
25+
const json = JSON.parse(data)
26+
const parsed = parseDataUrl(json.modified.raw);
27+
const bytes = base64ToUint8Array(parsed.base64);
28+
29+
return new Response(bytes, {
30+
headers: { 'Content-Type': parsed.mime }
31+
});
32+
})
33+
}
34+
35+
function parseDataUrl(dataUrl) {
36+
// Example: data:image/png;base64,iVBORw0KG...
37+
const match = dataUrl.match(/^data:(.+);base64,(.+)$/);
38+
if (!match) return null;
39+
return {
40+
mime: match[1],
41+
base64: match[2]
42+
};
43+
}
44+
45+
function base64ToUint8Array(base64) {
46+
const binary = atob(base64);
47+
const len = binary.length;
48+
const bytes = new Uint8Array(len);
49+
for (let i = 0; i < len; i++) {
50+
bytes[i] = binary.charCodeAt(i);
51+
}
52+
return bytes;
53+
}
54+
55+
// IndexedDB
56+
function openDB() {
57+
return new Promise((resolve, reject) => {
58+
const request = indexedDB.open('nuxt-content-studio-media', 1);
59+
request.onupgradeneeded = event => {
60+
const db = event.target.result;
61+
db.createObjectStore('drafts', { keyPath: 'id' });
62+
};
63+
request.onsuccess = event => resolve(event.target.result);
64+
request.onerror = event => reject(event.target.error);
65+
});
66+
}
67+
68+
// Read data from the object store
69+
function getData(key) {
70+
return openDB().then(db => {
71+
return new Promise((resolve, reject) => {
72+
const tx = db.transaction('drafts', 'readonly');
73+
const store = tx.objectStore('drafts');
74+
const request = store.get(key);
75+
request.onsuccess = () => resolve(request.result);
76+
request.onerror = () => reject(request.error);
77+
});
78+
});
79+
}
80+
`

src/app/vite.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export default defineConfig({
100100
cssCodeSplit: false,
101101
outDir: '../../dist/app',
102102
lib: {
103-
entry: ['./src/index.ts', './src/utils.ts'],
103+
entry: ['./src/index.ts', './src/utils.ts', './src/service-worker.ts'],
104104
formats: ['es'],
105105
},
106106
sourcemap: false,

src/module/src/module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ export default defineNuxtModule<ModuleOptions>({
146146
route: '/__nuxt_content/studio',
147147
handler: runtime('./server/routes/admin'),
148148
})
149+
addServerHandler({
150+
route: '/sw.js',
151+
handler: runtime('./server/routes/sw'),
152+
})
149153
// addServerHandler({
150154
// route: '/__nuxt_content/studio/auth/google',
151155
// handler: runtime('./server/routes/auth/google.get'),

src/module/src/runtime/host.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ export function useStudioHost(user: StudioUser): StudioHost {
260260
.then((_localDatabaseAdapter) => {
261261
localDatabaseAdapter = _localDatabaseAdapter
262262
isMounted.value = true
263+
}).then(() => {
264+
if ('serviceWorker' in navigator) {
265+
navigator.serviceWorker.register('/sw.js')
266+
}
263267
})
264268
})()
265269

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { serviceWorker } from 'nuxt-studio/app/service-worker'
2+
import { eventHandler, setHeader } from 'h3'
3+
4+
export default eventHandler(async (event) => {
5+
setHeader(event, 'Content-Type', 'application/javascript')
6+
return serviceWorker()
7+
})

0 commit comments

Comments
 (0)