Profile: support subscribe to home timeline
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				/ depoly (push) Successful in 3m0s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	/ depoly (push) Successful in 3m0s
				
			This commit is contained in:
		
							parent
							
								
									97a1fb9cf1
								
							
						
					
					
						commit
						6705b754d1
					
				
					 1 changed files with 81 additions and 15 deletions
				
			
		|  | @ -17,6 +17,7 @@ import { | |||
|   AppBar, | ||||
|   Avatar, | ||||
|   Button, | ||||
|   Checkbox, | ||||
|   CircularProgress, | ||||
|   Divider, | ||||
|   IconButton, | ||||
|  | @ -67,6 +68,8 @@ const Profile: Component = () => { | |||
|   const time = createTimeSource(); | ||||
| 
 | ||||
|   const menuButId = createUniqueId(); | ||||
|   const recentTootListId = createUniqueId(); | ||||
|   const optMenuId = createUniqueId(); | ||||
| 
 | ||||
|   const [menuOpen, setMenuOpen] = createSignal(false); | ||||
| 
 | ||||
|  | @ -88,17 +91,20 @@ const Profile: Component = () => { | |||
|   ); | ||||
|   onCleanup(() => obx.disconnect()); | ||||
| 
 | ||||
|   const [profileErrorUncaught] = createResource( | ||||
|   const [profileUncaught] = createResource( | ||||
|     () => [session().client, params.id] as const, | ||||
|     async ([client, id]) => { | ||||
|       return await client.v1.accounts.$select(id).fetch(); | ||||
|     }, | ||||
|   ); | ||||
| 
 | ||||
|   const profile = () => | ||||
|     catchError(profileErrorUncaught, (err) => { | ||||
|       console.error(err); | ||||
|     }); | ||||
|   const profile = () => { | ||||
|     try { | ||||
|       return profileUncaught(); | ||||
|     } catch (reason) { | ||||
|       console.error(reason); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const isCurrentSessionProfile = () => { | ||||
|     return session().account?.inf?.url === profile()?.url; | ||||
|  | @ -127,6 +133,22 @@ const Profile: Component = () => { | |||
|     }, | ||||
|   ); | ||||
| 
 | ||||
|   const [relationshipUncaught, { mutate: mutateRelationship }] = createResource( | ||||
|     () => [session(), params.id] as const, | ||||
|     async ([sess, id]) => { | ||||
|       if (!sess.account) return; // No account, no relation
 | ||||
|       const relations = await session().client.v1.accounts.relationships.fetch({ | ||||
|         id: [id], | ||||
|       }); | ||||
|       return relations.length > 0 ? relations[0] : undefined; | ||||
|     }, | ||||
|   ); | ||||
| 
 | ||||
|   const relationship = () => | ||||
|     catchError(relationshipUncaught, (reason) => { | ||||
|       console.error(reason); | ||||
|     }); | ||||
| 
 | ||||
|   const bannerImg = () => profile()?.header; | ||||
|   const avatarImg = () => profile()?.avatar; | ||||
|   const displayName = () => | ||||
|  | @ -149,10 +171,27 @@ const Profile: Component = () => { | |||
|     createRenderEffect(() => (e.innerHTML = sessionDisplayName())); | ||||
|   }; | ||||
| 
 | ||||
|   const toggleSubscribeHome = async () => { | ||||
|     const client = session().client; | ||||
|     if (!session().account) return; | ||||
|     const isSubscribed = relationship()?.following ?? false; | ||||
|     mutateRelationship((x) => Object.assign({ following: !isSubscribed }, x)); | ||||
|     subscribeMenuState.onClose(); | ||||
| 
 | ||||
|     if (isSubscribed) { | ||||
|       const nrel = await client.v1.accounts.$select(params.id).unfollow(); | ||||
|       mutateRelationship(nrel); | ||||
|     } else { | ||||
|       const nrel = await client.v1.accounts.$select(params.id).follow(); | ||||
|       mutateRelationship(nrel); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <Scaffold | ||||
|       topbar={ | ||||
|         <AppBar | ||||
|           role="navigation" | ||||
|           position="static" | ||||
|           color={scrolledPastBanner() ? "primary" : "transparent"} | ||||
|           elevation={scrolledPastBanner() ? undefined : 0} | ||||
|  | @ -186,8 +225,10 @@ const Profile: Component = () => { | |||
| 
 | ||||
|             <IconButton | ||||
|               id={menuButId} | ||||
|               aria-controls={optMenuId} | ||||
|               color="inherit" | ||||
|               onClick={[setMenuOpen, true]} | ||||
|               aria-label="Open Options for the Profile" | ||||
|             > | ||||
|               <MoreVert /> | ||||
|             </IconButton> | ||||
|  | @ -197,11 +238,13 @@ const Profile: Component = () => { | |||
|       class="Profile" | ||||
|     > | ||||
|       <Menu | ||||
|         id={optMenuId} | ||||
|         open={menuOpen()} | ||||
|         onClose={[setMenuOpen, false]} | ||||
|         anchor={() => | ||||
|           document.getElementById(menuButId)!.getBoundingClientRect() | ||||
|         } | ||||
|         aria-label="Options for the Profile" | ||||
|       > | ||||
|         <Show when={session().account}> | ||||
|           <MenuItem> | ||||
|  | @ -296,6 +339,7 @@ const Profile: Component = () => { | |||
|           "margin-top": | ||||
|             "calc(-1 * (var(--scaffold-topbar-height) + var(--safe-area-inset-top)))", | ||||
|         }} | ||||
|         role="presentation" | ||||
|       > | ||||
|         <img | ||||
|           ref={(e) => obx.observe(e)} | ||||
|  | @ -306,6 +350,7 @@ const Profile: Component = () => { | |||
|             height: "100%", | ||||
|           }} | ||||
|           crossOrigin="anonymous" | ||||
|           alt={`Banner image for ${profile()?.displayName || "the user"}`} | ||||
|           onLoad={(event) => { | ||||
|             const ins = new FastAverageColor(); | ||||
|             const colors = ins.getColor(event.currentTarget); | ||||
|  | @ -319,14 +364,21 @@ const Profile: Component = () => { | |||
|       </div> | ||||
| 
 | ||||
|       <Menu {...subscribeMenuState}> | ||||
|         <MenuItem disabled> | ||||
|         <MenuItem | ||||
|           onClick={toggleSubscribeHome} | ||||
|           aria-details="Subscribe or Unsubscribe this account on your home timeline" | ||||
|         > | ||||
|           <ListItemAvatar> | ||||
|             <Avatar src={session().account?.inf?.avatar}></Avatar> | ||||
|           </ListItemAvatar> | ||||
|           <ListItemText> | ||||
|           <ListItemText | ||||
|             secondary={relationship()?.following ? "Subscribed" : undefined} | ||||
|           > | ||||
|             <span ref={useSessionDisplayName}></span> | ||||
|             <span>'s Home</span> | ||||
|           </ListItemText> | ||||
| 
 | ||||
|           <Checkbox checked={relationship()?.following ?? false} /> | ||||
|         </MenuItem> | ||||
|       </Menu> | ||||
| 
 | ||||
|  | @ -337,9 +389,10 @@ const Profile: Component = () => { | |||
|           color: bannerSampledColors()?.text, | ||||
|         }} | ||||
|       > | ||||
|         <div class="acct-grp"> | ||||
|         <section class="acct-grp"> | ||||
|           <Avatar | ||||
|             src={avatarImg()} | ||||
|             alt={`${profile()?.displayName || "the user"}'s avatar`} | ||||
|             sx={{ | ||||
|               marginTop: "calc(-16px - 72px / 2)", | ||||
|               width: "72px", | ||||
|  | @ -351,12 +404,19 @@ const Profile: Component = () => { | |||
|               ref={(e) => | ||||
|                 createRenderEffect(() => (e.innerHTML = displayName())) | ||||
|               } | ||||
|               aria-label="Display name" | ||||
|             ></span> | ||||
|             <span>{fullUsername()}</span> | ||||
|             <span aria-label="Complete username">{fullUsername()}</span> | ||||
|           </div> | ||||
|           <div> | ||||
|             <Switch> | ||||
|               <Match when={!session().account || profileErrorUncaught.loading}> | ||||
|               <Match | ||||
|                 when={ | ||||
|                   !session().account || | ||||
|                   profileUncaught.loading || | ||||
|                   profileUncaught.error | ||||
|                 } | ||||
|               > | ||||
|                 {<></>} | ||||
|               </Match> | ||||
|               <Match when={isCurrentSessionProfile()}> | ||||
|  | @ -374,20 +434,24 @@ const Profile: Component = () => { | |||
|                     ); | ||||
|                   }} | ||||
|                 > | ||||
|                   Subscribe | ||||
|                   {relationship()?.following ? "Subscribed" : "Subscribe"} | ||||
|                 </Button> | ||||
|               </Match> | ||||
|             </Switch> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div | ||||
|         </section> | ||||
|         <section | ||||
|           class="description" | ||||
|           aria-label={`${profile()?.displayName || "the user"}'s description`} | ||||
|           ref={(e) => | ||||
|             createRenderEffect(() => (e.innerHTML = description() || "")) | ||||
|           } | ||||
|         ></div> | ||||
|         ></section> | ||||
| 
 | ||||
|         <table class="acct-fields"> | ||||
|         <table | ||||
|           class="acct-fields" | ||||
|           aria-label={`${profile()?.displayName || "the user"}'s fields`} | ||||
|         > | ||||
|           <tbody> | ||||
|             <For each={profile()?.fields ?? []}> | ||||
|               {(item, index) => { | ||||
|  | @ -436,6 +500,7 @@ const Profile: Component = () => { | |||
|           <Divider /> | ||||
|         </Show> | ||||
|         <TootList | ||||
|           id={recentTootListId} | ||||
|           threads={recentToots.list} | ||||
|           onUnknownThread={recentToots.getPath} | ||||
|           onChangeToot={recentToots.set} | ||||
|  | @ -451,6 +516,7 @@ const Profile: Component = () => { | |||
|         > | ||||
|           <IconButton | ||||
|             aria-label="Load More" | ||||
|             aria-controls={recentTootListId} | ||||
|             size="large" | ||||
|             color="primary" | ||||
|             onClick={[refetchRecentToots, "prev"]} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue