An app for logging board climbs
0
fork

Configure Feed

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

feat: use /climb/{climbId}

+36 -37
+9 -3
www/index.ts
··· 46 46 // meta.title omitted: home-page sets its own via connectedCallback 47 47 meta: { navActive: '/' }, 48 48 }, 49 - '/climb': { 50 - landmarks: { subHeader: 'climb-header', main: 'climb-page' }, 51 - meta: { navActive: '/climb' }, 49 + '/climb/{id}': { 50 + landmarks: { 51 + subHeader: 'climb-header', 52 + main: (ctx) => ({ 53 + tag: 'climb-page', 54 + attrs: { 'climb-id': ctx.params.id }, 55 + }), 56 + }, 57 + meta: {}, 52 58 }, 53 59 '/library': { 54 60 landmarks: { subHeader: 'library-filters', main: 'library-page' },
+19 -26
www/routes/climb.ts
··· 23 23 } 24 24 25 25 update(climb: Benchmark): void { 26 - const nav = app.getNav()! 26 + const nav = app.getNav() 27 + const backRoute = nav?.backRoute ?? '/' 27 28 28 29 this.innerHTML = html` 29 30 <button class="back" aria-label="Back"> ··· 34 35 <small class="subtitle"> 35 36 ${GRADE_FULL[climb.grade] ?? ''} ${sandbagLabel( 36 37 climb.sandbagScore, 37 - )} · ${climb.setter} 38 + )}&#32;·&#32;${climb.setter} 38 39 </small> 39 40 </div> 40 41 <div class="meta">${safe(this.metaHtml(climb))}</div> 41 42 `.toString() 42 43 43 44 this.querySelector('.back')?.addEventListener('click', () => { 44 - globalThis.location.hash = nav.backRoute 45 + globalThis.location.hash = backRoute 45 46 }) 46 47 } 47 48 ··· 53 54 : '<ui-badge>Project</ui-badge>' 54 55 const stars = entry.rating ? '★'.repeat(entry.rating) : '' 55 56 return html` 56 - <span>${badge} ${stars}</span> 57 - <span>${entry.totalAttempts} attempts</span> 57 + <span>${safe(badge)} ${stars}</span> 58 + <span>${entry.totalAttempts}&#32;attempts</span> 58 59 `.toString() 59 60 } 60 61 return ` ··· 70 71 private dialogRating: number | null = null 71 72 72 73 async connectedCallback() { 73 - const nav = app.getNav() 74 - if (!nav) { 74 + const climbId = parseInt(this.getAttribute('climb-id') ?? '0', 10) 75 + if (!climbId) { 75 76 globalThis.location.hash = '/' 76 77 return 77 78 } 78 79 79 - let climb = nav.climb ?? null 80 + // Use cached climb from nav state if IDs match 81 + const nav = app.getNav() 82 + let climb = (nav?.climbId === climbId ? nav.climb : null) ?? null 83 + 80 84 if (!climb) { 81 85 try { 82 86 const all = await loadBenchmarks() 83 - climb = all.find((b) => b.id === nav.climbId) ?? null 87 + climb = all.find((b) => b.id === climbId) ?? null 84 88 } catch { 85 - globalThis.location.hash = nav.backRoute 89 + globalThis.location.hash = nav?.backRoute ?? '/' 86 90 return 87 91 } 88 92 } 89 93 90 94 if (!climb) { 91 - globalThis.location.hash = nav.backRoute 95 + globalThis.location.hash = nav?.backRoute ?? '/' 92 96 return 93 97 } 94 98 ··· 115 119 116 120 this.renderContent(climb) 117 121 118 - if (nav.filteredIds) { 122 + if (nav?.filteredIds) { 119 123 this.hammer = new Hammer(this, { touchAction: 'pan-y' }) 120 124 this.hammer.get('swipe').set({ direction: Hammer.DIRECTION_HORIZONTAL }) 121 125 this.hammer.on('swipeleft', () => this.navigate(1)) ··· 134 138 const next = nav.currentIndex + direction 135 139 if (next < 0 || next >= nav.filteredIds.length) return 136 140 const nextId = nav.filteredIds[next] 137 - loadBenchmarks().then((all) => { 138 - if (!this.isConnected) return 139 - const nextClimb = all.find((b) => b.id === nextId) 140 - if (!nextClimb) return 141 - app.setNav({ 142 - ...nav, 143 - climbId: nextId, 144 - climb: nextClimb, 145 - currentIndex: next, 146 - }) 147 - this.renderContent(nextClimb) 148 - }) 141 + app.setNav({ ...nav, climbId: nextId, currentIndex: next }) 142 + globalThis.location.hash = `/climb/${nextId}` 149 143 } 150 144 151 145 private renderContent(climb: Benchmark): void { 152 - const nav = app.getNav()! 153 - const config = BOARD_CONFIGS[nav.mbType] ?? BOARD_CONFIGS[0] 146 + const config = BOARD_CONFIGS[climb.mbType] ?? BOARD_CONFIGS[0] 154 147 const height = canvasHeight(config.rows) 155 148 156 149 activeClimbHeader?.update(climb)
+3 -3
www/routes/home.ts
··· 335 335 <button data-bm-id="${b.id}" role="listitem"> 336 336 <div class="bm-item-main"> 337 337 <span class="bm-name">${b.name}</span> 338 - ${sentTag} 338 + ${safe(sentTag)} 339 339 <ui-badge class="grade"> 340 340 ${gradeLabel(b.grade, this.gradeScale)} 341 341 </ui-badge> ··· 343 343 <div class="bm-item-meta"> 344 344 <span class="bm-setter">${b.setter}</span> 345 345 <span class="bm-stats"> 346 - ★ ${b.avgUserStars.toFixed(1)} · ${b 346 + ★ ${b.avgUserStars.toFixed(1)}&#32;·&#32;${b 347 347 .repeats.toLocaleString()} 348 348 </span> 349 349 </div> ··· 366 366 currentIndex: index, 367 367 backRoute: '/', 368 368 }) 369 - globalThis.location.hash = '/climb' 369 + globalThis.location.hash = `/climb/${climb.id}` 370 370 } 371 371 } 372 372
+5 -5
www/routes/library.ts
··· 1 - import { html } from '@bpev/civility' 1 + import { html, safe } from '@bpev/civility' 2 2 import app from '../models/app.ts' 3 3 import type { ClimbLogEntry } from '../models/schema.ts' 4 4 import { gradeLabel } from '../utils/benchmarks.ts' ··· 105 105 const climbId = parseInt(entry.dataset.climbId ?? '0') 106 106 const mbType = parseInt(entry.dataset.mbType ?? '0') 107 107 app.setNav({ climbId, mbType, backRoute: '/library' }) 108 - globalThis.location.hash = '/climb' 108 + globalThis.location.hash = `/climb/${climbId}` 109 109 } 110 110 } 111 111 ··· 159 159 <div class="lb-entry-row"> 160 160 <span class="lb-entry-name">${e.name}</span> 161 161 <ui-badge class="grade">${grade}</ui-badge> 162 - ${badge} 162 + ${safe(badge)} 163 163 </div> 164 164 <div class="lb-entry-meta"> 165 - <span>${e.totalAttempts} attempt${e.totalAttempts === 1 165 + <span>${e.totalAttempts}&#32;attempt${e.totalAttempts === 1 166 166 ? '' 167 167 : 's'}</span> 168 - ${stars ? `<span class="lb-entry-stars">${stars}</span>` : ''} 168 + ${safe(stars ? `<span class="lb-entry-stars">${stars}</span>` : '')} 169 169 <span>${e.setter}</span> 170 170 <span class="lb-entry-date">${formatDate(e.lastAttempted)}</span> 171 171 </div>