Offline-capable geomap, meant for storing location bookmarks
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: update schema for civility 1.0.0

+77 -64
+8 -8
deno.json
··· 20 20 "semiColons": false 21 21 }, 22 22 "imports": { 23 - "@civility/blobs": "jsr:@civility/blobs@^1.0.0-beta.1", 24 - "@civility/blobs/idb": "jsr:@civility/blobs@^1.0.0-beta.1/idb", 25 - "@civility/store": "jsr:@civility/store@^1.0.0-beta.6", 26 - "@civility/store/idb": "jsr:@civility/store@^1.0.0-beta.6/idb", 27 - "@civility/sync": "jsr:@civility/sync@^1.0.0-beta.7", 28 - "@civility/ui": "jsr:@civility/ui@^1.0.0-beta.1", 29 - "@civility/workers": "jsr:@civility/workers@^0.2.4", 23 + "@civility/blobs": "jsr:@civility/blobs@^1.0.0-beta.4", 24 + "@civility/blobs/idb": "jsr:@civility/blobs@^1.0.0-beta.4/idb", 25 + "@civility/store": "jsr:@civility/store@^1.0.0-beta.8", 26 + "@civility/store/idb": "jsr:@civility/store@^1.0.0-beta.8/idb", 27 + "@civility/sync": "jsr:@civility/sync@^1.0.0-beta.10", 28 + "@civility/ui": "jsr:@civility/ui@^1.0.0-beta.2", 29 + "@civility/workers": "jsr:@civility/workers@^0.2.5", 30 30 "@zod/zod": "jsr:@zod/zod@^4.3.6", 31 31 "fflate": "npm:fflate@^0.8.2", 32 32 "lit": "npm:lit@^3.3.2", 33 - "maplibre-gl": "npm:maplibre-gl@^5.21.0", 33 + "maplibre-gl": "npm:maplibre-gl@^5.22.0", 34 34 "pmtiles": "npm:pmtiles@^4.4.0", 35 35 "pmtiles-offline": "npm:pmtiles-offline@^1.0.0" 36 36 }
+32 -24
deno.lock
··· 1 1 { 2 2 "version": "5", 3 3 "specifiers": { 4 - "jsr:@civility/blobs@^1.0.0-beta.1": "1.0.0-beta.4", 5 4 "jsr:@civility/blobs@^1.0.0-beta.4": "1.0.0-beta.4", 6 - "jsr:@civility/store@^1.0.0-beta.6": "1.0.0-beta.6", 7 - "jsr:@civility/sync@^1.0.0-beta.7": "1.0.0-beta.7", 8 - "jsr:@civility/ui@^1.0.0-beta.1": "1.0.0-beta.1", 9 - "jsr:@civility/workers@~0.2.4": "0.2.5", 5 + "jsr:@civility/store@^1.0.0-beta.8": "1.0.0-beta.8", 6 + "jsr:@civility/sync@^1.0.0-beta.10": "1.0.0-beta.10", 7 + "jsr:@civility/ui@^1.0.0-beta.2": "1.0.0-beta.2", 8 + "jsr:@civility/workers@~0.2.5": "0.2.5", 10 9 "jsr:@cliffy/command@1": "1.0.0", 11 10 "jsr:@cliffy/flags@1.0.0": "1.0.0", 12 11 "jsr:@cliffy/internal@1.0.0": "1.0.0", 13 12 "jsr:@cliffy/table@1.0.0": "1.0.0", 13 + "jsr:@paulmillr/qr@~0.5.5": "0.5.5", 14 14 "jsr:@std/fmt@^1.0.9": "1.0.9", 15 15 "jsr:@std/fs@1": "1.0.23", 16 + "jsr:@std/fs@^1.0.23": "1.0.23", 16 17 "jsr:@std/html@^1.0.5": "1.0.5", 17 18 "jsr:@std/internal@^1.0.12": "1.0.12", 18 19 "jsr:@std/path@^1.1.4": "1.1.4", ··· 24 25 "npm:fast-json-patch@^3.1.1": "3.1.1", 25 26 "npm:fflate@~0.8.2": "0.8.2", 26 27 "npm:lit@^3.3.2": "3.3.2", 27 - "npm:maplibre-gl@^5.21.0": "5.21.0", 28 + "npm:maplibre-gl@^5.22.0": "5.22.0", 28 29 "npm:pmtiles-offline@1": "1.0.0", 29 30 "npm:pmtiles@^4.4.0": "4.4.0" 30 31 }, ··· 32 33 "@civility/blobs@1.0.0-beta.4": { 33 34 "integrity": "6806eb2a5b02e9e611385107b539abe0b2fe8e17066cfc42eaf467e301a6afa0" 34 35 }, 35 - "@civility/store@1.0.0-beta.6": { 36 - "integrity": "92226d6e669fd90da7dac1da9f60c63587fb194df1d59a34791f3b827c000383", 36 + "@civility/store@1.0.0-beta.8": { 37 + "integrity": "97d24ab2100fd2dbc5665e45abc1d0de68c81f5b717ddc95313c6a1502eb0546", 37 38 "dependencies": [ 39 + "jsr:@std/fs@^1.0.23", 40 + "jsr:@std/path", 38 41 "jsr:@std/semver", 39 42 "jsr:@std/ulid", 40 43 "npm:fast-json-patch" 41 44 ] 42 45 }, 43 - "@civility/sync@1.0.0-beta.7": { 44 - "integrity": "2997902549d7fbe6810c208efa8cd79c852192ca1c36cad90dc6fc1f27a6cf47", 46 + "@civility/sync@1.0.0-beta.10": { 47 + "integrity": "15345da12c2b3d7f83e31626350dd066561a5cac86cb317f094c8ede73872183", 45 48 "dependencies": [ 46 - "jsr:@civility/blobs@^1.0.0-beta.4", 47 - "jsr:@civility/store" 49 + "jsr:@civility/blobs", 50 + "jsr:@civility/store", 51 + "jsr:@paulmillr/qr" 48 52 ] 49 53 }, 50 - "@civility/ui@1.0.0-beta.1": { 51 - "integrity": "34baed127597c084f0326649de1f9d0d025d5cf91ec81a35e3048c8919e01eb2", 54 + "@civility/ui@1.0.0-beta.2": { 55 + "integrity": "4cdef14beafa95ca418cdd14f5f9d54551bd5c8f9f84517438fd848133be340e", 52 56 "dependencies": [ 53 57 "jsr:@std/html", 54 58 "npm:lit" ··· 67 71 "jsr:@cliffy/internal", 68 72 "jsr:@cliffy/table", 69 73 "jsr:@std/fmt", 74 + "jsr:@std/semver", 70 75 "jsr:@std/text" 71 76 ] 72 77 }, ··· 85 90 "dependencies": [ 86 91 "jsr:@std/fmt" 87 92 ] 93 + }, 94 + "@paulmillr/qr@0.5.5": { 95 + "integrity": "2f8ff22c8d2194f2147eac1b3093f5e85f648c0a8005d5635a617fb72bf5ae38" 88 96 }, 89 97 "@std/fmt@1.0.9": { 90 98 "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" ··· 163 171 "kdbush" 164 172 ] 165 173 }, 166 - "@maplibre/maplibre-gl-style-spec@24.7.0": { 167 - "integrity": "sha512-Ed7rcKYU5iELfablg9Mj+TVCsXsPBgdMyXPRAxb2v7oWg9YJnpQdZ5msDs1LESu/mtXy3Z48Vdppv2t/x5kAhw==", 174 + "@maplibre/maplibre-gl-style-spec@24.8.1": { 175 + "integrity": "sha512-zxa92qF96ZNojLxeAjnaRpjVCy+swoUNJvDhtpC90k7u5F0TMr4GmvNqMKvYrMoPB8d7gRSXbMG1hBbmgESIsw==", 168 176 "dependencies": [ 169 177 "@mapbox/jsonlint-lines-primitives", 170 178 "@mapbox/unitbezier", ··· 345 353 "lit-html" 346 354 ] 347 355 }, 348 - "maplibre-gl@5.21.0": { 349 - "integrity": "sha512-n0v4J/Ge0EG8ix/z3TY3ragtJYMqzbtSnj1riOC0OwQbzwp0lUF2maS1ve1z8HhitQCKtZZiZJhb8to36aMMfQ==", 356 + "maplibre-gl@5.22.0": { 357 + "integrity": "sha512-nc8YA+YSEioMZg5W0cb6Cf3wQ8aJge66dsttyBgpOArOnlmFJO1Kc5G32kYVPeUYhLpBja83T99uanmJvYAIyQ==", 350 358 "dependencies": [ 351 359 "@mapbox/jsonlint-lines-primitives", 352 360 "@mapbox/point-geometry", ··· 462 470 }, 463 471 "workspace": { 464 472 "dependencies": [ 465 - "jsr:@civility/blobs@^1.0.0-beta.1", 466 - "jsr:@civility/store@^1.0.0-beta.6", 467 - "jsr:@civility/sync@^1.0.0-beta.7", 468 - "jsr:@civility/ui@^1.0.0-beta.1", 469 - "jsr:@civility/workers@~0.2.4", 473 + "jsr:@civility/blobs@^1.0.0-beta.4", 474 + "jsr:@civility/store@^1.0.0-beta.8", 475 + "jsr:@civility/sync@^1.0.0-beta.10", 476 + "jsr:@civility/ui@^1.0.0-beta.2", 477 + "jsr:@civility/workers@~0.2.5", 470 478 "jsr:@zod/zod@^4.3.6", 471 479 "npm:fflate@~0.8.2", 472 480 "npm:lit@^3.3.2", 473 - "npm:maplibre-gl@^5.21.0", 481 + "npm:maplibre-gl@^5.22.0", 474 482 "npm:pmtiles-offline@1", 475 483 "npm:pmtiles@^4.4.0" 476 484 ],
+8 -6
www/components/m-map.ts
··· 347 347 const collectionColors = new Map( 348 348 app.bookmarkCollections.map((c) => [c.id, c.color ?? null]), 349 349 ) 350 + 350 351 const geojson = { 351 352 type: 'FeatureCollection' as const, 352 353 features: app.bookmarks.map((b) => ({ ··· 595 596 if (!this.#map) return 596 597 let worldFilename = 'world_z3.pmtiles' 597 598 if (!registeredSources.has('world')) { 598 - const z7 = await getCachedPMTiles('world_z7.pmtiles') 599 - const z6 = !z7 ? await getCachedPMTiles('world_z6.pmtiles') : null 600 - const z5 = !z6 ? await getCachedPMTiles('world_z5.pmtiles') : null 601 - if (z7) worldFilename = 'world_z7.pmtiles' 602 - else if (z6) worldFilename = 'world_z6.pmtiles' 603 - else if (z5) worldFilename = 'world_z5.pmtiles' 599 + const z7 = await getCachedPMTiles('world/world_z7.pmtiles') 600 + const z6 = !z7 ? await getCachedPMTiles('world/world_z6.pmtiles') : null 601 + const z5 = !z6 ? await getCachedPMTiles('world/world_z5.pmtiles') : null 602 + 603 + if (z7) worldFilename = 'world/world_z7.pmtiles' 604 + else if (z6) worldFilename = 'world/world_z6.pmtiles' 605 + else if (z5) worldFilename = 'world/world_z5.pmtiles' 604 606 const pmtiles = z7 ?? z6 ?? z5 ?? await downloadAndSavePMTiles( 605 607 '/static/tiles/world/world_z3.pmtiles', 606 608 'world_z3.pmtiles',
+21 -17
www/models/app.ts
··· 25 25 } 26 26 27 27 const backend = new IDBStorage({ dbName: 'maps-store' }) 28 - const blobStore = new BlobStore(new IDBBlobStorage({ dbName: 'maps-blobs' })) 28 + const blobBackend = new IDBBlobStorage({ dbName: 'maps-blobs' }) 29 + const blobStore = new BlobStore(blobBackend) 29 30 const store = new Store(backend, { 30 31 documents: ['preferences', 'searchHistory'], 31 32 collections: ['bookmarks', 'bookmarkCollections'], ··· 58 59 59 60 synced = new Synced({ 60 61 stores: [bookmarksColl, collectionsColl], 61 - appId: 'map-app', 62 + appId: 'bpev-maps', 62 63 blobStore, 63 64 }) 64 65 ··· 116 117 // ====== BOOKMARKS ====== 117 118 118 119 get bookmarks(): Bookmark[] { 119 - return [...(bookmarksColl.value?.values() ?? [])].sort( 120 - (a, b) => a.createdAt < b.createdAt ? -1 : 1, 121 - ) 120 + return [...(bookmarksColl.value?.values() ?? [])] 121 + .sort((a, b) => a.createdAt < b.createdAt ? -1 : 1) 122 122 } 123 123 124 124 get bookmarkCollections(): BookmarkCollection[] { ··· 313 313 314 314 async deleteAllData(): Promise<{ success: boolean; error?: string }> { 315 315 try { 316 - await store.deleteAll() 317 - await new Promise<void>((resolve, reject) => { 318 - const req = indexedDB.deleteDatabase('maps-offline') 319 - req.onsuccess = () => resolve() 320 - req.onerror = () => reject(req.error) 321 - req.onblocked = () => 322 - reject( 323 - new Error( 324 - 'Database deletion blocked — close other tabs and try again', 325 - ), 326 - ) 327 - }) 316 + // Close open connections before deleting so the DB isn't blocked 317 + await store.dispose() 318 + blobBackend.close() 319 + 320 + const deleteDB = (name: string) => 321 + new Promise<void>((resolve, reject) => { 322 + const req = indexedDB.deleteDatabase(name) 323 + req.onsuccess = () => resolve() 324 + req.onerror = () => reject(req.error) 325 + // onblocked fires when another tab still has the DB open; 326 + // resolve anyway — the deletion will complete once they close. 327 + req.onblocked = () => resolve() 328 + }) 329 + 330 + await deleteDB('maps-store') 331 + await deleteDB('maps-blobs') 328 332 return { success: true } 329 333 } catch (error) { 330 334 return {
+6 -6
www/models/schema/v0.ts
··· 66 66 // GeoJSON coordinate order: [lng, lat] 67 67 coordinates: z.tuple([Lng, Lat]), 68 68 }), 69 - properties: BookmarkProperties, 69 + properties: z._default(BookmarkProperties, {}), 70 70 categories: z._default(z.array(z.string()), []), 71 71 zoom: z._default(z.number(), 12), 72 72 images: z._default(z.array(BlobRef), []), ··· 79 79 /** Resolves a human-readable name for display, falling through available fields. */ 80 80 export function bookmarkDisplayName(b: Bookmark): string { 81 81 return ( 82 - b.properties.name ?? 83 - b.properties.displayName ?? 84 - b.properties.address?.displayText ?? 85 - `${b.geometry.coordinates[1].toFixed(4)}, ${ 86 - b.geometry.coordinates[0].toFixed(4) 82 + b.properties?.name ?? 83 + b.properties?.displayName ?? 84 + b.properties?.address?.displayText ?? 85 + `${(b.geometry?.coordinates?.[1] || 0).toFixed(4)}, ${ 86 + (b.geometry?.coordinates?.[0] || 0).toFixed(4) 87 87 }` 88 88 ) 89 89 }
+2 -3
www/routes/bookmarks.ts
··· 267 267 } 268 268 269 269 override render(): TemplateResult { 270 - const uncategorized = this.bookmarks.filter((b) => 271 - b.categories.length === 0 272 - ) 270 + const uncategorized = this.bookmarks 271 + .filter((b) => (b.categories || []).length === 0) 273 272 const hasCollections = this.collections.length > 0 274 273 const isEmpty = this.bookmarks.length === 0 && !hasCollections 275 274