adjust thread visual

This commit is contained in:
thislight 2024-10-12 19:48:34 +08:00
parent 8a021836cf
commit e3f592554b
No known key found for this signature in database
GPG key ID: A50F9451AC56A63E
5 changed files with 131 additions and 64 deletions

View file

@ -261,6 +261,7 @@ export function createTimeline(
if (!chk) {
return;
}
console.debug("fetched chunk", chk);
batch(() => {

View file

@ -270,7 +270,9 @@ const Home: ParentComponent = (props) => {
</TimeSourceProvider>
<Suspense>
<HeroSourceProvider value={[heroSrc, setHeroSrc]}>
<BottomSheet open={!!child()}>{child()}</BottomSheet>
<BottomSheet open={!!child()} onClose={() => navigate(-1)}>
{child()}
</BottomSheet>
</HeroSourceProvider>
</Suspense>
</Scaffold>

View file

@ -111,13 +111,17 @@ type TootActionGroupProps<T extends mastodon.v1.Status> = {
onRetoot?: (value: T) => void;
onFavourite?: (value: T) => void;
onBookmark?: (value: T) => void;
onReply?: (value: T) => void;
onReply?: (
value: T,
event: MouseEvent & { currentTarget: HTMLButtonElement },
) => void;
};
type TootCardProps = {
status: mastodon.v1.Status;
actionable?: boolean;
evaluated?: boolean;
thread?: "top" | "bottom" | "middle";
} & TootActionGroupProps<mastodon.v1.Status> &
JSX.HTMLElementTags["article"];
@ -125,19 +129,40 @@ function isolatedCallback(e: MouseEvent) {
e.stopPropagation();
}
export function findRootToot(element: HTMLElement) {
let current: HTMLElement | null = element;
while (current && !current.classList.contains(tootStyle.toot)) {
current = current.parentElement;
}
if (!current) {
throw Error(
`the element must be placed under a element with ${tootStyle.toot}`,
);
}
return current;
}
function TootActionGroup<T extends mastodon.v1.Status>(
props: TootActionGroupProps<T> & { value: T },
) {
let actGrpElement: HTMLDivElement;
const toot = () => props.value;
return (
<div class={tootStyle.tootBottomActionGrp} onClick={isolatedCallback}>
<div
ref={actGrpElement!}
class={tootStyle.tootBottomActionGrp}
onClick={isolatedCallback}
>
<Show when={props.onReply}>
<Button
class={tootStyle.tootActionWithCount}
onClick={() => props.onReply?.(toot())}
onClick={[props.onReply!, props.value]}
>
<ReplyAll />
<span>{toot().repliesCount}</span>
</Button>
</Show>
<Button
class={tootStyle.tootActionWithCount}
style={{
@ -288,7 +313,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
let rootRef: HTMLElement;
const [managed, managedActionGroup, rest] = splitProps(
props,
["status", "lang", "class", "actionable", "evaluated"],
["status", "lang", "class", "actionable", "evaluated", "thread"],
["onRetoot", "onFavourite", "onBookmark", "onReply"],
);
const now = useTimeSource();
@ -300,6 +325,42 @@ const RegularToot: Component<TootCardProps> = (props) => {
margin-left: calc(var(--toot-avatar-size) + var(--card-pad) + 8px);
margin-block: 8px;
}
.thread-top,
.thread-mid,
.thread-btm {
position: relative;
&::before {
content: "";
position: absolute;
left: 36px;
background-color: var(--tutu-color-secondary);
width: 2px;
display: block;
}
}
.thread-mid {
&::before {
top: 0;
bottom: 0;
}
}
.thread-top {
&::before {
top: 16px;
bottom: 0;
}
}
.thread-btm {
&::before {
top: 0;
height: 16px;
}
}
`;
return (
@ -308,6 +369,9 @@ const RegularToot: Component<TootCardProps> = (props) => {
classList={{
[tootStyle.toot]: true,
[tootStyle.expanded]: managed.evaluated,
"thread-top": managed.thread === "top",
"thread-mid": managed.thread === "middle",
"thread-btm": managed.thread === "bottom",
[managed.class || ""]: true,
}}
ref={rootRef!}

View file

@ -9,14 +9,19 @@ import {
} from "solid-js";
import CompactToot from "./CompactToot";
import { useTimeSource } from "../platform/timesrc";
import RegularToot from "./RegularToot";
import RegularToot, { findRootToot } from "./RegularToot";
import cardStyle from "../material/cards.module.css";
import { css } from "solid-styled";
type TootActionTarget = {
client: mastodon.rest.Client;
status: mastodon.v1.Status;
};
type TootActions = {
onBoost(client: mastodon.rest.Client, status: mastodon.v1.Status): void;
onBookmark(client: mastodon.rest.Client, status: mastodon.v1.Status): void;
onReply(client: mastodon.rest.Client, status: mastodon.v1.Status): void;
onReply(target: TootActionTarget, element: HTMLElement): void;
};
type ThreadProps = {
@ -37,55 +42,48 @@ const Thread: Component<ThreadProps> = (props) => {
props.onBookmark(props.client, status);
};
const reply = (status: mastodon.v1.Status) => {
props.onReply(props.client, status);
const reply = (
status: mastodon.v1.Status,
event: MouseEvent & { currentTarget: HTMLElement },
) => {
const element = findRootToot(event.currentTarget);
props.onReply({ client: props.client, status }, element);
};
css`
article {
transition:
margin 90ms var(--tutu-anim-curve-sharp),
var(--tutu-transition-shadow);
.thread {
user-select: none;
cursor: pointer;
}
.thread-line {
position: relative;
&::before {
content: "";
position: absolute;
left: 36px;
top: 16px;
bottom: 0;
background-color: var(--tutu-color-secondary);
width: 2px;
display: block;
}
}
`;
`
return (
<article
ref={props.ref}
classList={{
"thread-line": props.toots.length > 1,
}}
>
<article ref={props.ref} class="thread">
<For each={props.toots}>
{(status, index) => (
{(status, index) => {
const useThread = props.toots.length > 1;
const threadPosition = useThread
? index() === 0
? "top"
: index() === props.toots.length - 1
? "bottom"
: "middle"
: undefined;
return (
<RegularToot
data-status-id={status.id}
data-thread-sort={index()}
status={status}
class={`${cardStyle.card}`}
thread={threadPosition}
class={cardStyle.card}
evaluated={props.isExpended(status)}
actionable={props.isExpended(status)}
onBookmark={(s) => bookmark(s)}
onRetoot={(s) => boost(s)}
onReply={(s) => reply(s)}
onReply={reply}
onClick={[props.onItemClick, status]}
/>
)}
);
}}
</For>
</article>
);

View file

@ -113,27 +113,29 @@ const TimelinePanel: Component<{
</Show>
<For each={timeline.list}>
{(itemId, index) => {
let element: HTMLElement | undefined;
const path = timeline.getPath(itemId)!;
const toots = path.reverse().map((x) => x.value);
return (
<Thread
ref={element}
toots={toots}
onBoost={onBoost}
onBookmark={onBookmark}
onReply={(client, status) =>
onReply={
({ status }, element) =>
props.openFullScreenToot(status, element, true)
}
client={props.client}
isExpended={(status) => status.id === expandedThreadId()}
onItemClick={(status) => {
onItemClick={(status, event) => {
setTyping(false);
if (status.id !== expandedThreadId()) {
setExpandedThreadId((x) => (x ? undefined : status.id));
} else {
props.openFullScreenToot(status, element);
props.openFullScreenToot(
status,
event.currentTarget as HTMLElement,
);
}
}}
/>