useTimeline: use store to provide partial editing
This commit is contained in:
		
							parent
							
								
									9486ac34ea
								
							
						
					
					
						commit
						fc8d489977
					
				
					 3 changed files with 65 additions and 30 deletions
				
			
		|  | @ -1,5 +1,6 @@ | ||||||
| import { type mastodon } from "masto"; | import { type mastodon } from "masto"; | ||||||
| import { Accessor, createResource, createSignal } from "solid-js"; | import { Accessor, createEffect, createResource, createSignal } from "solid-js"; | ||||||
|  | import { createStore } from "solid-js/store"; | ||||||
| 
 | 
 | ||||||
| type TimelineFetchTips = { | type TimelineFetchTips = { | ||||||
|   direction?: "new" | "old"; |   direction?: "new" | "old"; | ||||||
|  | @ -17,8 +18,8 @@ export function useTimeline(timeline: Accessor<Timeline>) { | ||||||
|   let maxId: string | undefined; |   let maxId: string | undefined; | ||||||
|   let otl: Timeline | undefined; |   let otl: Timeline | undefined; | ||||||
|   const idSet = new Set<string>(); |   const idSet = new Set<string>(); | ||||||
|   return createResource< |   const [snapshot, { refetch }] = createResource< | ||||||
|     mastodon.v1.Status[], |     { records: mastodon.v1.Status[]; direction: "old" | "new" }, | ||||||
|     [Timeline], |     [Timeline], | ||||||
|     TimelineFetchTips | undefined |     TimelineFetchTips | undefined | ||||||
|   >( |   >( | ||||||
|  | @ -28,7 +29,6 @@ export function useTimeline(timeline: Accessor<Timeline>) { | ||||||
|         minId = undefined; |         minId = undefined; | ||||||
|         maxId = undefined; |         maxId = undefined; | ||||||
|         idSet.clear(); |         idSet.clear(); | ||||||
|         info.value = []; |  | ||||||
|         otl = tl; |         otl = tl; | ||||||
|       } |       } | ||||||
|       const direction = |       const direction = | ||||||
|  | @ -44,7 +44,6 @@ export function useTimeline(timeline: Accessor<Timeline>) { | ||||||
|               minId: maxId, |               minId: maxId, | ||||||
|             }, |             }, | ||||||
|       ); |       ); | ||||||
|       const old = info.value || []; |  | ||||||
|       const diff = pager.filter((x) => !idSet.has(x.id)); |       const diff = pager.filter((x) => !idSet.has(x.id)); | ||||||
|       for (const v of diff.map((x) => x.id)) { |       for (const v of diff.map((x) => x.id)) { | ||||||
|         idSet.add(v); |         idSet.add(v); | ||||||
|  | @ -54,20 +53,39 @@ export function useTimeline(timeline: Accessor<Timeline>) { | ||||||
|         if (!maxId && pager.length > 0) { |         if (!maxId && pager.length > 0) { | ||||||
|           maxId = pager[0].id; |           maxId = pager[0].id; | ||||||
|         } |         } | ||||||
|         return [...old, ...diff]; |         return { | ||||||
|  |           direction: "old" as const, | ||||||
|  |           records: diff, | ||||||
|  |         }; | ||||||
|       } else { |       } else { | ||||||
|         maxId = pager.length > 0 ? pager[0].id : undefined; |         maxId = pager.length > 0 ? pager[0].id : undefined; | ||||||
|         if (!minId && pager.length > 0) { |         if (!minId && pager.length > 0) { | ||||||
|           minId = pager[pager.length - 1]?.id; |           minId = pager[pager.length - 1]?.id; | ||||||
|         } |         } | ||||||
|         return [...diff, ...old]; |         return { direction: "new" as const, records: diff }; | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     { |  | ||||||
|       initialValue: [], |  | ||||||
|       storage(init) { |  | ||||||
|         return createSignal(init, { equals: false }); |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|   ); |   ); | ||||||
|  | 
 | ||||||
|  |   const [store, setStore] = createStore([] as mastodon.v1.Status[]); | ||||||
|  | 
 | ||||||
|  |   createEffect(() => { | ||||||
|  |     const shot = snapshot(); | ||||||
|  |     if (!shot) return; | ||||||
|  |     const { direction, records } = shot; | ||||||
|  |     if (direction == "new") { | ||||||
|  |       setStore((x) => [...records, ...x]); | ||||||
|  |     } else if (direction == "old") { | ||||||
|  |       setStore((x) => [...x, ...records]); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return [ | ||||||
|  |     store, | ||||||
|  |     snapshot, | ||||||
|  |     { | ||||||
|  |       refetch, | ||||||
|  |       mutate: setStore, | ||||||
|  |     }, | ||||||
|  |   ] as const; | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								src/platform/hardware.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/platform/hardware.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | export function vibrate(pattern: number | number[]) { | ||||||
|  |   if (typeof navigator.vibrate !== "undefined") { | ||||||
|  |     return navigator.vibrate(pattern); | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  | @ -37,21 +37,25 @@ import { makeEventListener } from "@solid-primitives/event-listener"; | ||||||
| import BottomSheet from "../material/BottomSheet"; | import BottomSheet from "../material/BottomSheet"; | ||||||
| import { $settings } from "../settings/stores"; | import { $settings } from "../settings/stores"; | ||||||
| import { useStore } from "@nanostores/solid"; | import { useStore } from "@nanostores/solid"; | ||||||
|  | import { vibrate } from "../platform/hardware"; | ||||||
| 
 | 
 | ||||||
| const TimelinePanel: Component<{ | const TimelinePanel: Component<{ | ||||||
|   client: mastodon.rest.Client; |   client: mastodon.rest.Client; | ||||||
|   name: "home" | "public" | "trends"; |   name: "home" | "public" | "trends"; | ||||||
|   prefetch?: boolean; |   prefetch?: boolean; | ||||||
| }> = (props) => { | }> = (props) => { | ||||||
|   const [timeline, { refetch: refetchTimeline, mutate: mutateTimeline }] = |   const [ | ||||||
|     useTimeline(() => |     timeline, | ||||||
|       props.name !== "trends" |     snapshot, | ||||||
|         ? props.client.v1.timelines[props.name] |     { refetch: refetchTimeline, mutate: mutateTimeline }, | ||||||
|         : props.client.v1.trends.statuses, |   ] = useTimeline(() => | ||||||
|     ); |     props.name !== "trends" | ||||||
|  |       ? props.client.v1.timelines[props.name] | ||||||
|  |       : props.client.v1.trends.statuses, | ||||||
|  |   ); | ||||||
| 
 | 
 | ||||||
|   const tlEndObserver = new IntersectionObserver(() => { |   const tlEndObserver = new IntersectionObserver(() => { | ||||||
|     if (untrack(() => props.prefetch) && !timeline.loading) |     if (untrack(() => props.prefetch) && !snapshot.loading) | ||||||
|       refetchTimeline({ direction: "old" }); |       refetchTimeline({ direction: "old" }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  | @ -76,12 +80,19 @@ const TimelinePanel: Component<{ | ||||||
|     client: mastodon.rest.Client, |     client: mastodon.rest.Client, | ||||||
|     status: mastodon.v1.Status, |     status: mastodon.v1.Status, | ||||||
|   ) => { |   ) => { | ||||||
|     const reblogged = false; |     const reblogged = status.reblog | ||||||
|     mutateTimeline((o) => { |       ? status.reblog.reblogged | ||||||
|       Object.assign(o[index].reblog ?? o[index], { |       : status.reblogged; | ||||||
|         reblogged: !reblogged, |     vibrate(50); | ||||||
|       }); |     mutateTimeline(index, (x) => { | ||||||
|       return o; |       if (x.reblog) { | ||||||
|  |         x.reblog = { ...x.reblog, reblogged: !reblogged }; | ||||||
|  |         return Object.assign({}, x); | ||||||
|  |       } else { | ||||||
|  |         return Object.assign({}, x, { | ||||||
|  |           reblogged: !reblogged, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|     }); |     }); | ||||||
|     const result = reblogged |     const result = reblogged | ||||||
|       ? await client.v1.statuses.$select(status.id).unreblog() |       ? await client.v1.statuses.$select(status.id).unreblog() | ||||||
|  | @ -98,7 +109,7 @@ const TimelinePanel: Component<{ | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <div> |       <div> | ||||||
|         <For each={timeline()}> |         <For each={timeline}> | ||||||
|           {(item, index) => { |           {(item, index) => { | ||||||
|             return ( |             return ( | ||||||
|               <TootThread |               <TootThread | ||||||
|  | @ -113,12 +124,12 @@ const TimelinePanel: Component<{ | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div ref={(e) => tlEndObserver.observe(e)}></div> |       <div ref={(e) => tlEndObserver.observe(e)}></div> | ||||||
|       <Show when={timeline.loading}> |       <Show when={snapshot.loading}> | ||||||
|         <div class="loading-line" style={{ width: "100%" }}> |         <div class="loading-line" style={{ width: "100%" }}> | ||||||
|           <LinearProgress /> |           <LinearProgress /> | ||||||
|         </div> |         </div> | ||||||
|       </Show> |       </Show> | ||||||
|       <Show when={timeline.error}> |       <Show when={snapshot.error}> | ||||||
|         <div |         <div | ||||||
|           style={{ |           style={{ | ||||||
|             display: "flex", |             display: "flex", | ||||||
|  | @ -132,7 +143,7 @@ const TimelinePanel: Component<{ | ||||||
|           </Button> |           </Button> | ||||||
|         </div> |         </div> | ||||||
|       </Show> |       </Show> | ||||||
|       <Show when={!props.prefetch && !timeline.loading}> |       <Show when={!props.prefetch && !snapshot.loading}> | ||||||
|         <div |         <div | ||||||
|           style={{ |           style={{ | ||||||
|             display: "flex", |             display: "flex", | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue