feat: move average color to server side

This commit is contained in:
DIYgod 2023-06-08 12:57:54 +01:00
parent 8ef36c065b
commit aee2247313
No known key found for this signature in database
6 changed files with 129 additions and 74 deletions

View File

@ -73,7 +73,7 @@
"dayjs": "1.11.8",
"dotenv": "16.1.4",
"emoji-mart": "5.5.2",
"fast-average-color": "9.3.0",
"fast-average-color-node": "2.6.0",
"fast-deep-equal": "3.1.3",
"hast-util-from-html": "1.0.2",
"hast-util-to-html": "8.0.4",

View File

@ -140,9 +140,9 @@ dependencies:
emoji-mart:
specifier: 5.5.2
version: 5.5.2
fast-average-color:
specifier: 9.3.0
version: 9.3.0
fast-average-color-node:
specifier: 2.6.0
version: 2.6.0
fast-deep-equal:
specifier: 3.1.3
version: 3.1.3
@ -7979,6 +7979,17 @@ packages:
resolution: {integrity: sha512-oNvBekbhsm/0PNSOWca5raHNAi6dG960Bx6LJgxDPNF59WpuspgQ17bN5MKwOr7JcFdQYc7StW3VZ28DBZLavQ==}
dev: false
/fast-average-color-node@2.6.0:
resolution: {integrity: sha512-dn7ps7AhcsO8Gq4468aAeSVMtYWAGjTIjQ6J83+QueA6UYZ22YeKfIrfKjQiYAjBt+oBWskZ5e1fXtmsm24xMg==}
engines: {node: '>= 12'}
dependencies:
fast-average-color: 9.3.0
node-fetch: 2.6.11
sharp: 0.31.3
transitivePeerDependencies:
- encoding
dev: false
/fast-average-color@9.3.0:
resolution: {integrity: sha512-FlPROSqDMOnoBgkFhWMHJODPvpS0Od0WDpedcKq4U/t0JVapGAkblNwxOr75qT+ZNd0dQM4qlgqrtnXbCJ8cNg==}
engines: {node: '>= 12'}
@ -10609,6 +10620,10 @@ packages:
resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==}
dev: false
/node-addon-api@5.1.0:
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
dev: false
/node-addon-api@6.1.0:
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
dev: false
@ -12228,6 +12243,21 @@ packages:
safe-buffer: 5.2.1
dev: false
/sharp@0.31.3:
resolution: {integrity: sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==}
engines: {node: '>=14.15.0'}
requiresBuild: true
dependencies:
color: 4.2.3
detect-libc: 2.0.1
node-addon-api: 5.1.0
prebuild-install: 7.1.1
semver: 7.5.1
simple-get: 4.0.1
tar-fs: 2.1.1
tunnel-agent: 0.6.0
dev: false
/sharp@0.32.1:
resolution: {integrity: sha512-kQTFtj7ldpUqSe8kDxoGLZc1rnMFU0AO2pqbX6pLy3b7Oj8ivJIdoKNwxHVQG2HN6XpHPJqCSM2nsma2gOXvOg==}
engines: {node: '>=14.15.0'}

View File

@ -18,6 +18,7 @@ import { cn } from "~/lib/utils"
import { fetchGetPage } from "~/queries/page.server"
import {
fetchGetSite,
getCharacterColors,
prefetchGetSiteSubscriptions,
prefetchGetSiteToSubscriptions,
} from "~/queries/site.server"
@ -156,12 +157,34 @@ export default async function SiteLayout({
const dehydratedState = dehydrate(queryClient)
const colors = await getCharacterColors(site)
return (
<Hydrate state={dehydratedState}>
<div
className={`xlog-page xlog-page-${type} xlog-user xlog-deprecated-class`}
>
<Style content={site?.metadata?.content?.css} />
{colors?.light.averageColor && (
<style>
{`.light {
--auto-hover-color: ${colors.light.autoHoverColor};
--auto-theme-color: ${colors.light.autoThemeColor};
--auto-banner-bg-color: ${colors.light.averageColor};
}
.light .xlog-banner {
background-color: var(--banner-bg-color, ${colors.light.averageColor});
}
.dark {
--auto-hover-color: ${colors.dark.autoHoverColor};
--auto-theme-color: ${colors.dark.autoThemeColor};
--auto-banner-bg-color: ${colors.dark.averageColor};
}
.dark .xlog-banner {
background-color: var(--banner-bg-color, ${colors.dark.averageColor});
}`}
</style>
)}
{site && !onlyContent && <SiteHeader handle={params.site} />}
<main
className={cn(

View File

@ -5,7 +5,7 @@ export default function PostMeta({ cover }: { cover?: string }) {
return (
<>
<div className="xlog-post-cover hidden sm:flex items-center relative sm:w-28 sm:h-28 mt-2 sm:ml-4 sm:mt-0">
<div className="xlog-post-cover hidden sm:flex items-center relative w-28 h-28 ml-4 mt-0">
<Image
className="object-cover rounded"
alt="cover"

View File

@ -1,9 +1,7 @@
"use client"
import chroma from "chroma-js"
import { FastAverageColor } from "fast-average-color"
import { usePathname } from "next/navigation"
import { MutableRefObject, RefObject, useEffect, useRef, useState } from "react"
import { useState } from "react"
import { XCharLogo, XFeedLogo } from "@crossbell/ui"
import { RssIcon } from "@heroicons/react/24/solid"
@ -16,7 +14,6 @@ import { BlockchainIcon } from "~/components/icons/BlockchainIcon"
import { Image } from "~/components/ui/Image"
import { Modal } from "~/components/ui/Modal"
import { Tooltip } from "~/components/ui/Tooltip"
import { useIsDark } from "~/hooks/useDarkMode"
import { CSB_SCAN, CSB_XCHAR, CSB_XFEED } from "~/lib/env"
import { useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
@ -35,8 +32,6 @@ type HeaderLinkType = {
onClick?: () => void
}
const fac = new FastAverageColor()
const HeaderLink: React.FC<{ link: HeaderLinkType }> = ({ link }) => {
const pathname = usePathname()
const { t } = useTranslation("site")
@ -135,68 +130,9 @@ export const SiteHeader: React.FC<{
const [searchOpen, setSearchOpen] = useState(false)
const avatarRef = useRef<HTMLImageElement>(null)
const bannerRef = useRef<HTMLImageElement | HTMLVideoElement>(null)
const isDark = useIsDark()
const [averageColor, setAverageColor] = useState<string>()
const [autoHoverColor, setAutoHoverColor] = useState<string>()
const [autoThemeColor, setAutoThemeColor] = useState<string>()
useEffect(() => {
if (bannerRef?.current) {
fac
.getColorAsync(bannerRef.current)
.then((color) => {
if (isDark) {
setAverageColor(chroma(color.hex).luminance(0.007).hex())
setAutoHoverColor(chroma(color.hex).luminance(0.02).hex())
} else {
setAverageColor(chroma(color.hex).hex())
setAutoHoverColor(chroma(color.hex).luminance(0.8).hex())
}
setAutoThemeColor(chroma(color.hex).saturate(3).luminance(0.3).hex())
})
.catch((e) => {
console.warn(e)
})
} else if (avatarRef?.current) {
fac
.getColorAsync(avatarRef.current)
.then((color) => {
if (isDark) {
setAverageColor(chroma(color.hex).luminance(0.007).hex())
setAutoHoverColor(chroma(color.hex).luminance(0.02).hex())
} else {
setAverageColor(chroma(color.hex).luminance(0.95).hex())
setAutoHoverColor(chroma(color.hex).luminance(0.8).hex())
}
setAutoThemeColor(chroma(color.hex).saturate(3).luminance(0.3).hex())
})
.catch((e) => {
console.warn(e)
})
}
}, [bannerRef, avatarRef, isDark])
return (
<header className="xlog-header border-b border-zinc-100 relative">
{averageColor && (
<style jsx global>{`
:root {
--auto-hover-color: ${autoHoverColor};
--auto-theme-color: ${autoThemeColor};
}
`}</style>
)}
<div
className="xlog-banner absolute top-0 bottom-0 left-0 right-0 -z-10 overflow-hidden"
style={{
backgroundColor: averageColor
? `var(--banner-bg-color, ${averageColor})`
: "var(--banner-bg-color)",
}}
>
<div className="xlog-banner absolute top-0 bottom-0 left-0 right-0 -z-10 overflow-hidden">
{(() => {
switch (
site.data?.metadata?.content?.banners?.[0]?.mime_type?.split("/")[0]
@ -208,7 +144,6 @@ export const SiteHeader: React.FC<{
src={site.data?.metadata?.content?.banners?.[0]?.address}
alt="banner"
fill
imageRef={bannerRef as MutableRefObject<HTMLImageElement>}
/>
)
case "video":
@ -219,7 +154,6 @@ export const SiteHeader: React.FC<{
autoPlay
muted
playsInline
ref={bannerRef as RefObject<HTMLVideoElement>}
crossOrigin="anonymous"
/>
)
@ -245,7 +179,6 @@ export const SiteHeader: React.FC<{
images={site.data?.metadata?.content?.avatars}
size={120}
name={site.data?.metadata?.content?.name}
imageRef={avatarRef as MutableRefObject<HTMLImageElement>}
/>
)}
<div className="flex-1 min-w-0 relative">

View File

@ -1,9 +1,12 @@
import chroma from "chroma-js"
import { getAverageColor } from "fast-average-color-node"
import { Asset } from "unidata.js"
import { QueryClient } from "@tanstack/react-query"
import { IPFS_GATEWAY } from "~/lib/env"
import { cacheGet, getRedis } from "~/lib/redis.server"
import { ExpandedCharacter } from "~/lib/types"
import * as siteModel from "~/models/site.model"
export const prefetchGetSite = async (
@ -153,3 +156,69 @@ export const getNFTs = async (address?: string) => {
return result
}
}
export const getCharacterColors = async (character?: ExpandedCharacter) => {
const key = ["getThemeColors", character?.handle]
if (!character) {
return null
}
return cacheGet({
key,
getValueFun: async () => {
let colors = {
dark: {},
light: {},
}
if (
character.metadata?.content?.banners?.[0]?.mime_type?.split("/")[0] ===
"image"
) {
const color = await getAverageColor(
character.metadata.content.banners[0].address,
)
colors = {
dark: {
averageColor: chroma(color.hex).luminance(0.007).hex(),
autoHoverColor: chroma(color.hex).luminance(0.02).hex(),
autoThemeColor: chroma(color.hex).saturate(3).luminance(0.3).hex(),
},
light: {
averageColor: chroma(color.hex).hex(),
autoHoverColor: chroma(color.hex).luminance(0.8).hex(),
autoThemeColor: chroma(color.hex).saturate(3).luminance(0.3).hex(),
},
}
} else if (character.metadata?.content?.avatars?.[0]) {
const color = await getAverageColor(
character.metadata.content.avatars[0],
)
colors = {
dark: {
averageColor: chroma(color.hex).luminance(0.007).hex(),
autoHoverColor: chroma(color.hex).luminance(0.02).hex(),
autoThemeColor: chroma(color.hex).saturate(3).luminance(0.3).hex(),
},
light: {
averageColor: chroma(color.hex).luminance(0.95).hex(),
autoHoverColor: chroma(color.hex).luminance(0.8).hex(),
autoThemeColor: chroma(color.hex).saturate(3).luminance(0.3).hex(),
},
}
}
return colors
},
}) as Promise<{
dark: {
averageColor?: string
autoHoverColor?: string
autoThemeColor?: string
}
light: {
averageColor?: string
autoHoverColor?: string
autoThemeColor?: string
}
}>
}