grain.social is a photo sharing platform built on atproto.

feat: add gallery state and reference to photo lexicon; update photo and gallery functions to support gallery items

Changed files
+57 -15
__generated__
types
social
grain
photo
lexicons
social
grain
photo
src
+15
__generated__/lexicons.ts
··· 3723 3723 ref: 'lex:social.grain.photo.defs#exifView', 3724 3724 description: 'EXIF metadata for the photo, if available.', 3725 3725 }, 3726 + gallery: { 3727 + type: 'ref', 3728 + ref: 'lex:social.grain.photo.defs#galleryState', 3729 + }, 3726 3730 }, 3727 3731 }, 3728 3732 exifView: { ··· 3774 3778 }, 3775 3779 model: { 3776 3780 type: 'string', 3781 + }, 3782 + }, 3783 + }, 3784 + galleryState: { 3785 + type: 'object', 3786 + description: 3787 + "Metadata about the photo's relationship with the subject content. Only has meaningful content when photo is attached to a gallery.", 3788 + properties: { 3789 + item: { 3790 + type: 'string', 3791 + format: 'at-uri', 3777 3792 }, 3778 3793 }, 3779 3794 },
+17
__generated__/types/social/grain/photo/defs.ts
··· 27 27 alt: string 28 28 aspectRatio?: SocialGrainDefs.AspectRatio 29 29 exif?: ExifView 30 + gallery?: GalleryState 30 31 } 31 32 32 33 const hashPhotoView = 'photoView' ··· 66 67 export function validateExifView<V>(v: V) { 67 68 return validate<ExifView & V>(v, id, hashExifView) 68 69 } 70 + 71 + /** Metadata about the photo's relationship with the subject content. Only has meaningful content when photo is attached to a gallery. */ 72 + export interface GalleryState { 73 + $type?: 'social.grain.photo.defs#galleryState' 74 + item?: string 75 + } 76 + 77 + const hashGalleryState = 'galleryState' 78 + 79 + export function isGalleryState<V>(v: V) { 80 + return is$typed(v, id, hashGalleryState) 81 + } 82 + 83 + export function validateGalleryState<V>(v: V) { 84 + return validate<GalleryState & V>(v, id, hashGalleryState) 85 + }
+9 -1
lexicons/social/grain/photo/defs.json
··· 30 30 "type": "ref", 31 31 "ref": "social.grain.photo.defs#exifView", 32 32 "description": "EXIF metadata for the photo, if available." 33 - } 33 + }, 34 + "gallery": { "type": "ref", "ref": "#galleryState" } 34 35 } 35 36 }, 36 37 "exifView": { ··· 51 52 "lensModel": { "type": "string" }, 52 53 "make": { "type": "string" }, 53 54 "model": { "type": "string" } 55 + } 56 + }, 57 + "galleryState": { 58 + "type": "object", 59 + "description": "Metadata about the photo's relationship with the subject content. Only has meaningful content when photo is attached to a gallery.", 60 + "properties": { 61 + "item": { "type": "string", "format": "at-uri" } 54 62 } 55 63 } 56 64 }
+14 -14
src/lib/gallery.ts
··· 24 24 import { getActorProfile, getActorProfilesBulk } from "./actor.ts"; 25 25 import { photoToView } from "./photo.ts"; 26 26 27 - type PhotoWithExif = WithBffMeta<Photo> & { 27 + type PhotoWithMeta = WithBffMeta<Photo> & { 28 + galleryItemUri?: string; 28 29 exif?: WithBffMeta<PhotoExif>; 29 30 }; 30 31 31 32 export function getGalleryItemsAndPhotos( 32 33 ctx: BffContext, 33 34 galleries: WithBffMeta<Gallery>[], 34 - ): Map<string, PhotoWithExif[]> { 35 + ): Map<string, PhotoWithMeta[]> { 35 36 const galleryUris = galleries.map( 36 37 (gallery) => 37 38 `at://${gallery.did}/social.grain.gallery/${new AtUri(gallery.uri).rkey}`, ··· 66 67 }, 67 68 ); 68 69 69 - const photosMap = new Map<string, PhotoWithExif>(); 70 + const photosMap = new Map<string, PhotoWithMeta>(); 70 71 const exifMap = new Map<string, WithBffMeta<PhotoExif>>(); 71 72 for (const exif of photosExif) { 72 73 exifMap.set(exif.photo, exif); ··· 76 77 photosMap.set(photo.uri, exif ? { ...photo, exif } : photo); 77 78 } 78 79 79 - const galleryPhotosMap = new Map<string, PhotoWithExif[]>(); 80 + const galleryPhotosMap = new Map<string, PhotoWithMeta[]>(); 80 81 for (const item of galleryItems) { 81 82 const galleryUri = item.gallery; 82 83 const photo = photosMap.get(item.item); ··· 86 87 } 87 88 88 89 if (photo) { 89 - galleryPhotosMap.get(galleryUri)?.push(photo); 90 + // Attach the galleryItem uri as itemUri 91 + galleryPhotosMap.get(galleryUri)?.push({ 92 + ...photo, 93 + galleryItemUri: item.uri, 94 + }); 90 95 } 91 96 } 92 97 ··· 207 212 }: { 208 213 record: WithBffMeta<Gallery>; 209 214 creator: Un$Typed<ProfileView>; 210 - items: PhotoWithExif[]; 215 + items: PhotoWithMeta[]; 211 216 labels: Label[]; 212 217 favCount?: number; 213 218 commentCount?: number; ··· 241 246 function itemToView( 242 247 did: string, 243 248 item: 244 - | PhotoWithExif 249 + | PhotoWithMeta 245 250 | { 246 251 $type: string; 247 252 }, 248 253 ): Un$Typed<PhotoView> | undefined { 249 254 if (isPhoto(item)) { 250 - return photoToView(did, item, item.exif); 255 + return photoToView(did, item, item.exif, item.galleryItemUri); 251 256 } 252 257 return undefined; 253 258 } 254 259 255 260 export function getGalleryCameras( 256 - items: Array<PhotoWithExif | PhotoView>, 261 + items: Array<PhotoWithMeta | PhotoView>, 257 262 ): string[] { 258 263 const cameras = new Set<string>(); 259 264 if (!Array.isArray(items)) return []; ··· 373 378 where: [{ field: "photo", in: photoUris }], 374 379 }, 375 380 ); 376 - const photosMap = new Map<string, PhotoWithExif>(); 377 381 const exifMap = new Map<string, WithBffMeta<PhotoExif>>(); 378 382 for (const exif of photosExif) { 379 383 exifMap.set(exif.photo, exif); 380 - } 381 - for (const photo of photos) { 382 - const exif = exifMap.get(photo.uri); 383 - photosMap.set(photo.uri, exif ? { ...photo, exif } : photo); 384 384 } 385 385 // Get the gallery DID from the URI 386 386 const did = (() => {
+2
src/lib/photo.ts
··· 40 40 did: string, 41 41 photo: WithBffMeta<Photo>, 42 42 exif?: WithBffMeta<PhotoExif>, 43 + galleryItemUri?: string, 43 44 ): $Typed<PhotoView> { 44 45 return { 45 46 $type: "social.grain.photo.defs#photoView", ··· 50 51 alt: photo.alt, 51 52 aspectRatio: photo.aspectRatio, 52 53 exif: exif ? exifToView(exif) : undefined, 54 + gallery: galleryItemUri ? { item: galleryItemUri } : undefined, 53 55 }; 54 56 } 55 57