PreviewCard: move to sparated files
All checks were successful
/ depoly (push) Successful in 1m24s

This commit is contained in:
thislight 2024-11-12 19:21:10 +08:00
parent 3cacf64c8e
commit 4d9c2b3aa8
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E
3 changed files with 167 additions and 95 deletions

View file

@ -5,13 +5,11 @@ import {
type JSX, type JSX,
Show, Show,
createRenderEffect, createRenderEffect,
createEffect,
createMemo,
} from "solid-js"; } from "solid-js";
import tootStyle from "./toot.module.css"; import tootStyle from "./toot.module.css";
import { formatRelative } from "date-fns"; import { formatRelative } from "date-fns";
import Img from "../material/Img.js"; import Img from "../material/Img.js";
import { Body1, Body2, Title } from "../material/typography.js"; import { Body2 } from "../material/typography.js";
import { css } from "solid-styled"; import { css } from "solid-styled";
import { import {
BookmarkAddOutlined, BookmarkAddOutlined,
@ -30,13 +28,12 @@ import { Divider } from "@suid/material";
import cardStyle from "../material/cards.module.css"; import cardStyle from "../material/cards.module.css";
import Button from "../material/Button.js"; import Button from "../material/Button.js";
import MediaAttachmentGrid from "./MediaAttachmentGrid.js"; import MediaAttachmentGrid from "./MediaAttachmentGrid.js";
import Color from "colorjs.io";
import { useDateFnLocale } from "../platform/i18n"; import { useDateFnLocale } from "../platform/i18n";
import { canShare, share } from "../platform/share"; import { canShare, share } from "../platform/share";
import { makeAcctText, useDefaultSession } from "../masto/clients"; import { makeAcctText, useDefaultSession } from "../masto/clients";
import TootContent from "./toot-components/TootContent"; import TootContent from "./toot-components/TootContent";
import BoostIcon from "./toot-components/BoostIcon"; import BoostIcon from "./toot-components/BoostIcon";
import { averageColorHex } from "../platform/blurhash"; import PreviewCard from "./toot-components/PreviewCard";
type TootActionGroupProps<T extends mastodon.v1.Status> = { type TootActionGroupProps<T extends mastodon.v1.Status> = {
onRetoot?: (value: T) => void; onRetoot?: (value: T) => void;
@ -186,95 +183,6 @@ function TootAuthorGroup(
); );
} }
export function TootPreviewCard(props: {
src: mastodon.v1.PreviewCard;
alwaysCompact?: boolean;
}) {
let root: HTMLAnchorElement;
createEffect(() => {
if (props.alwaysCompact) {
root.classList.add(tootStyle.compact);
return;
}
if (!props.src.width) return;
const width = root.getBoundingClientRect().width;
if (width > props.src.width) {
root.classList.add(tootStyle.compact);
} else {
root.classList.remove(tootStyle.compact);
}
});
const imgAverageColor = createMemo(() => {
if (!props.src.image) return;
return new Color(averageColorHex(props.src.blurhash));
});
const prefersWhiteText = createMemo(() => {
const oc = imgAverageColor();
if (!oc) return;
const colorWhite = new Color("white");
return colorWhite.luminance / oc.luminance > 3.5;
});
const focusSurfaceColor = createMemo(() => {
const oc = imgAverageColor();
if (!oc) return;
if (prefersWhiteText()) {
return new Color(oc).darken(0.2);
} else {
return new Color(oc).lighten(0.2);
}
});
const textColorName = createMemo(() => {
const useWhiteText = prefersWhiteText();
if (typeof useWhiteText === "undefined") {
return;
}
return useWhiteText ? "white" : "black";
});
const secondaryTextColor = createMemo(() => {
const tcn = textColorName();
if (!tcn) return;
const tc = new Color(tcn);
tc.alpha = 0.75;
return tc;
});
return (
<a
ref={root!}
class={tootStyle.previewCard}
href={props.src.url}
target="_blank"
referrerPolicy="unsafe-url"
style={{
"--tutu-color-surface": imgAverageColor()?.toString(),
"--tutu-color-surface-d": focusSurfaceColor()?.toString(),
"--tutu-color-on-surface": textColorName(),
"--tutu-color-secondary-text-on-surface":
secondaryTextColor()?.toString(),
}}
>
<Show when={props.src.image}>
<img
crossOrigin="anonymous"
src={props.src.image!}
width={props.src.width || undefined}
height={props.src.height || undefined}
loading="lazy"
/>
</Show>
<Title component="h1">{props.src.title}</Title>
<Body1 component="p">{props.src.description}</Body1>
</a>
);
}
/** /**
* find bottom-to-top the element with `data-action`. * find bottom-to-top the element with `data-action`.
*/ */
@ -420,7 +328,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
class={tootStyle.tootContent} class={tootStyle.tootContent}
/> />
<Show when={toot().card}> <Show when={toot().card}>
<TootPreviewCard src={toot().card!} /> <PreviewCard src={toot().card!} />
</Show> </Show>
<Show when={toot().mediaAttachments.length > 0}> <Show when={toot().mediaAttachments.length > 0}>
<MediaAttachmentGrid attachments={toot().mediaAttachments} /> <MediaAttachmentGrid attachments={toot().mediaAttachments} />

View file

@ -0,0 +1,67 @@
.PreviewCard {
display: block;
border: 1px solid #eeeeee;
background-color: var(--tutu-color-surface);
text-decoration: none;
border-radius: 4px;
overflow: hidden;
margin-top: 1em;
margin-bottom: 1.5em;
color: var(--tutu-color-secondary-text-on-surface);
transition: color 220ms var(--tutu-anim-curve-std), background-color 220ms var(--tutu-anim-curve-std);
padding-bottom: 8px;
overflow: hidden;
z-index: 1;
position: relative;
>img {
background-color: #eeeeee;
max-width: 100%;
height: auto;
}
&:hover,
&:focus-visible {
background-color: var(--tutu-color-surface-d);
color: var(--tutu-color-on-surface);
>h1 {
text-decoration: underline;
}
}
>h1 {
color: var(--tutu-color-on-surface);
max-height: calc(4 * var(--title-line-height) * var(--title-size));
}
>p {
max-height: calc(8 * var(--body-line-height) * var(--body-size));
}
>h1,
>p {
margin-left: 16px;
margin-right: 16px;
overflow: hidden;
text-overflow: ellipsis;
}
&.compact {
display: grid;
grid-template-columns: minmax(10%, 30%) 1fr;
padding-left: 16px;
padding-right: 16px;
padding-top: 8px;
>img:first-child {
grid-row: 1 / 3;
object-fit: contain;
}
>h1,
>p {
margin-right: 0;
}
}
}

View file

@ -0,0 +1,97 @@
import Color from "colorjs.io";
import type { mastodon } from "masto";
import { createEffect, createMemo, Show } from "solid-js";
import { Title, Body1 } from "../../material/typography";
import { averageColorHex } from "../../platform/blurhash";
import "./PreviewCard.css";
export function PreviewCard(props: {
src: mastodon.v1.PreviewCard;
alwaysCompact?: boolean;
}) {
let root: HTMLAnchorElement;
createEffect(() => {
if (props.alwaysCompact) {
root.classList.add("compact");
return;
}
if (!props.src.width) return;
const width = root.getBoundingClientRect().width;
if (width > props.src.width) {
root.classList.add("compact");
} else {
root.classList.remove("compact");
}
});
const imgAverageColor = createMemo(() => {
if (!props.src.image) return;
return new Color(averageColorHex(props.src.blurhash));
});
const prefersWhiteText = createMemo(() => {
const oc = imgAverageColor();
if (!oc) return;
const colorWhite = new Color("white");
return colorWhite.luminance / oc.luminance > 3.5;
});
const focusSurfaceColor = createMemo(() => {
const oc = imgAverageColor();
if (!oc) return;
if (prefersWhiteText()) {
return new Color(oc).darken(0.2);
} else {
return new Color(oc).lighten(0.2);
}
});
const textColorName = createMemo(() => {
const useWhiteText = prefersWhiteText();
if (typeof useWhiteText === "undefined") {
return;
}
return useWhiteText ? "white" : "black";
});
const secondaryTextColor = createMemo(() => {
const tcn = textColorName();
if (!tcn) return;
const tc = new Color(tcn);
tc.alpha = 0.75;
return tc;
});
return (
<a
ref={root!}
class={"PreviewCard"}
href={props.src.url}
target="_blank"
referrerPolicy="unsafe-url"
style={{
"--tutu-color-surface": imgAverageColor()?.toString(),
"--tutu-color-surface-d": focusSurfaceColor()?.toString(),
"--tutu-color-on-surface": textColorName(),
"--tutu-color-secondary-text-on-surface":
secondaryTextColor()?.toString(),
}}
>
<Show when={props.src.image}>
<img
crossOrigin="anonymous"
src={props.src.image!}
width={props.src.width || undefined}
height={props.src.height || undefined}
loading="lazy"
/>
</Show>
<Title component="h1">{props.src.title}</Title>
<Body1 component="p">{props.src.description}</Body1>
</a>
);
}
export default PreviewCard;