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

Configure Feed

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

fix: revert "download all" button

+6 -196
+6 -196
www/routes/settings-downloads.ts
··· 22 22 group: string 23 23 } 24 24 25 - type GroupEntry = { 26 - id: string 27 - label: string 28 - group: string 29 - } 30 - 31 25 type TreeNode = { 32 26 tiles: TileConfig[] 33 - groups: GroupEntry[] 34 27 children: Map<string, TreeNode> 35 28 } 36 29 ··· 104 97 this.requestUpdate() 105 98 } 106 99 107 - async #handleDownloadGroup(group: GroupEntry): Promise<void> { 108 - const tilesInGroup = this.tiles.filter( 109 - (t) => t.group === `${group.group}/${group.id}` && t.filename, 110 - ) 111 - 112 - for (const tile of tilesInGroup) { 113 - if (!tile.filename) continue 114 - this.statuses[tile.id] = 'downloading' 115 - } 116 - this.requestUpdate() 117 - 118 - let failed = false 119 - for (const tile of tilesInGroup) { 120 - if (!tile.filename) continue 121 - try { 122 - await downloadAndSavePMTiles(tile.path!, tile.filename) 123 - this.statuses[tile.id] = 'cached' 124 - } catch (err) { 125 - this.statuses[tile.id] = 'error' 126 - this.errors[tile.id] = err instanceof Error 127 - ? err.message 128 - : 'Download failed' 129 - failed = true 130 - } 131 - } 132 - 133 - if (!failed) { 134 - globalThis.dispatchEvent(new CustomEvent('pmtiles-updated')) 135 - } 136 - this.requestUpdate() 137 - } 138 - 139 - async #handleDeleteGroup(group: GroupEntry): Promise<void> { 140 - const tilesInGroup = this.tiles.filter( 141 - (t) => t.group === `${group.group}/${group.id}` && t.filename, 142 - ) 143 - 144 - for (const tile of tilesInGroup) { 145 - if (!tile.filename) continue 146 - this.statuses[tile.id] = 'deleting' 147 - } 148 - this.requestUpdate() 149 - 150 - for (const tile of tilesInGroup) { 151 - if (!tile.filename) continue 152 - try { 153 - await deletePMTiles(tile.filename) 154 - this.statuses[tile.id] = 'available' 155 - } catch (err) { 156 - this.statuses[tile.id] = 'error' 157 - this.errors[tile.id] = err instanceof Error 158 - ? err.message 159 - : 'Delete failed' 160 - } 161 - } 162 - this.requestUpdate() 163 - } 164 - 165 100 #buildTree(tiles: TileConfig[]): Map<string, TreeNode> { 166 101 const root = new Map<string, TreeNode>() 167 102 168 103 const tileEntries = tiles.filter((t) => t.filename) 169 - const groupEntries = tiles.filter((t) => !t.filename) as GroupEntry[] 170 104 171 105 for (const tile of tileEntries) { 172 106 const segments = tile.group ? tile.group.split('/') : ['other'] 173 107 this.#insertTile(root, segments, tile) 174 108 } 175 109 176 - for (const group of groupEntries) { 177 - const segments = group.group ? group.group.split('/') : ['other'] 178 - this.#insertGroup(root, segments, group) 179 - } 180 - 181 110 return root 182 111 } 183 112 ··· 188 117 ): void { 189 118 const [head, ...rest] = segments 190 119 if (!map.has(head)) { 191 - map.set(head, { tiles: [], groups: [], children: new Map() }) 120 + map.set(head, { tiles: [], children: new Map() }) 192 121 } 193 122 const node = map.get(head)! 194 123 if (rest.length === 0) { ··· 198 127 } 199 128 } 200 129 201 - #insertGroup( 202 - map: Map<string, TreeNode>, 203 - segments: string[], 204 - group: GroupEntry, 205 - ): void { 206 - const [head, ...rest] = segments 207 - if (!map.has(head)) { 208 - map.set(head, { tiles: [], groups: [], children: new Map() }) 209 - } 210 - const node = map.get(head)! 211 - if (rest.length === 0) { 212 - node.groups.push(group) 213 - } else { 214 - this.#insertGroup(node.children, rest, group) 215 - } 216 - } 217 - 218 - #getGroupStatus(group: GroupEntry): TileStatus { 219 - const tilesInGroup = this.tiles.filter( 220 - (t) => t.group === `${group.group}/${group.id}` && t.filename, 221 - ) 222 - 223 - if (tilesInGroup.length === 0) return 'available' 224 - 225 - let allCached = true 226 - let anyDownloading = false 227 - for (const tile of tilesInGroup) { 228 - const status = this.statuses[tile.id] ?? 'available' 229 - if (status === 'checking' || status === 'downloading') { 230 - anyDownloading = true 231 - } 232 - if (status !== 'cached') allCached = false 233 - } 234 - 235 - if (anyDownloading) return 'downloading' 236 - return allCached ? 'cached' : 'available' 237 - } 238 - 239 130 #renderTree(map: Map<string, TreeNode>): TemplateResult { 240 131 const sorted = [...map.entries()].sort(([a], [b]) => { 241 132 if (a === 'world') return -1 ··· 245 136 return html` 246 137 ${sorted.map(([key, node]) => { 247 138 const directTiles = [...node.tiles].sort((a, b) => 248 - a.label.localeCompare(b.label) 249 - ) 250 - const directGroups = [...node.groups].sort((a, b) => 251 139 a.label.localeCompare(b.label) 252 140 ) 253 141 return html` ··· 255 143 <summary>${slugToLabel(key)}</summary> 256 144 ${node.children.size > 0 257 145 ? this.#renderTree(node.children) 258 - : ''} ${directGroups.map((group) => 259 - this.#renderGroupItem(group) 260 - )} ${directTiles.length > 0 146 + : ''} ${directTiles.length > 0 261 147 ? html` 262 148 <div class="tile-list"> 263 149 ${directTiles.map((tile) => this.#renderTileItem(tile))} ··· 270 156 ` 271 157 } 272 158 273 - #renderGroupItem(group: GroupEntry): TemplateResult { 274 - const status = this.#getGroupStatus(group) 275 - const tilesInGroup = this.tiles.filter( 276 - (t) => t.group === `${group.group}/${group.id}` && t.filename, 277 - ) 278 - const count = tilesInGroup.length 279 - 280 - return html` 281 - <div class="tile-item"> 282 - <div class="tile-item-info"> 283 - <span class="tile-item-name">${group.label} (${count} regions)</span> 284 - <div class="tile-item-meta">Download all regions at once</div> 285 - ${status === 'error' 286 - ? html` 287 - <div class="tile-item-meta settings-data-status--error"> 288 - ${this.errors[group.id] ?? 'Download failed'} 289 - </div> 290 - ` 291 - : ''} 292 - </div> 293 - <div class="tile-item-action"> 294 - ${this.#renderGroupAction(group, status)} 295 - </div> 296 - </div> 297 - ` 298 - } 299 - 300 - #renderGroupAction(group: GroupEntry, status: TileStatus): TemplateResult { 301 - if (status === 'checking') { 302 - return html` 303 - <ui-spinner></ui-spinner> 304 - ` 305 - } 306 - if (status === 'cached') { 307 - return html` 308 - <span class="tile-status-cached">✓ Downloaded</span> 309 - <button 310 - class="action" 311 - style="width:auto;padding:var(--s2) var(--s3)" 312 - @click="${() => this.#handleDeleteGroup(group)}" 313 - > 314 - <img 315 - src="/static/icons/x.svg" 316 - alt="" 317 - aria-hidden="true" 318 - style="width:16px;height:16px" 319 - > 320 - Remove 321 - </button> 322 - ` 323 - } 324 - if (status === 'downloading') { 325 - return html` 326 - <ui-spinner></ui-spinner> 327 - ` 328 - } 329 - return html` 330 - <button 331 - class="action" 332 - style="width:auto;padding:var(--s2) var(--s3)" 333 - @click="${() => this.#handleDownloadGroup(group)}" 334 - > 335 - <img 336 - src="/static/icons/download.svg" 337 - alt="" 338 - aria-hidden="true" 339 - style="width:16px;height:16px" 340 - > 341 - Download all 342 - </button> 343 - ` 344 - } 345 - 346 159 override render(): TemplateResult { 347 160 const q = this.filter.toLowerCase() 348 161 const filteredTiles = q 349 162 ? this.tiles.filter((t) => 350 - t.label.toLowerCase().includes(q) || 351 - t.group.toLowerCase().includes(q) 163 + t.filename && 164 + (t.label.toLowerCase().includes(q) || 165 + t.group.toLowerCase().includes(q)) 352 166 ) 353 167 : null 354 168 ··· 372 186 ${filteredTiles 373 187 ? html` 374 188 <div class="tile-list"> 375 - ${filteredTiles.map((tile) => 376 - tile.filename 377 - ? this.#renderTileItem(tile) 378 - : this.#renderGroupItem(tile as GroupEntry) 379 - )} 189 + ${filteredTiles.map((tile) => this.#renderTileItem(tile))} 380 190 </div> 381 191 ` 382 192 : this.#renderTree(this.#buildTree(this.tiles))}