import type { mastodon } from "masto"; import { type Component, For, Index, createMemo, createRenderEffect, createSignal, onCleanup, } from "solid-js"; import { css } from "solid-styled"; import tootStyle from "./toot.module.css"; import MediaViewer from "./MediaViewer"; import { render } from "solid-js/web"; import { createElementSize, useWindowSize, } from "@solid-primitives/resize-observer"; import { useStore } from "@nanostores/solid"; import { $settings } from "../settings/stores"; type ElementSize = { width: number; height: number }; function constraintedSize( { width: owidth, height: oheight }: Readonly, // originalSize { width: mwidth, height: mheight }: Readonly>, // modifier { width: maxWidth, height: maxHeight }: Readonly, // maxSize ) { const ySize = owidth + (mwidth ?? 0); const yScale = ySize > maxWidth ? ySize / maxWidth : 1; const xSize = oheight + (mheight ?? 0); const xScale = xSize > maxHeight ? xSize / maxHeight : 1; const maxScale = Math.max(yScale, xScale); const scaledWidth = owidth / maxScale; const scaledHeight = oheight / maxScale; return { width: scaledWidth, height: scaledHeight, }; } const MediaAttachmentGrid: Component<{ attachments: mastodon.v1.MediaAttachment[]; }> = (props) => { const [rootRef, setRootRef] = createSignal(); const [viewerIndex, setViewerIndex] = createSignal(); const viewerOpened = () => typeof viewerIndex() !== "undefined"; const settings = useStore($settings); const windowSize = useWindowSize(); createRenderEffect((lastDispose?: () => void) => { lastDispose?.(); const vidx = viewerIndex(); if (typeof vidx === "undefined") return; const container = document.createElement("div"); container.setAttribute("role", "presentation"); document.body.appendChild(container); return render(() => { onCleanup(() => { document.body.removeChild(container); }); return ( setViewerIndex()} /> ); }, container); }); const openViewerFor = (index: number) => { setViewerIndex(index); }; const columnCount = () => { if (props.attachments.length === 1) { return 1; } else if (props.attachments.length % 2 === 0) { return 2; } else { return 3; } }; const rawElementSize = createElementSize(rootRef); const elementWidth = () => rawElementSize.width; const itemMaxSize = createMemo(() => { const ewidth = elementWidth(); const width = ewidth ? (ewidth - (columnCount() - 1) * 4) / columnCount() : 1; return { height: windowSize.height * 0.35, width, }; }); const itemStyle = (item: mastodon.v1.MediaAttachment) => { const { width, height } = constraintedSize( item.meta?.small || { width: 1, height: 1 }, { width: 2, height: 2 }, itemMaxSize(), ); // I don't know why mastodon does not return this // and the condition for it to return this. // Anyway, It is useless now. // My hope is the FastAverageColor, but // we may need better tool to manage the performance impact // before using this. See #37. // TODO: use fast average color to extract accent color const accentColor = item.meta?.colors?.accent; return Object.assign( { width: `${width}px`, height: `${height}px`, "contain-intrinsic-size": `${width}px ${height}px`, }, accentColor ? { "--media-color-accent": accentColor } : {}, ); }; css` .attachments { column-count: ${columnCount().toString()}; } `; return (
{ if (e.target !== e.currentTarget) { e.stopImmediatePropagation(); } }} > {(item, index) => { switch (item().type) { case "image": return ( {item().description ); case "video": return (
); }; export default MediaAttachmentGrid;