Profile: support subscribe to home timeline
All checks were successful
/ depoly (push) Successful in 3m0s

This commit is contained in:
thislight 2024-11-04 18:16:23 +08:00
parent 97a1fb9cf1
commit 6705b754d1
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E

View file

@ -17,6 +17,7 @@ import {
AppBar, AppBar,
Avatar, Avatar,
Button, Button,
Checkbox,
CircularProgress, CircularProgress,
Divider, Divider,
IconButton, IconButton,
@ -67,6 +68,8 @@ const Profile: Component = () => {
const time = createTimeSource(); const time = createTimeSource();
const menuButId = createUniqueId(); const menuButId = createUniqueId();
const recentTootListId = createUniqueId();
const optMenuId = createUniqueId();
const [menuOpen, setMenuOpen] = createSignal(false); const [menuOpen, setMenuOpen] = createSignal(false);
@ -88,17 +91,20 @@ const Profile: Component = () => {
); );
onCleanup(() => obx.disconnect()); onCleanup(() => obx.disconnect());
const [profileErrorUncaught] = createResource( const [profileUncaught] = createResource(
() => [session().client, params.id] as const, () => [session().client, params.id] as const,
async ([client, id]) => { async ([client, id]) => {
return await client.v1.accounts.$select(id).fetch(); return await client.v1.accounts.$select(id).fetch();
}, },
); );
const profile = () => const profile = () => {
catchError(profileErrorUncaught, (err) => { try {
console.error(err); return profileUncaught();
}); } catch (reason) {
console.error(reason);
}
};
const isCurrentSessionProfile = () => { const isCurrentSessionProfile = () => {
return session().account?.inf?.url === profile()?.url; return session().account?.inf?.url === profile()?.url;
@ -127,6 +133,22 @@ const Profile: Component = () => {
}, },
); );
const [relationshipUncaught, { mutate: mutateRelationship }] = createResource(
() => [session(), params.id] as const,
async ([sess, id]) => {
if (!sess.account) return; // No account, no relation
const relations = await session().client.v1.accounts.relationships.fetch({
id: [id],
});
return relations.length > 0 ? relations[0] : undefined;
},
);
const relationship = () =>
catchError(relationshipUncaught, (reason) => {
console.error(reason);
});
const bannerImg = () => profile()?.header; const bannerImg = () => profile()?.header;
const avatarImg = () => profile()?.avatar; const avatarImg = () => profile()?.avatar;
const displayName = () => const displayName = () =>
@ -149,10 +171,27 @@ const Profile: Component = () => {
createRenderEffect(() => (e.innerHTML = sessionDisplayName())); createRenderEffect(() => (e.innerHTML = sessionDisplayName()));
}; };
const toggleSubscribeHome = async () => {
const client = session().client;
if (!session().account) return;
const isSubscribed = relationship()?.following ?? false;
mutateRelationship((x) => Object.assign({ following: !isSubscribed }, x));
subscribeMenuState.onClose();
if (isSubscribed) {
const nrel = await client.v1.accounts.$select(params.id).unfollow();
mutateRelationship(nrel);
} else {
const nrel = await client.v1.accounts.$select(params.id).follow();
mutateRelationship(nrel);
}
};
return ( return (
<Scaffold <Scaffold
topbar={ topbar={
<AppBar <AppBar
role="navigation"
position="static" position="static"
color={scrolledPastBanner() ? "primary" : "transparent"} color={scrolledPastBanner() ? "primary" : "transparent"}
elevation={scrolledPastBanner() ? undefined : 0} elevation={scrolledPastBanner() ? undefined : 0}
@ -186,8 +225,10 @@ const Profile: Component = () => {
<IconButton <IconButton
id={menuButId} id={menuButId}
aria-controls={optMenuId}
color="inherit" color="inherit"
onClick={[setMenuOpen, true]} onClick={[setMenuOpen, true]}
aria-label="Open Options for the Profile"
> >
<MoreVert /> <MoreVert />
</IconButton> </IconButton>
@ -197,11 +238,13 @@ const Profile: Component = () => {
class="Profile" class="Profile"
> >
<Menu <Menu
id={optMenuId}
open={menuOpen()} open={menuOpen()}
onClose={[setMenuOpen, false]} onClose={[setMenuOpen, false]}
anchor={() => anchor={() =>
document.getElementById(menuButId)!.getBoundingClientRect() document.getElementById(menuButId)!.getBoundingClientRect()
} }
aria-label="Options for the Profile"
> >
<Show when={session().account}> <Show when={session().account}>
<MenuItem> <MenuItem>
@ -296,6 +339,7 @@ const Profile: Component = () => {
"margin-top": "margin-top":
"calc(-1 * (var(--scaffold-topbar-height) + var(--safe-area-inset-top)))", "calc(-1 * (var(--scaffold-topbar-height) + var(--safe-area-inset-top)))",
}} }}
role="presentation"
> >
<img <img
ref={(e) => obx.observe(e)} ref={(e) => obx.observe(e)}
@ -306,6 +350,7 @@ const Profile: Component = () => {
height: "100%", height: "100%",
}} }}
crossOrigin="anonymous" crossOrigin="anonymous"
alt={`Banner image for ${profile()?.displayName || "the user"}`}
onLoad={(event) => { onLoad={(event) => {
const ins = new FastAverageColor(); const ins = new FastAverageColor();
const colors = ins.getColor(event.currentTarget); const colors = ins.getColor(event.currentTarget);
@ -319,14 +364,21 @@ const Profile: Component = () => {
</div> </div>
<Menu {...subscribeMenuState}> <Menu {...subscribeMenuState}>
<MenuItem disabled> <MenuItem
onClick={toggleSubscribeHome}
aria-details="Subscribe or Unsubscribe this account on your home timeline"
>
<ListItemAvatar> <ListItemAvatar>
<Avatar src={session().account?.inf?.avatar}></Avatar> <Avatar src={session().account?.inf?.avatar}></Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText> <ListItemText
secondary={relationship()?.following ? "Subscribed" : undefined}
>
<span ref={useSessionDisplayName}></span> <span ref={useSessionDisplayName}></span>
<span>'s Home</span> <span>'s Home</span>
</ListItemText> </ListItemText>
<Checkbox checked={relationship()?.following ?? false} />
</MenuItem> </MenuItem>
</Menu> </Menu>
@ -337,9 +389,10 @@ const Profile: Component = () => {
color: bannerSampledColors()?.text, color: bannerSampledColors()?.text,
}} }}
> >
<div class="acct-grp"> <section class="acct-grp">
<Avatar <Avatar
src={avatarImg()} src={avatarImg()}
alt={`${profile()?.displayName || "the user"}'s avatar`}
sx={{ sx={{
marginTop: "calc(-16px - 72px / 2)", marginTop: "calc(-16px - 72px / 2)",
width: "72px", width: "72px",
@ -351,12 +404,19 @@ const Profile: Component = () => {
ref={(e) => ref={(e) =>
createRenderEffect(() => (e.innerHTML = displayName())) createRenderEffect(() => (e.innerHTML = displayName()))
} }
aria-label="Display name"
></span> ></span>
<span>{fullUsername()}</span> <span aria-label="Complete username">{fullUsername()}</span>
</div> </div>
<div> <div>
<Switch> <Switch>
<Match when={!session().account || profileErrorUncaught.loading}> <Match
when={
!session().account ||
profileUncaught.loading ||
profileUncaught.error
}
>
{<></>} {<></>}
</Match> </Match>
<Match when={isCurrentSessionProfile()}> <Match when={isCurrentSessionProfile()}>
@ -374,20 +434,24 @@ const Profile: Component = () => {
); );
}} }}
> >
Subscribe {relationship()?.following ? "Subscribed" : "Subscribe"}
</Button> </Button>
</Match> </Match>
</Switch> </Switch>
</div> </div>
</div> </section>
<div <section
class="description" class="description"
aria-label={`${profile()?.displayName || "the user"}'s description`}
ref={(e) => ref={(e) =>
createRenderEffect(() => (e.innerHTML = description() || "")) createRenderEffect(() => (e.innerHTML = description() || ""))
} }
></div> ></section>
<table class="acct-fields"> <table
class="acct-fields"
aria-label={`${profile()?.displayName || "the user"}'s fields`}
>
<tbody> <tbody>
<For each={profile()?.fields ?? []}> <For each={profile()?.fields ?? []}>
{(item, index) => { {(item, index) => {
@ -436,6 +500,7 @@ const Profile: Component = () => {
<Divider /> <Divider />
</Show> </Show>
<TootList <TootList
id={recentTootListId}
threads={recentToots.list} threads={recentToots.list}
onUnknownThread={recentToots.getPath} onUnknownThread={recentToots.getPath}
onChangeToot={recentToots.set} onChangeToot={recentToots.set}
@ -451,6 +516,7 @@ const Profile: Component = () => {
> >
<IconButton <IconButton
aria-label="Load More" aria-label="Load More"
aria-controls={recentTootListId}
size="large" size="large"
color="primary" color="primary"
onClick={[refetchRecentToots, "prev"]} onClick={[refetchRecentToots, "prev"]}