a tool for shared writing and social publishing
at feature/profiles 210 lines 6.5 kB view raw
1"use client"; 2import { useMemo, useState } from "react"; 3import { parseColor } from "react-aria-components"; 4import { useEntity } from "src/replicache"; 5import { getColorContrast } from "./themeUtils"; 6import { useColorAttribute, colorToString } from "./useColorAttribute"; 7import { BaseThemeProvider, CardBorderHiddenContext } from "./ThemeProvider"; 8import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api"; 9import { usePublicationData } from "app/lish/[did]/[publication]/dashboard/PublicationSWRProvider"; 10import { blobRefToSrc } from "src/utils/blobRefToSrc"; 11 12const PubThemeDefaults = { 13 backgroundColor: "#FDFCFA", 14 pageBackground: "#FDFCFA", 15 primary: "#272727", 16 accentText: "#FFFFFF", 17 accentBackground: "#0000FF", 18}; 19 20// Default page background for standalone leaflets (matches editor default) 21const StandalonePageBackground = "#FFFFFF"; 22function parseThemeColor( 23 c: PubLeafletThemeColor.Rgb | PubLeafletThemeColor.Rgba, 24) { 25 if (c.$type === "pub.leaflet.theme.color#rgba") { 26 return parseColor(`rgba(${c.r}, ${c.g}, ${c.b}, ${c.a / 100})`); 27 } 28 return parseColor(`rgb(${c.r}, ${c.g}, ${c.b})`); 29} 30 31let useColor = ( 32 theme: PubLeafletPublication.Record["theme"] | null | undefined, 33 c: keyof typeof PubThemeDefaults, 34) => { 35 return useMemo(() => { 36 let v = theme?.[c]; 37 if (isColor(v)) { 38 return parseThemeColor(v); 39 } else return parseColor(PubThemeDefaults[c]); 40 }, [theme?.[c]]); 41}; 42let isColor = ( 43 c: any, 44): c is PubLeafletThemeColor.Rgb | PubLeafletThemeColor.Rgba => { 45 return ( 46 c?.$type === "pub.leaflet.theme.color#rgb" || 47 c?.$type === "pub.leaflet.theme.color#rgba" 48 ); 49}; 50 51export function PublicationThemeProviderDashboard(props: { 52 children: React.ReactNode; 53}) { 54 let { data } = usePublicationData(); 55 let { publication: pub } = data || {}; 56 return ( 57 <PublicationThemeProvider 58 pub_creator={pub?.identity_did || ""} 59 theme={(pub?.record as PubLeafletPublication.Record)?.theme} 60 > 61 <PublicationBackgroundProvider 62 theme={(pub?.record as PubLeafletPublication.Record)?.theme} 63 pub_creator={pub?.identity_did || ""} 64 > 65 {props.children} 66 </PublicationBackgroundProvider> 67 </PublicationThemeProvider> 68 ); 69} 70 71export function PublicationBackgroundProvider(props: { 72 theme?: PubLeafletPublication.Record["theme"] | null; 73 pub_creator: string; 74 className?: string; 75 children: React.ReactNode; 76}) { 77 let backgroundImage = props.theme?.backgroundImage?.image?.ref 78 ? blobRefToSrc(props.theme?.backgroundImage?.image?.ref, props.pub_creator) 79 : null; 80 81 let backgroundImageRepeat = props.theme?.backgroundImage?.repeat; 82 let backgroundImageSize = props.theme?.backgroundImage?.width || 500; 83 return ( 84 <div 85 className="PubBackgroundWrapper w-full bg-bg-leaflet text-primary h-full flex flex-col bg-cover bg-center bg-no-repeat items-stretch" 86 style={{ 87 backgroundImage: backgroundImage 88 ? `url(${backgroundImage})` 89 : undefined, 90 backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat", 91 backgroundSize: `${backgroundImageRepeat ? `${backgroundImageSize}px` : "cover"}`, 92 }} 93 > 94 {props.children} 95 </div> 96 ); 97} 98export function PublicationThemeProvider(props: { 99 local?: boolean; 100 children: React.ReactNode; 101 theme?: PubLeafletPublication.Record["theme"] | null; 102 pub_creator: string; 103 isStandalone?: boolean; 104}) { 105 let colors = usePubTheme(props.theme, props.isStandalone); 106 let cardBorderHidden = !colors.showPageBackground; 107 return ( 108 <CardBorderHiddenContext.Provider value={cardBorderHidden}> 109 <BaseThemeProvider local={props.local} {...colors}> 110 {props.children} 111 </BaseThemeProvider> 112 </CardBorderHiddenContext.Provider> 113 ); 114} 115 116export const usePubTheme = ( 117 theme?: PubLeafletPublication.Record["theme"] | null, 118 isStandalone?: boolean, 119) => { 120 let bgLeaflet = useColor(theme, "backgroundColor"); 121 let bgPage = useColor(theme, "pageBackground"); 122 // For standalone documents, use the editor default page background (#FFFFFF) 123 // For publications without explicit pageBackground, use bgLeaflet 124 if (isStandalone && !theme?.pageBackground) { 125 bgPage = parseColor(StandalonePageBackground); 126 } else if (theme && !theme.pageBackground) { 127 bgPage = bgLeaflet; 128 } 129 let showPageBackground = theme?.showPageBackground; 130 131 let primary = useColor(theme, "primary"); 132 133 let accent1 = useColor(theme, "accentBackground"); 134 let accent2 = useColor(theme, "accentText"); 135 136 let highlight1 = useEntity(null, "theme/highlight-1")?.data.value; 137 let highlight2 = useColorAttribute(null, "theme/highlight-2"); 138 let highlight3 = useColorAttribute(null, "theme/highlight-3"); 139 140 return { 141 bgLeaflet, 142 bgPage, 143 primary, 144 accent1, 145 accent2, 146 highlight1, 147 highlight2, 148 highlight3, 149 showPageBackground, 150 }; 151}; 152 153export const useLocalPubTheme = ( 154 theme: PubLeafletPublication.Record["theme"] | undefined, 155 showPageBackground?: boolean, 156) => { 157 const pubTheme = usePubTheme(theme); 158 const [localOverrides, setTheme] = useState<Partial<typeof pubTheme>>({}); 159 160 const mergedTheme = useMemo(() => { 161 let newTheme = { 162 ...pubTheme, 163 ...localOverrides, 164 showPageBackground, 165 }; 166 let newAccentContrast; 167 let sortedAccents = [newTheme.accent1, newTheme.accent2].sort((a, b) => { 168 return ( 169 getColorContrast( 170 colorToString(b, "rgb"), 171 colorToString( 172 showPageBackground ? newTheme.bgPage : newTheme.bgLeaflet, 173 "rgb", 174 ), 175 ) - 176 getColorContrast( 177 colorToString(a, "rgb"), 178 colorToString( 179 showPageBackground ? newTheme.bgPage : newTheme.bgLeaflet, 180 "rgb", 181 ), 182 ) 183 ); 184 }); 185 if ( 186 getColorContrast( 187 colorToString(sortedAccents[0], "rgb"), 188 colorToString(newTheme.primary, "rgb"), 189 ) < 30 && 190 getColorContrast( 191 colorToString(sortedAccents[1], "rgb"), 192 colorToString( 193 showPageBackground ? newTheme.bgPage : newTheme.bgLeaflet, 194 "rgb", 195 ), 196 ) > 12 197 ) { 198 newAccentContrast = sortedAccents[1]; 199 } else newAccentContrast = sortedAccents[0]; 200 return { 201 ...newTheme, 202 accentContrast: newAccentContrast, 203 }; 204 }, [pubTheme, localOverrides, showPageBackground]); 205 return { 206 theme: mergedTheme, 207 setTheme, 208 changes: Object.keys(localOverrides).length > 0, 209 }; 210};