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