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 { Accessor, createResource, createSignal } from "solid-js";
 | 
			
		||||
import { Accessor, createEffect, createResource, createSignal } from "solid-js";
 | 
			
		||||
import { createStore } from "solid-js/store";
 | 
			
		||||
 | 
			
		||||
type TimelineFetchTips = {
 | 
			
		||||
  direction?: "new" | "old";
 | 
			
		||||
| 
						 | 
				
			
			@ -17,8 +18,8 @@ export function useTimeline(timeline: Accessor<Timeline>) {
 | 
			
		|||
  let maxId: string | undefined;
 | 
			
		||||
  let otl: Timeline | undefined;
 | 
			
		||||
  const idSet = new Set<string>();
 | 
			
		||||
  return createResource<
 | 
			
		||||
    mastodon.v1.Status[],
 | 
			
		||||
  const [snapshot, { refetch }] = createResource<
 | 
			
		||||
    { records: mastodon.v1.Status[]; direction: "old" | "new" },
 | 
			
		||||
    [Timeline],
 | 
			
		||||
    TimelineFetchTips | undefined
 | 
			
		||||
  >(
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +29,6 @@ export function useTimeline(timeline: Accessor<Timeline>) {
 | 
			
		|||
        minId = undefined;
 | 
			
		||||
        maxId = undefined;
 | 
			
		||||
        idSet.clear();
 | 
			
		||||
        info.value = [];
 | 
			
		||||
        otl = tl;
 | 
			
		||||
      }
 | 
			
		||||
      const direction =
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +44,6 @@ export function useTimeline(timeline: Accessor<Timeline>) {
 | 
			
		|||
              minId: maxId,
 | 
			
		||||
            },
 | 
			
		||||
      );
 | 
			
		||||
      const old = info.value || [];
 | 
			
		||||
      const diff = pager.filter((x) => !idSet.has(x.id));
 | 
			
		||||
      for (const v of diff.map((x) => x.id)) {
 | 
			
		||||
        idSet.add(v);
 | 
			
		||||
| 
						 | 
				
			
			@ -54,20 +53,39 @@ export function useTimeline(timeline: Accessor<Timeline>) {
 | 
			
		|||
        if (!maxId && pager.length > 0) {
 | 
			
		||||
          maxId = pager[0].id;
 | 
			
		||||
        }
 | 
			
		||||
        return [...old, ...diff];
 | 
			
		||||
        return {
 | 
			
		||||
          direction: "old" as const,
 | 
			
		||||
          records: diff,
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        maxId = pager.length > 0 ? pager[0].id : undefined;
 | 
			
		||||
        if (!minId && pager.length > 0) {
 | 
			
		||||
          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 { $settings } from "../settings/stores";
 | 
			
		||||
import { useStore } from "@nanostores/solid";
 | 
			
		||||
import { vibrate } from "../platform/hardware";
 | 
			
		||||
 | 
			
		||||
const TimelinePanel: Component<{
 | 
			
		||||
  client: mastodon.rest.Client;
 | 
			
		||||
  name: "home" | "public" | "trends";
 | 
			
		||||
  prefetch?: boolean;
 | 
			
		||||
}> = (props) => {
 | 
			
		||||
  const [timeline, { refetch: refetchTimeline, mutate: mutateTimeline }] =
 | 
			
		||||
    useTimeline(() =>
 | 
			
		||||
      props.name !== "trends"
 | 
			
		||||
        ? props.client.v1.timelines[props.name]
 | 
			
		||||
        : props.client.v1.trends.statuses,
 | 
			
		||||
    );
 | 
			
		||||
  const [
 | 
			
		||||
    timeline,
 | 
			
		||||
    snapshot,
 | 
			
		||||
    { refetch: refetchTimeline, mutate: mutateTimeline },
 | 
			
		||||
  ] = useTimeline(() =>
 | 
			
		||||
    props.name !== "trends"
 | 
			
		||||
      ? props.client.v1.timelines[props.name]
 | 
			
		||||
      : props.client.v1.trends.statuses,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const tlEndObserver = new IntersectionObserver(() => {
 | 
			
		||||
    if (untrack(() => props.prefetch) && !timeline.loading)
 | 
			
		||||
    if (untrack(() => props.prefetch) && !snapshot.loading)
 | 
			
		||||
      refetchTimeline({ direction: "old" });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -76,12 +80,19 @@ const TimelinePanel: Component<{
 | 
			
		|||
    client: mastodon.rest.Client,
 | 
			
		||||
    status: mastodon.v1.Status,
 | 
			
		||||
  ) => {
 | 
			
		||||
    const reblogged = false;
 | 
			
		||||
    mutateTimeline((o) => {
 | 
			
		||||
      Object.assign(o[index].reblog ?? o[index], {
 | 
			
		||||
        reblogged: !reblogged,
 | 
			
		||||
      });
 | 
			
		||||
      return o;
 | 
			
		||||
    const reblogged = status.reblog
 | 
			
		||||
      ? status.reblog.reblogged
 | 
			
		||||
      : status.reblogged;
 | 
			
		||||
    vibrate(50);
 | 
			
		||||
    mutateTimeline(index, (x) => {
 | 
			
		||||
      if (x.reblog) {
 | 
			
		||||
        x.reblog = { ...x.reblog, reblogged: !reblogged };
 | 
			
		||||
        return Object.assign({}, x);
 | 
			
		||||
      } else {
 | 
			
		||||
        return Object.assign({}, x, {
 | 
			
		||||
          reblogged: !reblogged,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    const result = reblogged
 | 
			
		||||
      ? await client.v1.statuses.$select(status.id).unreblog()
 | 
			
		||||
| 
						 | 
				
			
			@ -98,7 +109,7 @@ const TimelinePanel: Component<{
 | 
			
		|||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div>
 | 
			
		||||
        <For each={timeline()}>
 | 
			
		||||
        <For each={timeline}>
 | 
			
		||||
          {(item, index) => {
 | 
			
		||||
            return (
 | 
			
		||||
              <TootThread
 | 
			
		||||
| 
						 | 
				
			
			@ -113,12 +124,12 @@ const TimelinePanel: Component<{
 | 
			
		|||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div ref={(e) => tlEndObserver.observe(e)}></div>
 | 
			
		||||
      <Show when={timeline.loading}>
 | 
			
		||||
      <Show when={snapshot.loading}>
 | 
			
		||||
        <div class="loading-line" style={{ width: "100%" }}>
 | 
			
		||||
          <LinearProgress />
 | 
			
		||||
        </div>
 | 
			
		||||
      </Show>
 | 
			
		||||
      <Show when={timeline.error}>
 | 
			
		||||
      <Show when={snapshot.error}>
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
            display: "flex",
 | 
			
		||||
| 
						 | 
				
			
			@ -132,7 +143,7 @@ const TimelinePanel: Component<{
 | 
			
		|||
          </Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </Show>
 | 
			
		||||
      <Show when={!props.prefetch && !timeline.loading}>
 | 
			
		||||
      <Show when={!props.prefetch && !snapshot.loading}>
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
            display: "flex",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue