feat: move average color to server side
This commit is contained in:
parent
8ef36c065b
commit
aee2247313
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue