Compare commits
5 commits
18fa2810c4
...
62aaaeee9a
Author | SHA1 | Date | |
---|---|---|---|
|
62aaaeee9a | ||
|
66d0bc8d84 | ||
|
bb3ba32dc5 | ||
|
fbbac36b4a | ||
|
487de9237b |
6 changed files with 377 additions and 332 deletions
|
@ -5,12 +5,7 @@
|
||||||
@supports (grid-template-rows: masonry) {
|
@supports (grid-template-rows: masonry) {
|
||||||
.NativeMasonry {
|
.NativeMasonry {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(44px, min-content));
|
grid-template-columns: repeat(auto-fit, minmax(33%, min-content));
|
||||||
grid-template-rows: masonry;
|
grid-template-rows: masonry;
|
||||||
|
|
||||||
&:has(> :last-child:nth-child(2n)) {
|
|
||||||
grid-template-columns: repeat(2, minmax(auto, min-content));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,8 @@ import {
|
||||||
type Ref,
|
type Ref,
|
||||||
createRenderEffect,
|
createRenderEffect,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
children,
|
|
||||||
createEffect,
|
createEffect,
|
||||||
createSignal,
|
createSignal,
|
||||||
onMount,
|
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { Dynamic, type DynamicProps } from "solid-js/web";
|
import { Dynamic, type DynamicProps } from "solid-js/web";
|
||||||
import MasonryLayout from "masonry-layout";
|
import MasonryLayout from "masonry-layout";
|
||||||
|
@ -20,7 +18,6 @@ type MasonryContainer =
|
||||||
| Component<{
|
| Component<{
|
||||||
ref?: Ref<Element>;
|
ref?: Ref<Element>;
|
||||||
class?: string;
|
class?: string;
|
||||||
children?: JSX.Element;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type ElementOf<T extends MasonryContainer> =
|
type ElementOf<T extends MasonryContainer> =
|
||||||
|
@ -37,7 +34,10 @@ function forwardRef<T>(value: T, ref?: Ref<T>) {
|
||||||
(ref as (value: T) => void)(value);
|
(ref as (value: T) => void)(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMasonry(element: Element, options: () => MasonryLayout.Options) {
|
function createCompatMasonry(
|
||||||
|
element: Element,
|
||||||
|
options: () => MasonryLayout.Options,
|
||||||
|
) {
|
||||||
const layout = new MasonryLayout(element, {
|
const layout = new MasonryLayout(element, {
|
||||||
initLayout: false,
|
initLayout: false,
|
||||||
});
|
});
|
||||||
|
@ -46,11 +46,21 @@ function createMasonry(element: Element, options: () => MasonryLayout.Options) {
|
||||||
|
|
||||||
const size = createElementSize(element);
|
const size = createElementSize(element);
|
||||||
|
|
||||||
|
const treeMutObx = new MutationObserver(() => {
|
||||||
|
layout.reloadItems?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
onCleanup(() => treeMutObx.disconnect());
|
||||||
|
|
||||||
createRenderEffect(() => {
|
createRenderEffect(() => {
|
||||||
const opts = options();
|
const opts = options();
|
||||||
layout.option?.(opts);
|
layout.option?.(opts);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createRenderEffect(() => {
|
||||||
|
treeMutObx.observe(element, { childList: true });
|
||||||
|
});
|
||||||
|
|
||||||
createRenderEffect(() => {
|
createRenderEffect(() => {
|
||||||
const width = size.width; // only tracking width
|
const width = size.width; // only tracking width
|
||||||
layout.layout?.();
|
layout.layout?.();
|
||||||
|
@ -61,8 +71,6 @@ function createMasonry(element: Element, options: () => MasonryLayout.Options) {
|
||||||
layout.layout?.();
|
layout.layout?.();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportsCSSMasonryLayout = /* @__PURE__ */ CSS.supports(
|
const supportsCSSMasonryLayout = /* @__PURE__ */ CSS.supports(
|
||||||
|
@ -85,9 +93,7 @@ if (import.meta.env.VITE_PLATFROM_MASONRY_ALWAYS_COMPAT) {
|
||||||
function MasonryCompat<T extends MasonryContainer>(
|
function MasonryCompat<T extends MasonryContainer>(
|
||||||
oprops: DynamicProps<T> & { class?: string },
|
oprops: DynamicProps<T> & { class?: string },
|
||||||
) {
|
) {
|
||||||
const [props, rest] = splitProps(oprops, ["ref", "children", "class"]);
|
const [props, rest] = splitProps(oprops, ["ref", "class"]);
|
||||||
|
|
||||||
const childrenComponents = children(() => props.children);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dynamic
|
<Dynamic
|
||||||
|
@ -96,7 +102,7 @@ function MasonryCompat<T extends MasonryContainer>(
|
||||||
|
|
||||||
const [columnGap, setColumnGap] = createSignal<number>();
|
const [columnGap, setColumnGap] = createSignal<number>();
|
||||||
|
|
||||||
const layout = createMasonry(element, () => {
|
createCompatMasonry(element, () => {
|
||||||
return {
|
return {
|
||||||
gutter: columnGap(),
|
gutter: columnGap(),
|
||||||
};
|
};
|
||||||
|
@ -115,18 +121,9 @@ function MasonryCompat<T extends MasonryContainer>(
|
||||||
setColumnGap(Number(colGap.slice(0, colGap.length - 2)));
|
setColumnGap(Number(colGap.slice(0, colGap.length - 2)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
createRenderEffect(() => {
|
|
||||||
childrenComponents(); // just tracks
|
|
||||||
setTimeout(() => {
|
|
||||||
layout.reloadItems?.();
|
|
||||||
layout.layout?.();
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
class={`Masonry CompatMasonry ${props.class || ""}`}
|
class={`Masonry CompatMasonry ${props.class || ""}`}
|
||||||
{...rest}
|
{...rest}
|
||||||
children={childrenComponents}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -147,6 +144,13 @@ function MasonryNative<T extends MasonryContainer>(
|
||||||
* and fallback to masonry-layout if not supported. The children
|
* and fallback to masonry-layout if not supported. The children
|
||||||
* must have specified width and height.
|
* must have specified width and height.
|
||||||
*
|
*
|
||||||
|
* Testing native behaviour:
|
||||||
|
* - Firefox: in `about:config`, search for `layout.css.grid-template-masonry-value.enabled`
|
||||||
|
*
|
||||||
|
* Class `NativeMasonry` will be added to the element if it's under the
|
||||||
|
* css masonry layout, otherwise it's `CompatMasonry`. `Masonry` is always
|
||||||
|
* added.
|
||||||
|
*
|
||||||
* **Children Changes** As the children changed, reflow will be triggered,
|
* **Children Changes** As the children changed, reflow will be triggered,
|
||||||
* and there is might be a blink (or transition) for user. If it's not your
|
* and there is might be a blink (or transition) for user. If it's not your
|
||||||
* intention, don't remove/add the direct children. Instead wraps them under
|
* intention, don't remove/add the direct children. Instead wraps them under
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
.StackedPage {
|
.StackedPage {
|
||||||
container: StackedPage / size;
|
contain: strict;
|
||||||
display: contents;
|
container: StackedPage / inline-size;
|
||||||
max-width: 100vw;
|
width: 100vw;
|
||||||
max-width: 100dvw;
|
width: 100dvw;
|
||||||
|
height: 100vh;
|
||||||
contain: layout;
|
height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.StackedPage {
|
dialog.StackedPage {
|
||||||
|
|
|
@ -115,6 +115,46 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@supports (container-type: inline-size) {
|
||||||
|
@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 {
|
.Profile__page-title {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -237,315 +237,321 @@ const Profile: Component = () => {
|
||||||
}
|
}
|
||||||
class="Profile"
|
class="Profile"
|
||||||
>
|
>
|
||||||
<Menu
|
<div class="details">
|
||||||
id={optMenuId}
|
<Menu
|
||||||
open={menuOpen()}
|
id={optMenuId}
|
||||||
onClose={[setMenuOpen, false]}
|
open={menuOpen()}
|
||||||
anchor={() =>
|
onClose={[setMenuOpen, false]}
|
||||||
document.getElementById(menuButId)!.getBoundingClientRect()
|
anchor={() =>
|
||||||
}
|
document.getElementById(menuButId)!.getBoundingClientRect()
|
||||||
aria-label="Options for the Profile"
|
}
|
||||||
>
|
aria-label="Options for the Profile"
|
||||||
<Show when={session().account}>
|
>
|
||||||
<MenuItem>
|
<Show when={session().account}>
|
||||||
<ListItemAvatar>
|
<MenuItem>
|
||||||
<Avatar src={session().account?.inf?.avatar} />
|
<ListItemAvatar>
|
||||||
</ListItemAvatar>
|
<Avatar src={session().account?.inf?.avatar} />
|
||||||
<ListItemText secondary={"Default account"}>
|
</ListItemAvatar>
|
||||||
<span ref={useSessionDisplayName}></span>
|
<ListItemText secondary={"Default account"}>
|
||||||
</ListItemText>
|
<span ref={useSessionDisplayName}></span>
|
||||||
{/* <ArrowRight /> // for future */}
|
</ListItemText>
|
||||||
</MenuItem>
|
{/* <ArrowRight /> // for future */}
|
||||||
</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>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Show>
|
</Show>
|
||||||
<Divider />
|
<Show when={session().account && profile()}>
|
||||||
</Show>
|
<Show
|
||||||
<MenuItem disabled>
|
when={isCurrentSessionProfile()}
|
||||||
<ListItemIcon>
|
fallback={
|
||||||
<Group />
|
<MenuItem
|
||||||
</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"
|
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
openSubscribeMenu(
|
const { left, right, top } =
|
||||||
event.currentTarget.getBoundingClientRect(),
|
event.currentTarget.getBoundingClientRect();
|
||||||
);
|
openSubscribeMenu({
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
top,
|
||||||
|
e: 1,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{relationship()?.following ? "Subscribed" : "Subscribe"}
|
<ListItemIcon>
|
||||||
</Button>
|
<PlaylistAdd />
|
||||||
</Match>
|
</ListItemIcon>
|
||||||
</Switch>
|
<ListItemText>Subscribe...</ListItemText>
|
||||||
</div>
|
</MenuItem>
|
||||||
</section>
|
}
|
||||||
<section
|
>
|
||||||
class="description"
|
<MenuItem disabled>
|
||||||
aria-label={`${profile()?.displayName || "the user"}'s description`}
|
<ListItemIcon>
|
||||||
ref={(e) =>
|
<Edit />
|
||||||
createRenderEffect(() => (e.innerHTML = description() || ""))
|
</ListItemIcon>
|
||||||
}
|
<ListItemText>Edit...</ListItemText>
|
||||||
></section>
|
</MenuItem>
|
||||||
|
</Show>
|
||||||
<table
|
<Divider />
|
||||||
class="acct-fields"
|
</Show>
|
||||||
aria-label={`${profile()?.displayName || "the user"}'s fields`}
|
<MenuItem disabled>
|
||||||
>
|
<ListItemIcon>
|
||||||
<tbody>
|
<Group />
|
||||||
<For each={profile()?.fields ?? []}>
|
</ListItemIcon>
|
||||||
{(item, index) => {
|
<ListItemText>Followers</ListItemText>
|
||||||
return (
|
<ListItemSecondaryAction>
|
||||||
<tr data-field-index={index()}>
|
<span aria-label="The number of the account follower">
|
||||||
<td>{item.name}</td>
|
{profile()?.followersCount ?? ""}
|
||||||
<td>
|
</span>
|
||||||
<Show when={item.verifiedAt}>
|
</ListItemSecondaryAction>
|
||||||
<Verified />
|
</MenuItem>
|
||||||
</Show>
|
<MenuItem disabled>
|
||||||
</td>
|
<ListItemIcon>
|
||||||
<td
|
<Subject />
|
||||||
ref={(e) => {
|
</ListItemIcon>
|
||||||
createRenderEffect(() => (e.innerHTML = item.value));
|
<ListItemText>Following</ListItemText>
|
||||||
}}
|
<ListItemSecondaryAction>
|
||||||
></td>
|
<span aria-label="The number the account following">
|
||||||
</tr>
|
{profile()?.followingCount ?? ""}
|
||||||
);
|
</span>
|
||||||
}}
|
</ListItemSecondaryAction>
|
||||||
</For>
|
</MenuItem>
|
||||||
</tbody>
|
<MenuItem disabled>
|
||||||
</table>
|
<ListItemIcon>
|
||||||
</div>
|
<PersonOff />
|
||||||
|
</ListItemIcon>
|
||||||
<div class="toot-list-toolbar">
|
<ListItemText>Blocklist</ListItemText>
|
||||||
<TootFilterButton
|
</MenuItem>
|
||||||
options={{
|
<MenuItem disabled>
|
||||||
pinned: "Pinneds",
|
<ListItemIcon>
|
||||||
boost: "Boosts",
|
<Send />
|
||||||
reply: "Replies",
|
</ListItemIcon>
|
||||||
original: "Originals",
|
<ListItemText>Mention in...</ListItemText>
|
||||||
}}
|
</MenuItem>
|
||||||
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 />
|
<Divider />
|
||||||
</Show>
|
<MenuItem
|
||||||
<TootList
|
component={"a"}
|
||||||
id={recentTootListId}
|
href={profile()?.url}
|
||||||
threads={recentToots.list}
|
target="_blank"
|
||||||
onUnknownThread={recentToots.getPath}
|
rel="noopener noreferrer"
|
||||||
onChangeToot={recentToots.set}
|
>
|
||||||
/>
|
<ListItemIcon>
|
||||||
</TimeSourceProvider>
|
<OpenInBrowser />
|
||||||
|
</ListItemIcon>
|
||||||
<Show when={!recentTootChunk()?.done}>
|
<ListItemText>Open in browser...</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => share({ url: profile()?.url })}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Share />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>Share...</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
"text-align": "center",
|
height: `${268 * (Math.min(560, windowSize.width) / 560)}px`,
|
||||||
"padding-bottom": "var(--safe-area-inset-bottom)",
|
}}
|
||||||
|
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
|
<section class="acct-grp">
|
||||||
aria-label="Load More"
|
<Avatar
|
||||||
aria-controls={recentTootListId}
|
src={avatarImg()}
|
||||||
size="large"
|
alt={`${profile()?.displayName || "the user"}'s avatar`}
|
||||||
color="primary"
|
sx={{
|
||||||
onClick={[refetchRecentToots, "prev"]}
|
marginTop: "calc(-16px - 72px / 2)",
|
||||||
disabled={isTootListLoading()}
|
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 />}>
|
<tbody>
|
||||||
<CircularProgress sx={{ width: "24px", height: "24px" }} />
|
<For each={profile()?.fields ?? []}>
|
||||||
</Show>
|
{(item, index) => {
|
||||||
</IconButton>
|
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>
|
||||||
</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>
|
</Scaffold>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
contain: layout style;
|
contain: layout style;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
> * {
|
>* {
|
||||||
max-height: 35vh;
|
max-height: 35vh;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
|
@ -26,16 +26,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> * > * {
|
>*>* {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
> * > :where(img, video) {
|
>*> :where(img, video) {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
> * >.sensitive-placeholder {
|
>*>.sensitive-placeholder {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
display: inline flex;
|
display: inline flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue