MediaAttachmentGrid: add a box to each item
This commit is contained in:
parent
b1f6033cc8
commit
1047a3b10d
3 changed files with 82 additions and 70 deletions
|
@ -6,18 +6,27 @@
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
contain: layout style;
|
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;
|
max-height: 35vh;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
object-fit: contain;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
contain: strict;
|
||||||
|
content-visibility: auto;
|
||||||
background-color: var(--media-color-accent, var(--tutu-color-surface-d));
|
background-color: var(--media-color-accent, var(--tutu-color-surface-d));
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid var(--tutu-color-surface-d);
|
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);
|
transition: outline-width 60ms var(--tutu-anim-curve-std), border-color 60ms var(--tutu-anim-curve-std);
|
||||||
contain: strict;
|
|
||||||
content-visibility: auto;
|
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import type { mastodon } from "masto";
|
import type { mastodon } from "masto";
|
||||||
import {
|
import {
|
||||||
type Component,
|
type Component,
|
||||||
For,
|
|
||||||
Index,
|
Index,
|
||||||
Match,
|
Match,
|
||||||
Switch,
|
Switch,
|
||||||
|
@ -44,8 +43,15 @@ function constraintedSize(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isolateCallback(event: Event) {
|
||||||
|
if (event.target !== event.currentTarget) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const MediaAttachmentGrid: Component<{
|
const MediaAttachmentGrid: Component<{
|
||||||
attachments: mastodon.v1.MediaAttachment[];
|
attachments: mastodon.v1.MediaAttachment[];
|
||||||
|
sensitive?: boolean;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const [rootRef, setRootRef] = createSignal<HTMLElement>();
|
const [rootRef, setRootRef] = createSignal<HTMLElement>();
|
||||||
const [viewerIndex, setViewerIndex] = createSignal<number>();
|
const [viewerIndex, setViewerIndex] = createSignal<number>();
|
||||||
|
@ -53,14 +59,13 @@ const MediaAttachmentGrid: Component<{
|
||||||
const settings = useStore($settings);
|
const settings = useStore($settings);
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
|
|
||||||
createRenderEffect((lastDispose?: () => void) => {
|
createRenderEffect(() => {
|
||||||
lastDispose?.();
|
|
||||||
const vidx = viewerIndex();
|
const vidx = viewerIndex();
|
||||||
if (typeof vidx === "undefined") return;
|
if (typeof vidx === "undefined") return;
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("div");
|
||||||
container.setAttribute("role", "presentation");
|
container.setAttribute("role", "presentation");
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
return render(() => {
|
const dispose = render(() => {
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container);
|
||||||
});
|
});
|
||||||
|
@ -75,6 +80,8 @@ const MediaAttachmentGrid: Component<{
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, container);
|
}, container);
|
||||||
|
|
||||||
|
onCleanup(dispose);
|
||||||
});
|
});
|
||||||
|
|
||||||
const openViewerFor = (index: number) => {
|
const openViewerFor = (index: number) => {
|
||||||
|
@ -131,69 +138,62 @@ const MediaAttachmentGrid: Component<{
|
||||||
<section
|
<section
|
||||||
ref={setRootRef}
|
ref={setRootRef}
|
||||||
class={`MediaAttachmentGrid ${cardStyle.cardNoPad}`}
|
class={`MediaAttachmentGrid ${cardStyle.cardNoPad}`}
|
||||||
style={{ "column-count": columnCount() }}
|
classList={{
|
||||||
onClick={(e) => {
|
sensitive: props.sensitive,
|
||||||
if (e.target !== e.currentTarget) {
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
|
style={{ "column-count": columnCount() }}
|
||||||
|
onClick={isolateCallback}
|
||||||
>
|
>
|
||||||
<Index each={props.attachments}>
|
<Index each={props.attachments}>
|
||||||
{(item, index) => {
|
{(item, index) => {
|
||||||
const itemType = () => item().type;
|
const itemType = () => item().type;
|
||||||
return (
|
return (
|
||||||
|
<div
|
||||||
|
class="cell"
|
||||||
|
role="presentation"
|
||||||
|
style={itemStyle(item())}
|
||||||
|
data-sort={index}
|
||||||
|
data-media-type={item().type}
|
||||||
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={itemType() === "image"}>
|
<Match when={itemType() === "image"}>
|
||||||
<img
|
<img
|
||||||
data-sort={index}
|
|
||||||
data-media-type={item().type}
|
|
||||||
src={item().previewUrl}
|
src={item().previewUrl}
|
||||||
width={item().meta?.small?.width}
|
width={item().meta?.small?.width}
|
||||||
height={item().meta?.small?.height}
|
height={item().meta?.small?.height}
|
||||||
alt={item().description || undefined}
|
alt={item().description || undefined}
|
||||||
onClick={[openViewerFor, index]}
|
onClick={[openViewerFor, index]}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
style={itemStyle(item())}
|
|
||||||
></img>
|
></img>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={itemType() === "video"}>
|
<Match when={itemType() === "video"}>
|
||||||
<video
|
<video
|
||||||
data-sort={index}
|
|
||||||
data-media-type={item().type}
|
|
||||||
src={item().url || undefined}
|
src={item().url || undefined}
|
||||||
autoplay={settings().autoPlayVideos}
|
autoplay={!props.sensitive && settings().autoPlayVideos}
|
||||||
playsinline={settings().autoPlayVideos ? true : undefined}
|
playsinline={settings().autoPlayVideos ? true : undefined}
|
||||||
controls
|
controls
|
||||||
poster={item().previewUrl}
|
poster={item().previewUrl}
|
||||||
width={item().meta?.small?.width}
|
width={item().meta?.small?.width}
|
||||||
height={item().meta?.small?.height}
|
height={item().meta?.small?.height}
|
||||||
style={itemStyle(item())}
|
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={itemType() === "gifv"}>
|
<Match when={itemType() === "gifv"}>
|
||||||
<video
|
<video
|
||||||
data-sort={index}
|
|
||||||
data-media-type={item().type}
|
|
||||||
src={item().url || undefined}
|
src={item().url || undefined}
|
||||||
autoplay={settings().autoPlayGIFs}
|
autoplay={!props.sensitive && settings().autoPlayGIFs}
|
||||||
controls
|
controls
|
||||||
playsinline /* or safari on iOS will play in full-screen */
|
playsinline /* or safari on iOS will play in full-screen */
|
||||||
loop
|
loop
|
||||||
poster={item().previewUrl}
|
poster={item().previewUrl}
|
||||||
width={item().meta?.small?.width}
|
width={item().meta?.small?.width}
|
||||||
height={item().meta?.small?.height}
|
height={item().meta?.small?.height}
|
||||||
style={itemStyle(item())}
|
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={itemType() === "audio"}>
|
<Match when={itemType() === "audio"}>
|
||||||
<audio
|
<audio src={item().url || undefined} controls></audio>
|
||||||
data-sort={index}
|
|
||||||
data-media-type={item().type}
|
|
||||||
src={item().url || undefined}
|
|
||||||
controls
|
|
||||||
></audio>
|
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Index>
|
</Index>
|
||||||
|
|
|
@ -47,7 +47,7 @@ type TootActionGroupProps<T extends mastodon.v1.Status> = {
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TootCardProps = {
|
type RegularTootProps = {
|
||||||
status: mastodon.v1.Status;
|
status: mastodon.v1.Status;
|
||||||
actionable?: boolean;
|
actionable?: boolean;
|
||||||
evaluated?: 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.
|
* You can extract the intent from the attributes of the "actionable" element.
|
||||||
* The action type is the dataset's `action`.
|
* The action type is the dataset's `action`.
|
||||||
*/
|
*/
|
||||||
const RegularToot: Component<TootCardProps> = (props) => {
|
const RegularToot: Component<RegularTootProps> = (props) => {
|
||||||
let rootRef: HTMLElement;
|
let rootRef: HTMLElement;
|
||||||
const [managed, managedActionGroup, rest] = splitProps(
|
const [managed, managedActionGroup, rest] = splitProps(
|
||||||
props,
|
props,
|
||||||
|
@ -293,7 +293,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section
|
<article
|
||||||
classList={{
|
classList={{
|
||||||
[tootStyle.toot]: true,
|
[tootStyle.toot]: true,
|
||||||
[tootStyle.expanded]: managed.evaluated,
|
[tootStyle.expanded]: managed.evaluated,
|
||||||
|
@ -343,7 +343,10 @@ const RegularToot: Component<TootCardProps> = (props) => {
|
||||||
<PreviewCard 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}
|
||||||
|
sensitive={toot().sensitive}
|
||||||
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={managed.actionable}>
|
<Show when={managed.actionable}>
|
||||||
<Divider
|
<Divider
|
||||||
|
@ -352,7 +355,7 @@ const RegularToot: Component<TootCardProps> = (props) => {
|
||||||
/>
|
/>
|
||||||
<TootActionGroup value={toot()} {...managedActionGroup} />
|
<TootActionGroup value={toot()} {...managedActionGroup} />
|
||||||
</Show>
|
</Show>
|
||||||
</section>
|
</article>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue