Profile: support wide page

This commit is contained in:
thislight 2024-11-23 16:10:48 +08:00
parent bb3ba32dc5
commit 66d0bc8d84
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E
3 changed files with 347 additions and 302 deletions

View file

@ -1,9 +1,10 @@
.StackedPage {
contain: layout style;
container: StackedPage / size;
display: contents;
max-width: 100vw;
max-width: 100dvw;
contain: strict;
container: StackedPage / inline-size;
width: 100vw;
width: 100dvw;
height: 100vh;
height: 100dvh;
}
dialog.StackedPage {

View file

@ -115,6 +115,44 @@
}
}
@container StackedPage (inline-size >=960px) {
.Profile {
display: grid;
grid-template-columns: auto 560px;
grid-template-rows: min-content 1fr;
height: 100cqh;
>.topbar {
grid-column: 1 / 3;
grid-row: 1 /2;
.MuiToolbar-root {
padding-right: calc(560px + 24px);
}
}
> .details {
height: 100%;
display: flex;
flex-flow: column nowrap;
> .intro {
flex-grow: 1;
}
}
>.recent-toots {
overflow-y: auto;
margin-top: calc(-1 * var(--scaffold-topbar-height));
z-index: calc(var(--tutu-zidx-nav, 1) + 1);
>.toot-list-toolbar {
top: 0;
}
}
}
}
.Profile__page-title {
flex-grow: 1;
white-space: nowrap;

View file

@ -237,315 +237,321 @@ const Profile: Component = () => {
}
class="Profile"
>
<Menu
id={optMenuId}
open={menuOpen()}
onClose={[setMenuOpen, false]}
anchor={() =>
document.getElementById(menuButId)!.getBoundingClientRect()
}
aria-label="Options for the Profile"
>
<Show when={session().account}>
<MenuItem>
<ListItemAvatar>
<Avatar src={session().account?.inf?.avatar} />
</ListItemAvatar>
<ListItemText secondary={"Default account"}>
<span ref={useSessionDisplayName}></span>
</ListItemText>
{/* <ArrowRight /> // for future */}
</MenuItem>
</Show>
<Show when={session().account && profile()}>
<Show
when={isCurrentSessionProfile()}
fallback={
<MenuItem
onClick={(event) => {
const { left, right, top } =
event.currentTarget.getBoundingClientRect();
openSubscribeMenu({
left,
right,
top,
e: 1,
});
}}
>
<ListItemIcon>
<PlaylistAdd />
</ListItemIcon>
<ListItemText>Subscribe...</ListItemText>
</MenuItem>
}
>
<MenuItem disabled>
<ListItemIcon>
<Edit />
</ListItemIcon>
<ListItemText>Edit...</ListItemText>
<div class="details">
<Menu
id={optMenuId}
open={menuOpen()}
onClose={[setMenuOpen, false]}
anchor={() =>
document.getElementById(menuButId)!.getBoundingClientRect()
}
aria-label="Options for the Profile"
>
<Show when={session().account}>
<MenuItem>
<ListItemAvatar>
<Avatar src={session().account?.inf?.avatar} />
</ListItemAvatar>
<ListItemText secondary={"Default account"}>
<span ref={useSessionDisplayName}></span>
</ListItemText>
{/* <ArrowRight /> // for future */}
</MenuItem>
</Show>
<Divider />
</Show>
<MenuItem disabled>
<ListItemIcon>
<Group />
</ListItemIcon>
<ListItemText>Followers</ListItemText>
<ListItemSecondaryAction>
<span aria-label="The number of the account follower">
{profile()?.followersCount ?? ""}
</span>
</ListItemSecondaryAction>
</MenuItem>
<MenuItem disabled>
<ListItemIcon>
<Subject />
</ListItemIcon>
<ListItemText>Following</ListItemText>
<ListItemSecondaryAction>
<span aria-label="The number the account following">
{profile()?.followingCount ?? ""}
</span>
</ListItemSecondaryAction>
</MenuItem>
<MenuItem disabled>
<ListItemIcon>
<PersonOff />
</ListItemIcon>
<ListItemText>Blocklist</ListItemText>
</MenuItem>
<MenuItem disabled>
<ListItemIcon>
<Send />
</ListItemIcon>
<ListItemText>Mention in...</ListItemText>
</MenuItem>
<Divider />
<MenuItem
component={"a"}
href={profile()?.url}
target="_blank"
rel="noopener noreferrer"
>
<ListItemIcon>
<OpenInBrowser />
</ListItemIcon>
<ListItemText>Open in browser...</ListItemText>
</MenuItem>
<MenuItem onClick={() => share({ url: profile()?.url })}>
<ListItemIcon>
<Share />
</ListItemIcon>
<ListItemText>Share...</ListItemText>
</MenuItem>
</Menu>
<div
style={{
height: `${268 * (Math.min(560, windowSize.width) / 560)}px`,
}}
class="banner"
role="presentation"
>
<img
ref={(e) => obx.observe(e)}
src={bannerImg()}
crossOrigin="anonymous"
alt={`Banner image for ${profile()?.displayName || "the user"}`}
onLoad={(event) => {
const ins = new FastAverageColor();
const colors = ins.getColor(event.currentTarget);
setBannerSampledColors({
average: colors.hex,
text: colors.isDark ? "white" : "black",
});
ins.destroy();
}}
></img>
</div>
<Menu {...subscribeMenuState}>
<MenuItem
onClick={toggleSubscribeHome}
aria-label={`${relationship()?.following ? "Unfollow" : "Follow"} on your home timeline`}
>
<ListItemAvatar>
<Avatar src={session().account?.inf?.avatar}></Avatar>
</ListItemAvatar>
<ListItemText
secondary={
relationship()?.following
? undefined
: profile()?.locked
? "A request will be sent"
: undefined
}
>
<span ref={useSessionDisplayName}></span>
<span>'s Home</span>
</ListItemText>
<Checkbox checked={relationship()?.following ?? false} />
</MenuItem>
</Menu>
<div
class="intro"
style={{
"background-color": bannerSampledColors()?.average,
color: bannerSampledColors()?.text,
}}
>
<section class="acct-grp">
<Avatar
src={avatarImg()}
alt={`${profile()?.displayName || "the user"}'s avatar`}
sx={{
marginTop: "calc(-16px - 72px / 2)",
width: "72px",
height: "72px",
}}
></Avatar>
<div class="name-grp">
<div class="display-name">
<Show when={profile()?.bot}>
<SmartToySharp class="acct-mark" aria-label="Bot" />
</Show>
<Show when={profile()?.locked}>
<Lock class="acct-mark" aria-label="Locked" />
</Show>
<Body2
component="span"
ref={(e: HTMLElement) =>
createRenderEffect(() => (e.innerHTML = displayName()))
}
aria-label="Display name"
></Body2>
</div>
<span aria-label="Complete username" class="username">{fullUsername()}</span>
</div>
<div role="presentation">
<Switch>
<Match
when={
!session().account ||
profileUncaught.loading ||
profileUncaught.error
}
>
{<></>}
</Match>
<Match when={isCurrentSessionProfile()}>
<IconButton color="inherit">
<Edit />
</IconButton>
</Match>
<Match when={true}>
<Button
variant="contained"
color="secondary"
<Show when={session().account && profile()}>
<Show
when={isCurrentSessionProfile()}
fallback={
<MenuItem
onClick={(event) => {
openSubscribeMenu(
event.currentTarget.getBoundingClientRect(),
);
const { left, right, top } =
event.currentTarget.getBoundingClientRect();
openSubscribeMenu({
left,
right,
top,
e: 1,
});
}}
>
{relationship()?.following ? "Subscribed" : "Subscribe"}
</Button>
</Match>
</Switch>
</div>
</section>
<section
class="description"
aria-label={`${profile()?.displayName || "the user"}'s description`}
ref={(e) =>
createRenderEffect(() => (e.innerHTML = description() || ""))
}
></section>
<table
class="acct-fields"
aria-label={`${profile()?.displayName || "the user"}'s fields`}
>
<tbody>
<For each={profile()?.fields ?? []}>
{(item, index) => {
return (
<tr data-field-index={index()}>
<td>{item.name}</td>
<td>
<Show when={item.verifiedAt}>
<Verified />
</Show>
</td>
<td
ref={(e) => {
createRenderEffect(() => (e.innerHTML = item.value));
}}
></td>
</tr>
);
}}
</For>
</tbody>
</table>
</div>
<div class="toot-list-toolbar">
<TootFilterButton
options={{
pinned: "Pinneds",
boost: "Boosts",
reply: "Replies",
original: "Originals",
}}
applied={recentTootFilter()}
onApply={setRecentTootFilter}
disabledKeys={["original"]}
></TootFilterButton>
</div>
<TimeSourceProvider value={time}>
<Show when={recentTootFilter().pinned && pinnedToots.list.length > 0}>
<TootList
threads={pinnedToots.list}
onUnknownThread={pinnedToots.getPath}
onChangeToot={pinnedToots.set}
/>
<ListItemIcon>
<PlaylistAdd />
</ListItemIcon>
<ListItemText>Subscribe...</ListItemText>
</MenuItem>
}
>
<MenuItem disabled>
<ListItemIcon>
<Edit />
</ListItemIcon>
<ListItemText>Edit...</ListItemText>
</MenuItem>
</Show>
<Divider />
</Show>
<MenuItem disabled>
<ListItemIcon>
<Group />
</ListItemIcon>
<ListItemText>Followers</ListItemText>
<ListItemSecondaryAction>
<span aria-label="The number of the account follower">
{profile()?.followersCount ?? ""}
</span>
</ListItemSecondaryAction>
</MenuItem>
<MenuItem disabled>
<ListItemIcon>
<Subject />
</ListItemIcon>
<ListItemText>Following</ListItemText>
<ListItemSecondaryAction>
<span aria-label="The number the account following">
{profile()?.followingCount ?? ""}
</span>
</ListItemSecondaryAction>
</MenuItem>
<MenuItem disabled>
<ListItemIcon>
<PersonOff />
</ListItemIcon>
<ListItemText>Blocklist</ListItemText>
</MenuItem>
<MenuItem disabled>
<ListItemIcon>
<Send />
</ListItemIcon>
<ListItemText>Mention in...</ListItemText>
</MenuItem>
<Divider />
</Show>
<TootList
id={recentTootListId}
threads={recentToots.list}
onUnknownThread={recentToots.getPath}
onChangeToot={recentToots.set}
/>
</TimeSourceProvider>
<Show when={!recentTootChunk()?.done}>
<MenuItem
component={"a"}
href={profile()?.url}
target="_blank"
rel="noopener noreferrer"
>
<ListItemIcon>
<OpenInBrowser />
</ListItemIcon>
<ListItemText>Open in browser...</ListItemText>
</MenuItem>
<MenuItem onClick={() => share({ url: profile()?.url })}>
<ListItemIcon>
<Share />
</ListItemIcon>
<ListItemText>Share...</ListItemText>
</MenuItem>
</Menu>
<div
style={{
"text-align": "center",
"padding-bottom": "var(--safe-area-inset-bottom)",
height: `${268 * (Math.min(560, windowSize.width) / 560)}px`,
}}
class="banner"
role="presentation"
>
<img
ref={(e) => obx.observe(e)}
src={bannerImg()}
crossOrigin="anonymous"
alt={`Banner image for ${profile()?.displayName || "the user"}`}
onLoad={(event) => {
const ins = new FastAverageColor();
const colors = ins.getColor(event.currentTarget);
setBannerSampledColors({
average: colors.hex,
text: colors.isDark ? "white" : "black",
});
ins.destroy();
}}
></img>
</div>
<Menu {...subscribeMenuState}>
<MenuItem
onClick={toggleSubscribeHome}
aria-label={`${relationship()?.following ? "Unfollow" : "Follow"} on your home timeline`}
>
<ListItemAvatar>
<Avatar src={session().account?.inf?.avatar}></Avatar>
</ListItemAvatar>
<ListItemText
secondary={
relationship()?.following
? undefined
: profile()?.locked
? "A request will be sent"
: undefined
}
>
<span ref={useSessionDisplayName}></span>
<span>'s Home</span>
</ListItemText>
<Checkbox checked={relationship()?.following ?? false} />
</MenuItem>
</Menu>
<div
class="intro"
style={{
"background-color": bannerSampledColors()?.average,
color: bannerSampledColors()?.text,
}}
>
<IconButton
aria-label="Load More"
aria-controls={recentTootListId}
size="large"
color="primary"
onClick={[refetchRecentToots, "prev"]}
disabled={isTootListLoading()}
<section class="acct-grp">
<Avatar
src={avatarImg()}
alt={`${profile()?.displayName || "the user"}'s avatar`}
sx={{
marginTop: "calc(-16px - 72px / 2)",
width: "72px",
height: "72px",
}}
></Avatar>
<div class="name-grp">
<div class="display-name">
<Show when={profile()?.bot}>
<SmartToySharp class="acct-mark" aria-label="Bot" />
</Show>
<Show when={profile()?.locked}>
<Lock class="acct-mark" aria-label="Locked" />
</Show>
<Body2
component="span"
ref={(e: HTMLElement) =>
createRenderEffect(() => (e.innerHTML = displayName()))
}
aria-label="Display name"
></Body2>
</div>
<span aria-label="Complete username" class="username">
{fullUsername()}
</span>
</div>
<div role="presentation">
<Switch>
<Match
when={
!session().account ||
profileUncaught.loading ||
profileUncaught.error
}
>
{<></>}
</Match>
<Match when={isCurrentSessionProfile()}>
<IconButton color="inherit">
<Edit />
</IconButton>
</Match>
<Match when={true}>
<Button
variant="contained"
color="secondary"
onClick={(event) => {
openSubscribeMenu(
event.currentTarget.getBoundingClientRect(),
);
}}
>
{relationship()?.following ? "Subscribed" : "Subscribe"}
</Button>
</Match>
</Switch>
</div>
</section>
<section
class="description"
aria-label={`${profile()?.displayName || "the user"}'s description`}
ref={(e) =>
createRenderEffect(() => (e.innerHTML = description() || ""))
}
></section>
<table
class="acct-fields"
aria-label={`${profile()?.displayName || "the user"}'s fields`}
>
<Show when={isTootListLoading()} fallback={<ExpandMore />}>
<CircularProgress sx={{ width: "24px", height: "24px" }} />
</Show>
</IconButton>
<tbody>
<For each={profile()?.fields ?? []}>
{(item, index) => {
return (
<tr data-field-index={index()}>
<td>{item.name}</td>
<td>
<Show when={item.verifiedAt}>
<Verified />
</Show>
</td>
<td
ref={(e) => {
createRenderEffect(() => (e.innerHTML = item.value));
}}
></td>
</tr>
);
}}
</For>
</tbody>
</table>
</div>
</Show>
</div>
<div class="recent-toots">
<div class="toot-list-toolbar">
<TootFilterButton
options={{
pinned: "Pinneds",
boost: "Boosts",
reply: "Replies",
original: "Originals",
}}
applied={recentTootFilter()}
onApply={setRecentTootFilter}
disabledKeys={["original"]}
></TootFilterButton>
</div>
<TimeSourceProvider value={time}>
<Show when={recentTootFilter().pinned && pinnedToots.list.length > 0}>
<TootList
threads={pinnedToots.list}
onUnknownThread={pinnedToots.getPath}
onChangeToot={pinnedToots.set}
/>
<Divider />
</Show>
<TootList
id={recentTootListId}
threads={recentToots.list}
onUnknownThread={recentToots.getPath}
onChangeToot={recentToots.set}
/>
</TimeSourceProvider>
<Show when={!recentTootChunk()?.done}>
<div
style={{
"text-align": "center",
"padding-bottom": "var(--safe-area-inset-bottom)",
}}
>
<IconButton
aria-label="Load More"
aria-controls={recentTootListId}
size="large"
color="primary"
onClick={[refetchRecentToots, "prev"]}
disabled={isTootListLoading()}
>
<Show when={isTootListLoading()} fallback={<ExpandMore />}>
<CircularProgress sx={{ width: "24px", height: "24px" }} />
</Show>
</IconButton>
</div>
</Show>
</div>
</Scaffold>
);
};