WIP PWA for Grain
0
fork

Configure Feed

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

at main 238 lines 6.4 kB view raw
1import { auth } from './auth.js'; 2import { recordCache } from './record-cache.js'; 3import { parseTextToFacets } from '../lib/richtext.js'; 4import { grainApi } from './grain-api.js'; 5 6class MutationsService { 7 async createFavorite(galleryUri) { 8 const client = auth.getClient(); 9 const result = await client.mutate(` 10 mutation CreateFavorite($input: SocialGrainFavoriteInput!) { 11 createSocialGrainFavorite(input: $input) { uri } 12 } 13 `, { 14 input: { 15 subject: galleryUri, 16 createdAt: new Date().toISOString() 17 } 18 }); 19 20 return result.createSocialGrainFavorite.uri; 21 } 22 23 async deleteFavorite(favoriteUri) { 24 const client = auth.getClient(); 25 const rkey = favoriteUri.split('/').pop(); 26 await client.mutate(` 27 mutation DeleteFavorite($rkey: String!) { 28 deleteSocialGrainFavorite(rkey: $rkey) { uri } 29 } 30 `, { rkey }); 31 } 32 33 async toggleFavorite(galleryUri, viewerHasFavorited, viewerFavoriteUri, currentCount) { 34 let newFavoriteUri = null; 35 let newCount = currentCount; 36 37 if (viewerHasFavorited) { 38 await this.deleteFavorite(viewerFavoriteUri); 39 newCount = Math.max(0, currentCount - 1); 40 } else { 41 newFavoriteUri = await this.createFavorite(galleryUri); 42 newCount = currentCount + 1; 43 } 44 45 const update = { 46 viewerHasFavorited: !viewerHasFavorited, 47 viewerFavoriteUri: newFavoriteUri, 48 favoriteCount: newCount 49 }; 50 51 recordCache.set(galleryUri, update); 52 53 return update; 54 } 55 56 async createFollow(did) { 57 const client = auth.getClient(); 58 const result = await client.mutate(` 59 mutation CreateFollow($input: SocialGrainGraphFollowInput!) { 60 createSocialGrainGraphFollow(input: $input) { uri } 61 } 62 `, { 63 input: { 64 subject: did, 65 createdAt: new Date().toISOString() 66 } 67 }); 68 69 return result.createSocialGrainGraphFollow.uri; 70 } 71 72 async deleteFollow(followUri) { 73 const client = auth.getClient(); 74 const rkey = followUri.split('/').pop(); 75 await client.mutate(` 76 mutation DeleteFollow($rkey: String!) { 77 deleteSocialGrainGraphFollow(rkey: $rkey) { uri } 78 } 79 `, { rkey }); 80 } 81 82 async toggleFollow(handle, did, viewerIsFollowing, viewerFollowUri, currentCount) { 83 let newFollowUri = null; 84 let newCount = currentCount; 85 86 if (viewerIsFollowing) { 87 await this.deleteFollow(viewerFollowUri); 88 newCount = Math.max(0, currentCount - 1); 89 } else { 90 newFollowUri = await this.createFollow(did); 91 newCount = currentCount + 1; 92 } 93 94 const update = { 95 viewerIsFollowing: !viewerIsFollowing, 96 viewerFollowUri: newFollowUri, 97 followerCount: newCount 98 }; 99 100 recordCache.set(`profile:${handle}`, update); 101 102 return update; 103 } 104 105 async createComment(galleryUri, text, replyToUri = null, focusUri = null) { 106 const client = auth.getClient(); 107 108 // Parse text for facets with handle resolution 109 const resolveHandle = async (handle) => grainApi.resolveHandle(handle); 110 const { facets } = await parseTextToFacets(text, resolveHandle); 111 112 const input = { 113 subject: galleryUri, 114 text, 115 createdAt: new Date().toISOString() 116 }; 117 118 // Only include facets if we found any 119 if (facets && facets.length > 0) { 120 input.facets = facets; 121 } 122 123 if (replyToUri) { 124 input.replyTo = replyToUri; 125 } 126 127 if (focusUri) { 128 input.focus = focusUri; 129 } 130 131 const result = await client.mutate(` 132 mutation CreateComment($input: SocialGrainCommentInput!) { 133 createSocialGrainComment(input: $input) { uri } 134 } 135 `, { input }); 136 137 return result.createSocialGrainComment.uri; 138 } 139 140 async deleteComment(commentUri) { 141 const client = auth.getClient(); 142 const rkey = commentUri.split('/').pop(); 143 await client.mutate(` 144 mutation DeleteComment($rkey: String!) { 145 deleteSocialGrainComment(rkey: $rkey) { uri } 146 } 147 `, { rkey }); 148 } 149 150 async uploadBlob(base64Data, mimeType = 'image/jpeg') { 151 const client = auth.getClient(); 152 const result = await client.mutate(` 153 mutation UploadBlob($data: String!, $mimeType: String!) { 154 uploadBlob(data: $data, mimeType: $mimeType) { 155 ref 156 mimeType 157 size 158 } 159 } 160 `, { data: base64Data, mimeType }); 161 162 return result.uploadBlob; 163 } 164 165 async updateAvatar(dataUrl, profile) { 166 const client = auth.getClient(); 167 168 // Upload the blob (already resized by crop component) 169 const base64Data = dataUrl.split(',')[1]; 170 const blob = await this.uploadBlob(base64Data, 'image/jpeg'); 171 172 if (!blob) { 173 throw new Error('Failed to upload avatar'); 174 } 175 176 // Build input with all profile fields (update requires full object) 177 const input = { 178 displayName: profile.displayName || null, 179 description: profile.description || null, 180 createdAt: profile.createdAt || new Date().toISOString(), 181 avatar: { 182 $type: 'blob', 183 ref: { $link: blob.ref }, 184 mimeType: blob.mimeType, 185 size: blob.size 186 } 187 }; 188 189 // Update profile 190 await client.mutate(` 191 mutation UpdateProfile($rkey: String!, $input: SocialGrainActorProfileInput!) { 192 updateSocialGrainActorProfile(rkey: $rkey, input: $input) { 193 uri 194 } 195 } 196 `, { rkey: 'self', input }); 197 198 // Refresh user data 199 await auth.refreshUser(); 200 } 201 202 async updateProfile(input) { 203 const client = auth.getClient(); 204 205 await client.mutate(` 206 mutation UpdateProfile($rkey: String!, $input: SocialGrainActorProfileInput!) { 207 updateSocialGrainActorProfile(rkey: $rkey, input: $input) { 208 uri 209 } 210 } 211 `, { rkey: 'self', input }); 212 213 await auth.refreshUser(); 214 } 215 216 async createEmptyProfile() { 217 return this.updateProfile({ 218 createdAt: new Date().toISOString() 219 }); 220 } 221 222 async createReport(subjectUri, reasonType, reason = null) { 223 const client = auth.getClient(); 224 const result = await client.mutate(` 225 mutation CreateReport($subjectUri: String!, $reasonType: ReportReasonType!, $reason: String) { 226 createReport(subjectUri: $subjectUri, reasonType: $reasonType, reason: $reason) { 227 id 228 status 229 createdAt 230 } 231 } 232 `, { subjectUri, reasonType, reason }); 233 234 return result.createReport; 235 } 236} 237 238export const mutations = new MutationsService();