a tool for shared writing and social publishing
at update/delete-leaflets 195 lines 5.9 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 "./ThemeProvider"; 6import { useColorAttribute, colorToString } from "./useColorAttribute"; 7import { BaseThemeProvider } 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}; 19function parseThemeColor( 20 c: PubLeafletThemeColor.Rgb | PubLeafletThemeColor.Rgba, 21) { 22 if (c.$type === "pub.leaflet.theme.color#rgba") { 23 return parseColor(`rgba(${c.r}, ${c.g}, ${c.b}, ${c.a / 100})`); 24 } 25 return parseColor(`rgb(${c.r}, ${c.g}, ${c.b})`); 26} 27 28let useColor = ( 29 record: PubLeafletPublication.Record | null | undefined, 30 c: keyof typeof PubThemeDefaults, 31) => { 32 return useMemo(() => { 33 let v = record?.theme?.[c]; 34 if (isColor(v)) { 35 return parseThemeColor(v); 36 } else return parseColor(PubThemeDefaults[c]); 37 }, [record?.theme?.[c]]); 38}; 39let isColor = ( 40 c: any, 41): c is PubLeafletThemeColor.Rgb | PubLeafletThemeColor.Rgba => { 42 return ( 43 c?.$type === "pub.leaflet.theme.color#rgb" || 44 c?.$type === "pub.leaflet.theme.color#rgba" 45 ); 46}; 47 48export function PublicationThemeProviderDashboard(props: { 49 children: React.ReactNode; 50}) { 51 let { data } = usePublicationData(); 52 let { publication: pub } = data || {}; 53 return ( 54 <PublicationThemeProvider 55 pub_creator={pub?.identity_did || ""} 56 record={pub?.record as PubLeafletPublication.Record} 57 > 58 <PublicationBackgroundProvider 59 record={pub?.record as PubLeafletPublication.Record} 60 pub_creator={pub?.identity_did || ""} 61 > 62 {props.children} 63 </PublicationBackgroundProvider> 64 </PublicationThemeProvider> 65 ); 66} 67 68export function PublicationBackgroundProvider(props: { 69 record?: PubLeafletPublication.Record | null; 70 pub_creator: string; 71 className?: string; 72 children: React.ReactNode; 73}) { 74 let backgroundImage = props.record?.theme?.backgroundImage?.image?.ref 75 ? blobRefToSrc( 76 props.record?.theme?.backgroundImage?.image?.ref, 77 props.pub_creator, 78 ) 79 : null; 80 81 let backgroundImageRepeat = props.record?.theme?.backgroundImage?.repeat; 82 let backgroundImageSize = props.record?.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: `url(${backgroundImage})`, 88 backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat", 89 backgroundSize: `${backgroundImageRepeat ? `${backgroundImageSize}px` : "cover"}`, 90 }} 91 > 92 {props.children} 93 </div> 94 ); 95} 96export function PublicationThemeProvider(props: { 97 local?: boolean; 98 children: React.ReactNode; 99 record?: PubLeafletPublication.Record | null; 100 pub_creator: string; 101}) { 102 let colors = usePubTheme(props.record); 103 return ( 104 <BaseThemeProvider local={props.local} {...colors}> 105 {props.children} 106 </BaseThemeProvider> 107 ); 108} 109 110export const usePubTheme = (record?: PubLeafletPublication.Record | null) => { 111 let bgLeaflet = useColor(record, "backgroundColor"); 112 let bgPage = useColor(record, "pageBackground"); 113 bgPage = record?.theme?.pageBackground ? bgPage : bgLeaflet; 114 let showPageBackground = record?.theme?.showPageBackground; 115 116 let primary = useColor(record, "primary"); 117 118 let accent1 = useColor(record, "accentBackground"); 119 let accent2 = useColor(record, "accentText"); 120 121 let highlight1 = useEntity(null, "theme/highlight-1")?.data.value; 122 let highlight2 = useColorAttribute(null, "theme/highlight-2"); 123 let highlight3 = useColorAttribute(null, "theme/highlight-3"); 124 125 return { 126 bgLeaflet, 127 bgPage, 128 primary, 129 accent1, 130 accent2, 131 highlight1, 132 highlight2, 133 highlight3, 134 showPageBackground, 135 }; 136}; 137 138export const useLocalPubTheme = ( 139 record: PubLeafletPublication.Record | undefined, 140 showPageBackground?: boolean, 141) => { 142 const pubTheme = usePubTheme(record); 143 const [localOverrides, setTheme] = useState<Partial<typeof pubTheme>>({}); 144 145 const mergedTheme = useMemo(() => { 146 let newTheme = { 147 ...pubTheme, 148 ...localOverrides, 149 showPageBackground, 150 }; 151 let newAccentContrast; 152 let sortedAccents = [newTheme.accent1, newTheme.accent2].sort((a, b) => { 153 return ( 154 getColorContrast( 155 colorToString(b, "rgb"), 156 colorToString( 157 showPageBackground ? newTheme.bgPage : newTheme.bgLeaflet, 158 "rgb", 159 ), 160 ) - 161 getColorContrast( 162 colorToString(a, "rgb"), 163 colorToString( 164 showPageBackground ? newTheme.bgPage : newTheme.bgLeaflet, 165 "rgb", 166 ), 167 ) 168 ); 169 }); 170 if ( 171 getColorContrast( 172 colorToString(sortedAccents[0], "rgb"), 173 colorToString(newTheme.primary, "rgb"), 174 ) < 30 && 175 getColorContrast( 176 colorToString(sortedAccents[1], "rgb"), 177 colorToString( 178 showPageBackground ? newTheme.bgPage : newTheme.bgLeaflet, 179 "rgb", 180 ), 181 ) > 12 182 ) { 183 newAccentContrast = sortedAccents[1]; 184 } else newAccentContrast = sortedAccents[0]; 185 return { 186 ...newTheme, 187 accentContrast: newAccentContrast, 188 }; 189 }, [pubTheme, localOverrides, showPageBackground]); 190 return { 191 theme: mergedTheme, 192 setTheme, 193 changes: Object.keys(localOverrides).length > 0, 194 }; 195};