MediaAttachmentGrid: major layout revised

* Now the media preview use masonry layout
* Supports preview video media
This commit is contained in:
thislight 2024-09-23 15:43:49 +08:00
parent 1a0a9d7e77
commit de03e62783
2 changed files with 28 additions and 14 deletions

View file

@ -12,24 +12,16 @@ import { css } from "solid-styled";
import tootStyle from "./toot.module.css"; import tootStyle from "./toot.module.css";
import MediaViewer from "./MediaViewer"; import MediaViewer from "./MediaViewer";
import { render } from "solid-js/web"; import { render } from "solid-js/web";
import { useWindowSize } from "@solid-primitives/resize-observer";
const MediaAttachmentGrid: Component<{ const MediaAttachmentGrid: Component<{
attachments: mastodon.v1.MediaAttachment[]; attachments: mastodon.v1.MediaAttachment[];
}> = (props) => { }> = (props) => {
let rootRef: HTMLElement; let rootRef: HTMLElement;
const [dispose, setDispose] = createSignal<() => void>();
const [viewerIndex, setViewerIndex] = createSignal<number>(); const [viewerIndex, setViewerIndex] = createSignal<number>();
const viewerOpened = () => typeof viewerIndex() !== "undefined"; const viewerOpened = () => typeof viewerIndex() !== "undefined";
const gridTemplateColumns = () => { const windowSize = useWindowSize();
const l = props.attachments.length; const vh35 = () => Math.floor(windowSize.height * 0.35);
if (l < 2) {
return "minmax(40px, auto)";
}
if (l < 4) {
return "repeat(2, minmax(40px, auto))";
}
return "repeat(3, minmax(40px, auto))";
};
createRenderEffect((lastDispose?: () => void) => { createRenderEffect((lastDispose?: () => void) => {
lastDispose?.(); lastDispose?.();
@ -61,7 +53,7 @@ const MediaAttachmentGrid: Component<{
css` css`
.attachments { .attachments {
grid-template-columns: ${gridTemplateColumns()}; column-count: ${(props.attachments.length === 1 ? 1 : 3).toString()};
} }
`; `;
return ( return (
@ -72,17 +64,39 @@ const MediaAttachmentGrid: Component<{
> >
<For each={props.attachments}> <For each={props.attachments}>
{(item, index) => { {(item, index) => {
const width = item.meta?.small?.width;
const height = item.meta?.small?.height;
const aspectRatio = item.meta?.small?.aspect;
const maxHeight = vh35();
const realHeight = height && height > maxHeight ? maxHeight : height;
const realWidth =
height && height > maxHeight
? maxHeight * (aspectRatio ?? 1)
: width;
const style = {
width: realWidth ? `${realWidth}px` : undefined,
height: realHeight ? `${realHeight}px` : undefined,
"aspect-ratio": aspectRatio?.toString(),
};
switch (item.type) { switch (item.type) {
case "image": case "image":
return ( return (
<img <img
src={item.previewUrl} src={item.previewUrl}
style={style}
alt={item.description || undefined} alt={item.description || undefined}
onClick={[openViewerFor, index()]} onClick={[openViewerFor, index()]}
loading="lazy" loading="lazy"
></img> ></img>
); );
case "video": case "video":
return (
<video
src={item.url || undefined}
style={style}
autoplay={false}
/>
);
case "gifv": case "gifv":
case "audio": case "audio":
case "unknown": case "unknown":

View file

@ -222,12 +222,12 @@
margin-top: 1em; margin-top: 1em;
margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px); margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px);
margin-right: var(--card-pad, 0); margin-right: var(--card-pad, 0);
display: grid;
gap: 4px; gap: 4px;
> :where(img) { > :where(img, video) {
max-height: 35vh; max-height: 35vh;
min-height: 40px; min-height: 40px;
min-width: 40px;
object-fit: contain; object-fit: contain;
max-width: 100%; max-width: 100%;
background-color: var(--tutu-color-surface-d); background-color: var(--tutu-color-surface-d);