MediaAttachmentGrid: add a box to each item

This commit is contained in:
thislight 2024-11-20 20:34:30 +08:00
parent b1f6033cc8
commit 1047a3b10d
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E
3 changed files with 82 additions and 70 deletions

View file

@ -6,18 +6,27 @@
gap: 4px;
contain: layout style;
> :where(img, video) {
&.sensitive>.cell> :where(img, video) {
filter: blur(20px) saturate(0.2);
}
>.cell> :where(img, video) {
object-fit: contain;
width: 100%;
height: 100%;
}
>.cell {
max-height: 35vh;
min-height: 40px;
min-width: 40px;
object-fit: contain;
max-width: 100%;
contain: strict;
content-visibility: auto;
background-color: var(--media-color-accent, var(--tutu-color-surface-d));
border-radius: 2px;
border: 1px solid var(--tutu-color-surface-d);
transition: outline-width 60ms var(--tutu-anim-curve-std), border-color 60ms var(--tutu-anim-curve-std);
contain: strict;
content-visibility: auto;
&:hover,
&:focus-visible {

View file

@ -1,7 +1,6 @@
import type { mastodon } from "masto";
import {
type Component,
For,
Index,
Match,
Switch,
@ -44,8 +43,15 @@ function constraintedSize(
};
}
function isolateCallback(event: Event) {
if (event.target !== event.currentTarget) {
event.stopPropagation();
}
}
const MediaAttachmentGrid: Component<{
attachments: mastodon.v1.MediaAttachment[];
sensitive?: boolean;
}> = (props) => {
const [rootRef, setRootRef] = createSignal<HTMLElement>();
const [viewerIndex, setViewerIndex] = createSignal<number>();
@ -53,14 +59,13 @@ const MediaAttachmentGrid: Component<{
const settings = useStore($settings);
const windowSize = useWindowSize();
createRenderEffect((lastDispose?: () => void) => {
lastDispose?.();
createRenderEffect(() => {
const vidx = viewerIndex();
if (typeof vidx === "undefined") return;
const container = document.createElement("div");
container.setAttribute("role", "presentation");
document.body.appendChild(container);
return render(() => {
const dispose = render(() => {
onCleanup(() => {
document.body.removeChild(container);
});
@ -75,6 +80,8 @@ const MediaAttachmentGrid: Component<{
/>
);
}, container);
onCleanup(dispose);
});
const openViewerFor = (index: number) => {
@ -131,69 +138,62 @@ const MediaAttachmentGrid: Component<{
<section
ref={setRootRef}
class={`MediaAttachmentGrid ${cardStyle.cardNoPad}`}
style={{ "column-count": columnCount() }}
onClick={(e) => {
if (e.target !== e.currentTarget) {
e.stopImmediatePropagation();
}
classList={{
sensitive: props.sensitive,
}}
style={{ "column-count": columnCount() }}
onClick={isolateCallback}
>
<Index each={props.attachments}>
{(item, index) => {
const itemType = () => item().type;
return (
<Switch>
<Match when={itemType() === "image"}>
<img
data-sort={index}
data-media-type={item().type}
src={item().previewUrl}
width={item().meta?.small?.width}
height={item().meta?.small?.height}
alt={item().description || undefined}
onClick={[openViewerFor, index]}
loading="lazy"
style={itemStyle(item())}
></img>
</Match>
<Match when={itemType() === "video"}>
<video
data-sort={index}
data-media-type={item().type}
src={item().url || undefined}
autoplay={settings().autoPlayVideos}
playsinline={settings().autoPlayVideos ? true : undefined}
controls
poster={item().previewUrl}
width={item().meta?.small?.width}
height={item().meta?.small?.height}
style={itemStyle(item())}
/>
</Match>
<Match when={itemType() === "gifv"}>
<video
data-sort={index}
data-media-type={item().type}
src={item().url || undefined}
autoplay={settings().autoPlayGIFs}
controls
playsinline /* or safari on iOS will play in full-screen */
loop
poster={item().previewUrl}
width={item().meta?.small?.width}
height={item().meta?.small?.height}
style={itemStyle(item())}
/>
</Match>
<Match when={itemType() === "audio"}>
<audio
data-sort={index}
data-media-type={item().type}
src={item().url || undefined}
controls
></audio>
</Match>
</Switch>
<div
class="cell"
role="presentation"
style={itemStyle(item())}
data-sort={index}
data-media-type={item().type}
>
<Switch>
<Match when={itemType() === "image"}>
<img
src={item().previewUrl}
width={item().meta?.small?.width}
height={item().meta?.small?.height}
alt={item().description || undefined}
onClick={[openViewerFor, index]}
loading="lazy"
></img>
</Match>
<Match when={itemType() === "video"}>
<video
src={item().url || undefined}
autoplay={!props.sensitive && settings().autoPlayVideos}
playsinline={settings().autoPlayVideos ? true : undefined}
controls
poster={item().previewUrl}
width={item().meta?.small?.width}
height={item().meta?.small?.height}
/>
</Match>
<Match when={itemType() === "gifv"}>
<video
src={item().url || undefined}
autoplay={!props.sensitive && settings().autoPlayGIFs}
controls
playsinline /* or safari on iOS will play in full-screen */
loop
poster={item().previewUrl}
width={item().meta?.small?.width}
height={item().meta?.small?.height}
/>
</Match>
<Match when={itemType() === "audio"}>
<audio src={item().url || undefined} controls></audio>
</Match>
</Switch>
</div>
);
}}
</Index>

View file

@ -47,7 +47,7 @@ type TootActionGroupProps<T extends mastodon.v1.Status> = {
) => void;
};
type TootCardProps = {
type RegularTootProps = {
status: mastodon.v1.Status;
actionable?: boolean;
evaluated?: boolean;
@ -235,7 +235,7 @@ function onToggleReveal(setValue: Setter<boolean>, event: Event) {
* You can extract the intent from the attributes of the "actionable" element.
* The action type is the dataset's `action`.
*/
const RegularToot: Component<TootCardProps> = (props) => {
const RegularToot: Component<RegularTootProps> = (props) => {
let rootRef: HTMLElement;
const [managed, managedActionGroup, rest] = splitProps(
props,
@ -293,7 +293,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
return (
<>
<section
<article
classList={{
[tootStyle.toot]: true,
[tootStyle.expanded]: managed.evaluated,
@ -343,7 +343,10 @@ const RegularToot: Component<TootCardProps> = (props) => {
<PreviewCard src={toot().card!} />
</Show>
<Show when={toot().mediaAttachments.length > 0}>
<MediaAttachmentGrid attachments={toot().mediaAttachments} />
<MediaAttachmentGrid
attachments={toot().mediaAttachments}
sensitive={toot().sensitive}
/>
</Show>
<Show when={managed.actionable}>
<Divider
@ -352,7 +355,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
/>
<TootActionGroup value={toot()} {...managedActionGroup} />
</Show>
</section>
</article>
</>
);
};