move TimelinePanel to separated files

- added TrendTimelinePanel for trending tab
This commit is contained in:
thislight 2024-10-10 16:24:27 +08:00
parent ab29e5fd70
commit 971fb6a8e7
4 changed files with 492 additions and 200 deletions

View file

@ -0,0 +1,203 @@
import {
Component,
For,
onCleanup,
createSignal,
Show,
untrack,
Match,
Switch as JsSwitch,
ErrorBoundary,
} from "solid-js";
import { type mastodon } from "masto";
import {
Button,
LinearProgress,
} from "@suid/material";
import TootThread from "./TootThread.js";
import { useTimeline } from "../masto/timelines";
import { vibrate } from "../platform/hardware";
import PullDownToRefresh from "./PullDownToRefresh";
import TootComposer from "./TootComposer";
const TimelinePanel: Component<{
client: mastodon.rest.Client;
name: "home" | "public" | "trends";
prefetch?: boolean;
fullRefetch?: number;
openFullScreenToot: (
toot: mastodon.v1.Status,
srcElement?: HTMLElement,
reply?: boolean,
) => void;
}> = (props) => {
const [scrollLinked, setScrollLinked] = createSignal<HTMLElement>();
const [
timeline,
snapshot,
{ refetch: refetchTimeline, mutate: mutateTimeline },
] = useTimeline(
() =>
props.name !== "trends"
? props.client.v1.timelines[props.name]
: props.client.v1.trends.statuses,
{ fullRefresh: props.fullRefetch },
);
const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
const [typing, setTyping] = createSignal(false);
const tlEndObserver = new IntersectionObserver(() => {
if (untrack(() => props.prefetch) && !snapshot.loading)
refetchTimeline({ direction: "old" });
});
onCleanup(() => tlEndObserver.disconnect());
const onBookmark = async (
index: number,
client: mastodon.rest.Client,
status: mastodon.v1.Status,
) => {
const result = await (status.bookmarked
? client.v1.statuses.$select(status.id).unbookmark()
: client.v1.statuses.$select(status.id).bookmark());
mutateTimeline((o) => {
o[index] = result;
return o;
});
};
const onBoost = async (
index: number,
client: mastodon.rest.Client,
status: mastodon.v1.Status,
) => {
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()
: (await client.v1.statuses.$select(status.id).reblog()).reblog!;
mutateTimeline((o) => {
Object.assign(o[index].reblog ?? o[index], {
reblogged: result.reblogged,
reblogsCount: result.reblogsCount,
});
return o;
});
};
return (
<ErrorBoundary
fallback={(err, reset) => {
return <p>Oops: {String(err)}</p>;
}}
>
<PullDownToRefresh
linkedElement={scrollLinked()}
loading={snapshot.loading}
onRefresh={() => refetchTimeline({ direction: "new" })}
/>
<div
ref={(e) =>
setTimeout(() => {
setScrollLinked(e.parentElement!);
}, 0)
}
>
<Show when={props.name === "home"}>
<TootComposer
style={{
"--scaffold-topbar-height": "0px",
}}
isTyping={typing()}
onTypingChange={setTyping}
client={props.client}
onSent={() => refetchTimeline({ direction: "new" })}
/>
</Show>
<For each={timeline}>
{(item, index) => {
let element: HTMLElement | undefined;
return (
<TootThread
ref={element}
status={item}
onBoost={(...args) => onBoost(index(), ...args)}
onBookmark={(...args) => onBookmark(index(), ...args)}
onReply={(client, status) =>
props.openFullScreenToot(status, element, true)
}
client={props.client}
expanded={item.id === expandedThreadId() ? 1 : 0}
onExpandChange={(x) => {
setTyping(false)
if (item.id !== expandedThreadId()) {
setExpandedThreadId((x) => (x ? undefined : item.id));
} else if (x === 2) {
props.openFullScreenToot(item, element);
}
}}
/>
);
}}
</For>
</div>
<div ref={(e) => tlEndObserver.observe(e)}></div>
<Show when={snapshot.loading}>
<div
class="loading-line"
style={{
width: "100%",
}}
>
<LinearProgress />
</div>
</Show>
<div
style={{
display: "flex",
padding: "20px 0 calc(20px + var(--safe-area-inset-bottom, 0px))",
"align-items": "center",
"justify-content": "center",
}}
>
<JsSwitch>
<Match when={snapshot.error}>
<Button
variant="contained"
onClick={[refetchTimeline, "old"]}
disabled={snapshot.loading}
>
Retry
</Button>
</Match>
<Match when={typeof props.fullRefetch === "undefined"}>
<Button
variant="contained"
onClick={[refetchTimeline, "old"]}
disabled={snapshot.loading}
>
Load More
</Button>
</Match>
</JsSwitch>
</div>
</ErrorBoundary>
);
};
export default TimelinePanel