RegularToot: added preview cards
This commit is contained in:
		
							parent
							
								
									bd1a515ef8
								
							
						
					
					
						commit
						df19e54d9b
					
				
					 5 changed files with 144 additions and 11 deletions
				
			
		| 
						 | 
				
			
			@ -18,7 +18,7 @@ const CompactToot: Component<CompactTootProps> = (props) => {
 | 
			
		|||
  const toot = () => props.status;
 | 
			
		||||
  return (
 | 
			
		||||
    <section
 | 
			
		||||
      class={[tootStyle.compact, props.class || ""].join(" ")}
 | 
			
		||||
      class={[tootStyle.toot, tootStyle.compact, props.class || ""].join(" ")}
 | 
			
		||||
      lang={toot().language || undefined}
 | 
			
		||||
    >
 | 
			
		||||
      <Img
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,11 +5,19 @@ import {
 | 
			
		|||
  type JSX,
 | 
			
		||||
  Show,
 | 
			
		||||
  createRenderEffect,
 | 
			
		||||
  createSignal,
 | 
			
		||||
  createEffect,
 | 
			
		||||
} from "solid-js";
 | 
			
		||||
import tootStyle from "./toot.module.css";
 | 
			
		||||
import { formatRelative } from "date-fns";
 | 
			
		||||
import Img from "../material/Img.js";
 | 
			
		||||
import { Body2 } from "../material/typography.js";
 | 
			
		||||
import {
 | 
			
		||||
  Body1,
 | 
			
		||||
  Body2,
 | 
			
		||||
  Caption,
 | 
			
		||||
  Subheading,
 | 
			
		||||
  Title,
 | 
			
		||||
} from "../material/typography.js";
 | 
			
		||||
import { css } from "solid-styled";
 | 
			
		||||
import {
 | 
			
		||||
  BookmarkAddOutlined,
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +34,8 @@ import { Divider } from "@suid/material";
 | 
			
		|||
import cardStyle from "../material/cards.module.css";
 | 
			
		||||
import Button from "../material/Button.js";
 | 
			
		||||
import MediaAttachmentGrid from "./MediaAttachmentGrid.js";
 | 
			
		||||
import { FastAverageColor } from "fast-average-color";
 | 
			
		||||
import Color from "colorjs.io";
 | 
			
		||||
 | 
			
		||||
type TootContentViewProps = {
 | 
			
		||||
  source?: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -187,6 +197,67 @@ function TootAuthorGroup(props: { status: mastodon.v1.Status; now: Date }) {
 | 
			
		|||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function TootPreviewCard(props: { src: mastodon.v1.PreviewCard }) {
 | 
			
		||||
  let root: HTMLAnchorElement;
 | 
			
		||||
 | 
			
		||||
  createEffect(() => {
 | 
			
		||||
    if (!props.src.width) return;
 | 
			
		||||
    const width = root.getBoundingClientRect().width;
 | 
			
		||||
    if (width > props.src.width) {
 | 
			
		||||
      root.classList.add(tootStyle.compact);
 | 
			
		||||
    } else {
 | 
			
		||||
      root.classList.remove(tootStyle.compact);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const onImgLoad = (event: Event & { currentTarget: HTMLImageElement }) => {
 | 
			
		||||
    // TODO: better extraction algorithm
 | 
			
		||||
    // I'd like to use a pattern panel and match one in the panel from the extracted color
 | 
			
		||||
    const fac = new FastAverageColor();
 | 
			
		||||
    const result = fac.getColor(event.currentTarget);
 | 
			
		||||
    if (result.error) {
 | 
			
		||||
      console.error(result.error);
 | 
			
		||||
      fac.destroy();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    root.style.setProperty("--tutu-color-surface", result.hex);
 | 
			
		||||
    const focusSurface = result.isDark
 | 
			
		||||
      ? new Color(result.hex).darken(0.2)
 | 
			
		||||
      : new Color(result.hex).lighten(0.2);
 | 
			
		||||
    root.style.setProperty("--tutu-color-surface-d", focusSurface.toString());
 | 
			
		||||
    const textColor = result.isDark ? "white" : "black";
 | 
			
		||||
    const secondaryTextColor = new Color(textColor);
 | 
			
		||||
    secondaryTextColor.alpha = 0.75;
 | 
			
		||||
    root.style.setProperty("--tutu-color-on-surface", textColor);
 | 
			
		||||
    root.style.setProperty(
 | 
			
		||||
      "--tutu-color-secondary-text-on-surface",
 | 
			
		||||
      secondaryTextColor.toString(),
 | 
			
		||||
    );
 | 
			
		||||
    fac.destroy();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <a
 | 
			
		||||
      ref={root!}
 | 
			
		||||
      class={tootStyle.previewCard}
 | 
			
		||||
      href={props.src.url}
 | 
			
		||||
      target="_blank"
 | 
			
		||||
      referrerPolicy="unsafe-url"
 | 
			
		||||
    >
 | 
			
		||||
      <Show when={props.src.image}>
 | 
			
		||||
        <img
 | 
			
		||||
          crossOrigin="anonymous"
 | 
			
		||||
          src={props.src.image!}
 | 
			
		||||
          onLoad={onImgLoad}
 | 
			
		||||
          loading="lazy"
 | 
			
		||||
        />
 | 
			
		||||
      </Show>
 | 
			
		||||
      <Title component="h1">{props.src.title}</Title>
 | 
			
		||||
      <Body1 component="p">{props.src.description}</Body1>
 | 
			
		||||
    </a>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const RegularToot: Component<TootCardProps> = (props) => {
 | 
			
		||||
  let rootRef: HTMLElement;
 | 
			
		||||
  const [managed, managedActionGroup, rest] = splitProps(
 | 
			
		||||
| 
						 | 
				
			
			@ -241,6 +312,9 @@ const RegularToot: Component<TootCardProps> = (props) => {
 | 
			
		|||
          emojis={toot().emojis}
 | 
			
		||||
          class={tootStyle.tootContent}
 | 
			
		||||
        />
 | 
			
		||||
        <Show when={toot().card}>
 | 
			
		||||
          <TootPreviewCard src={toot().card!} />
 | 
			
		||||
        </Show>
 | 
			
		||||
        <Show when={toot().mediaAttachments.length > 0}>
 | 
			
		||||
          <MediaAttachmentGrid attachments={toot().mediaAttachments} />
 | 
			
		||||
        </Show>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@
 | 
			
		|||
    border-radius: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & > .toot {
 | 
			
		||||
  &>.toot {
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +47,7 @@
 | 
			
		|||
  display: grid;
 | 
			
		||||
  grid-template-columns: 1fr auto;
 | 
			
		||||
 | 
			
		||||
  > * {
 | 
			
		||||
  >* {
 | 
			
		||||
    color: var(--tutu-color-secondary-text-on-surface);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +55,7 @@
 | 
			
		|||
    grid-column: 1 /3;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  > time {
 | 
			
		||||
  >time {
 | 
			
		||||
    text-align: end;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +104,64 @@
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.compact {
 | 
			
		||||
.previewCard {
 | 
			
		||||
  composes: cardGutSkip from "../material/cards.module.css";
 | 
			
		||||
  display: block;
 | 
			
		||||
  border: 1px solid #eeeeee;
 | 
			
		||||
  background-color: var(--tutu-color-surface);
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  margin-top: 1em;
 | 
			
		||||
  margin-bottom: 1.5em;
 | 
			
		||||
  color: var(--tutu-color-secondary-text-on-surface);
 | 
			
		||||
  transition: color 220ms var(--tutu-anim-curve-std), background-color 220ms var(--tutu-anim-curve-std);
 | 
			
		||||
  padding-bottom: 8px;
 | 
			
		||||
 | 
			
		||||
  >img {
 | 
			
		||||
    max-width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:hover,
 | 
			
		||||
  &:focus-visible {
 | 
			
		||||
    background-color: var(--tutu-color-surface-d);
 | 
			
		||||
    color: var(--tutu-color-on-surface);
 | 
			
		||||
 | 
			
		||||
    >h1 {
 | 
			
		||||
      text-decoration: underline;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  >h1 {
 | 
			
		||||
    color: var(--tutu-color-on-surface);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  >h1,
 | 
			
		||||
  >p {
 | 
			
		||||
    margin-left: 16px;
 | 
			
		||||
    margin-right: 16px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.compact {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: minmax(10%, 30%) 1fr;
 | 
			
		||||
    padding-left: 16px;
 | 
			
		||||
    padding-right: 16px;
 | 
			
		||||
 | 
			
		||||
    >img:first-child {
 | 
			
		||||
      grid-row: 1 / 3;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      object-fit: contain;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    >h1,
 | 
			
		||||
    >p {
 | 
			
		||||
      margin-right: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.toot.compact {
 | 
			
		||||
  display: grid;
 | 
			
		||||
  grid-template-columns: auto 1fr;
 | 
			
		||||
  gap: 8px;
 | 
			
		||||
| 
						 | 
				
			
			@ -127,12 +184,12 @@
 | 
			
		|||
  align-items: center;
 | 
			
		||||
  margin-bottom: 8px;
 | 
			
		||||
 | 
			
		||||
  > .compactAuthorUsername {
 | 
			
		||||
  >.compactAuthorUsername {
 | 
			
		||||
    color: var(--tutu-color-secondary-text-on-surface);
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  > time {
 | 
			
		||||
  >time {
 | 
			
		||||
    color: var(--tutu-color-secondary-text-on-surface);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -177,11 +234,11 @@
 | 
			
		|||
  flex-flow: row wrap;
 | 
			
		||||
  justify-content: space-evenly;
 | 
			
		||||
 | 
			
		||||
  > button {
 | 
			
		||||
  >button {
 | 
			
		||||
    color: var(--tutu-color-on-surface);
 | 
			
		||||
    padding: 10px 8px;
 | 
			
		||||
 | 
			
		||||
    > svg {
 | 
			
		||||
    >svg {
 | 
			
		||||
      font-size: 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -207,4 +264,4 @@
 | 
			
		|||
  100% {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue