Profile: support wide page
This commit is contained in:
parent
bb3ba32dc5
commit
66d0bc8d84
3 changed files with 347 additions and 302 deletions
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue