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;
 | 
			
		||||
  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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue