diff --git a/bun.lockb b/bun.lockb index 8acbc1a..bab2499 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 70973f6..6e1fdb9 100644 --- a/package.json +++ b/package.json @@ -17,16 +17,16 @@ "@suid/vite-plugin": "^0.3.1", "@types/hammerjs": "^2.0.46", "@vite-pwa/assets-generator": "^0.2.6", - "postcss": "^8.4.49", + "postcss": "^8.4.47", "prettier": "^3.3.3", "typescript": "^5.6.3", - "vite": "^5.4.11", + "vite": "^5.4.10", "vite-plugin-package-version": "^1.1.0", "vite-plugin-pwa": "^0.20.5", "vite-plugin-solid": "^2.10.2", "vite-plugin-solid-styled": "^0.11.1", "workbox-build": "^7.3.0", - "wrangler": "^3.86.1" + "wrangler": "^3.84.1" }, "dependencies": { "@formatjs/intl-localematcher": "^0.5.7", @@ -38,7 +38,7 @@ "@solid-primitives/map": "^0.4.13", "@solid-primitives/page-visibility": "^2.0.17", "@solid-primitives/resize-observer": "^2.0.26", - "@solidjs/router": "^0.15.1", + "@solidjs/router": "^0.14.10", "@suid/icons-material": "^0.8.1", "@suid/material": "^0.18.0", "blurhash": "^2.0.5", diff --git a/src/timelines/MediaAttachmentGrid.css b/src/timelines/MediaAttachmentGrid.css deleted file mode 100644 index 6b659c4..0000000 --- a/src/timelines/MediaAttachmentGrid.css +++ /dev/null @@ -1,27 +0,0 @@ -.MediaAttachmentGrid { - /* Note: MeidaAttachmentGrid has hard-coded layout calcalation */ - margin-top: 1em; - margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px); - margin-right: var(--card-pad, 0); - gap: 4px; - - > :where(img, video) { - max-height: 35vh; - min-height: 40px; - min-width: 40px; - object-fit: contain; - max-width: 100%; - background-color: 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 { - outline: 8px solid var(--media-color-accent, var(--tutu-color-surface-d)); - border-color: var(--media-color-accent, var(--tutu-color-surface-d)); - } - } -} \ No newline at end of file diff --git a/src/timelines/MediaAttachmentGrid.tsx b/src/timelines/MediaAttachmentGrid.tsx index 2b9db57..3f7147c 100644 --- a/src/timelines/MediaAttachmentGrid.tsx +++ b/src/timelines/MediaAttachmentGrid.tsx @@ -10,6 +10,8 @@ import { 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 { @@ -19,8 +21,6 @@ import { import { useStore } from "@nanostores/solid"; import { $settings } from "../settings/stores"; import { averageColorHex } from "../platform/blurhash"; -import "./MediaAttachmentGrid.css"; -import cardStyle from "../material/cards.module.css"; type ElementSize = { width: number; height: number }; @@ -114,6 +114,13 @@ const MediaAttachmentGrid: Component<{ 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 ?? (item.blurhash ? averageColorHex(item.blurhash) : undefined); @@ -127,11 +134,16 @@ const MediaAttachmentGrid: Component<{ accentColor ? { "--media-color-accent": accentColor } : {}, ); }; + + css` + .attachments { + column-count: ${columnCount().toString()}; + } + `; return (
{ if (e.target !== e.currentTarget) { e.stopImmediatePropagation(); diff --git a/src/timelines/RegularToot.tsx b/src/timelines/RegularToot.tsx index f72e4fc..ffcae6a 100644 --- a/src/timelines/RegularToot.tsx +++ b/src/timelines/RegularToot.tsx @@ -5,11 +5,13 @@ import { type JSX, Show, createRenderEffect, + createEffect, + createMemo, } 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, Title } from "../material/typography.js"; import { css } from "solid-styled"; import { BookmarkAddOutlined, @@ -28,12 +30,13 @@ import { Divider } from "@suid/material"; import cardStyle from "../material/cards.module.css"; import Button from "../material/Button.js"; import MediaAttachmentGrid from "./MediaAttachmentGrid.js"; +import Color from "colorjs.io"; import { useDateFnLocale } from "../platform/i18n"; import { canShare, share } from "../platform/share"; import { makeAcctText, useDefaultSession } from "../masto/clients"; import TootContent from "./toot-components/TootContent"; import BoostIcon from "./toot-components/BoostIcon"; -import PreviewCard from "./toot-components/PreviewCard"; +import { averageColorHex } from "../platform/blurhash"; type TootActionGroupProps = { onRetoot?: (value: T) => void; @@ -183,6 +186,95 @@ function TootAuthorGroup( ); } +export function TootPreviewCard(props: { + src: mastodon.v1.PreviewCard; + alwaysCompact?: boolean; +}) { + let root: HTMLAnchorElement; + + createEffect(() => { + if (props.alwaysCompact) { + root.classList.add(tootStyle.compact); + return; + } + 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 imgAverageColor = createMemo(() => { + if (!props.src.image) return; + return new Color(averageColorHex(props.src.blurhash)); + }); + + const prefersWhiteText = createMemo(() => { + const oc = imgAverageColor(); + if (!oc) return; + const colorWhite = new Color("white"); + + return colorWhite.luminance / oc.luminance > 3.5; + }); + + const focusSurfaceColor = createMemo(() => { + const oc = imgAverageColor(); + if (!oc) return; + if (prefersWhiteText()) { + return new Color(oc).darken(0.2); + } else { + return new Color(oc).lighten(0.2); + } + }); + + const textColorName = createMemo(() => { + const useWhiteText = prefersWhiteText(); + if (typeof useWhiteText === "undefined") { + return; + } + return useWhiteText ? "white" : "black"; + }); + + const secondaryTextColor = createMemo(() => { + const tcn = textColorName(); + if (!tcn) return; + const tc = new Color(tcn); + tc.alpha = 0.75; + return tc; + }); + + return ( + + + + + {props.src.title} + {props.src.description} + + ); +} + /** * find bottom-to-top the element with `data-action`. */ @@ -328,7 +420,7 @@ const RegularToot: Component = (props) => { class={tootStyle.tootContent} /> - + 0}> diff --git a/src/timelines/toot-components/PreviewCard.css b/src/timelines/toot-components/PreviewCard.css deleted file mode 100644 index 17d9f2b..0000000 --- a/src/timelines/toot-components/PreviewCard.css +++ /dev/null @@ -1,67 +0,0 @@ -.PreviewCard { - 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; - overflow: hidden; - z-index: 1; - position: relative; - - >img { - background-color: #eeeeee; - max-width: 100%; - height: auto; - } - - &: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); - max-height: calc(4 * var(--title-line-height) * var(--title-size)); - } - - >p { - max-height: calc(8 * var(--body-line-height) * var(--body-size)); - } - - >h1, - >p { - margin-left: 16px; - margin-right: 16px; - overflow: hidden; - text-overflow: ellipsis; - } - - &.compact { - display: grid; - grid-template-columns: minmax(10%, 30%) 1fr; - padding-left: 16px; - padding-right: 16px; - padding-top: 8px; - - >img:first-child { - grid-row: 1 / 3; - object-fit: contain; - } - - >h1, - >p { - margin-right: 0; - } - } -} diff --git a/src/timelines/toot-components/PreviewCard.tsx b/src/timelines/toot-components/PreviewCard.tsx deleted file mode 100644 index ed20eb8..0000000 --- a/src/timelines/toot-components/PreviewCard.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import Color from "colorjs.io"; -import type { mastodon } from "masto"; -import { createEffect, createMemo, Show } from "solid-js"; -import { Title, Body1 } from "../../material/typography"; -import { averageColorHex } from "../../platform/blurhash"; -import "./PreviewCard.css"; - -export function PreviewCard(props: { - src: mastodon.v1.PreviewCard; - alwaysCompact?: boolean; -}) { - let root: HTMLAnchorElement; - - createEffect(() => { - if (props.alwaysCompact) { - root.classList.add("compact"); - return; - } - if (!props.src.width) return; - const width = root.getBoundingClientRect().width; - if (width > props.src.width) { - root.classList.add("compact"); - } else { - root.classList.remove("compact"); - } - }); - - const imgAverageColor = createMemo(() => { - if (!props.src.image) return; - return new Color(averageColorHex(props.src.blurhash)); - }); - - const prefersWhiteText = createMemo(() => { - const oc = imgAverageColor(); - if (!oc) return; - const colorWhite = new Color("white"); - - return colorWhite.luminance / oc.luminance > 3.5; - }); - - const focusSurfaceColor = createMemo(() => { - const oc = imgAverageColor(); - if (!oc) return; - if (prefersWhiteText()) { - return new Color(oc).darken(0.2); - } else { - return new Color(oc).lighten(0.2); - } - }); - - const textColorName = createMemo(() => { - const useWhiteText = prefersWhiteText(); - if (typeof useWhiteText === "undefined") { - return; - } - return useWhiteText ? "white" : "black"; - }); - - const secondaryTextColor = createMemo(() => { - const tcn = textColorName(); - if (!tcn) return; - const tc = new Color(tcn); - tc.alpha = 0.75; - return tc; - }); - - return ( - - - - - {props.src.title} - {props.src.description} - - ); -} - -export default PreviewCard; diff --git a/src/timelines/toot.module.css b/src/timelines/toot.module.css index ecd74a1..cb3c70d 100644 --- a/src/timelines/toot.module.css +++ b/src/timelines/toot.module.css @@ -238,6 +238,35 @@ } } +.tootAttachmentGrp { + /* Note: MeidaAttachmentGrid has hard-coded layout calcalation */ + composes: cardNoPad from "../material/cards.module.css"; + margin-top: 1em; + margin-left: calc(var(--card-pad, 0) + var(--toot-avatar-size, 0) + 8px); + margin-right: var(--card-pad, 0); + gap: 4px; + + > :where(img, video) { + max-height: 35vh; + min-height: 40px; + min-width: 40px; + object-fit: contain; + max-width: 100%; + background-color: 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 { + outline: 8px solid var(--media-color-accent, var(--tutu-color-surface-d)); + border: none; + } + } +} + .tootBottomActionGrp { composes: cardGutSkip from "../material/cards.module.css"; padding-block: calc((var(--card-gut) - 10px) / 2);