useTimeline: use store to provide partial editing
This commit is contained in:
parent
cde30f8e29
commit
2d75ecfbe3
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…
Reference in a new issue