grain.social is a photo sharing platform built on atproto. grain.social
atproto photography appview
57
fork

Configure Feed

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

feat: sync profile tab selection with URL query param

Derive viewMode from ?tab= query param so browser back navigation
returns to the correct tab after visiting a gallery. Also fix type
error in GalleryCard deleteGallery rkey.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+27 -6
+1 -1
app/lib/components/molecules/GalleryCard.svelte
··· 32 32 const rkey = gallery.uri.split('/').pop() 33 33 deleting = true 34 34 try { 35 - await callXrpc('social.grain.unspecced.deleteGallery', { rkey }) 35 + await callXrpc('social.grain.unspecced.deleteGallery', { rkey: rkey! }) 36 36 queryClient.invalidateQueries({ queryKey: ['getFeed'] }) 37 37 goto(`/profile/${gallery.creator?.did}`) 38 38 } catch (err) {
+26 -5
app/routes/profile/[did]/+page.svelte
··· 13 13 import { viewer as viewerStore } from '$lib/stores' 14 14 import StoryViewer from '$lib/components/organisms/StoryViewer.svelte' 15 15 import StoryArchive from '$lib/components/molecules/StoryArchive.svelte' 16 + import { page } from '$app/state' 17 + import { goto } from '$app/navigation' 18 + 19 + const validTabs = ['grid', 'favorites', 'stories'] as const 20 + type ViewMode = (typeof validTabs)[number] 21 + function parseTab(v: string | null): ViewMode { 22 + return validTabs.includes(v as ViewMode) ? (v as ViewMode) : 'grid' 23 + } 16 24 17 25 let { data } = $props() 18 26 let lightboxSrc: string | null = $state(null) 19 - let viewMode: 'grid' | 'favorites' | 'stories' = $state('grid') 27 + const viewMode: ViewMode = $derived.by(() => { 28 + page.url.href 29 + return parseTab(page.url.searchParams.get('tab')) 30 + }) 20 31 let followersOffset = $state(0) 21 32 let showStoryViewer = $state(false) 22 33 const did = $derived(data.did) 23 - $effect(() => { void did; void profile.data; followersOffset = 0; viewMode = 'grid' }) 34 + $effect(() => { void did; void profile.data; followersOffset = 0 }) 35 + 36 + function setTab(tab: ViewMode) { 37 + const url = new URL(page.url) 38 + if (tab === 'grid') { 39 + url.searchParams.delete('tab') 40 + } else { 41 + url.searchParams.set('tab', tab) 42 + } 43 + goto(url, { replaceState: true, keepFocus: true, noScroll: true }) 44 + } 24 45 const viewerDid = $derived($viewerStore?.did) 25 46 const isOwnProfile = $derived(viewerDid === did) 26 47 ··· 132 153 {/if} 133 154 134 155 <div class="view-toggle"> 135 - <button class="toggle-btn" class:active={viewMode === 'grid'} onclick={() => (viewMode = 'grid')} aria-label="Grid view"> 156 + <button class="toggle-btn" class:active={viewMode === 'grid'} onclick={() => setTab('grid')} aria-label="Grid view"> 136 157 <Grid3x3 size={20} /> 137 158 </button> 138 159 {#if isOwnProfile} 139 - <button class="toggle-btn" class:active={viewMode === 'favorites'} onclick={() => (viewMode = 'favorites')} aria-label="Favorites"> 160 + <button class="toggle-btn" class:active={viewMode === 'favorites'} onclick={() => setTab('favorites')} aria-label="Favorites"> 140 161 <Heart size={20} /> 141 162 </button> 142 - <button class="toggle-btn" class:active={viewMode === 'stories'} onclick={() => (viewMode = 'stories')} aria-label="Story archive"> 163 + <button class="toggle-btn" class:active={viewMode === 'stories'} onclick={() => setTab('stories')} aria-label="Story archive"> 143 164 <Clock size={20} /> 144 165 </button> 145 166 {/if}