useTimeline: use store to provide partial editing

This commit is contained in:
thislight 2024-08-06 20:27:30 +08:00
parent cde30f8e29
commit 2d75ecfbe3
3 changed files with 65 additions and 30 deletions

View file

@ -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
View file

@ -0,0 +1,6 @@
export function vibrate(pattern: number | number[]) {
if (typeof navigator.vibrate !== "undefined") {
return navigator.vibrate(pattern);
}
return false;
}

View file

@ -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",