a tool for shared writing and social publishing
at feature/backdate 184 lines 5.7 kB view raw
1"use client"; 2import { useMemo, useState } from "react"; 3import { parseColor } from "react-aria-components"; 4import { useEntity } from "src/replicache"; 5import { getColorDifference } 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 theme = usePubTheme(props.theme, props.isStandalone); 106 let cardBorderHidden = !theme.showPageBackground; 107 let hasBackgroundImage = !!props.theme?.backgroundImage?.image?.ref; 108 109 return ( 110 <CardBorderHiddenContext.Provider value={cardBorderHidden}> 111 <BaseThemeProvider 112 local={props.local} 113 {...theme} 114 hasBackgroundImage={hasBackgroundImage} 115 > 116 {props.children} 117 </BaseThemeProvider> 118 </CardBorderHiddenContext.Provider> 119 ); 120} 121 122export const usePubTheme = ( 123 theme?: PubLeafletPublication.Record["theme"] | null, 124 isStandalone?: boolean, 125) => { 126 let bgLeaflet = useColor(theme, "backgroundColor"); 127 let bgPage = useColor(theme, "pageBackground"); 128 // For standalone documents, use the editor default page background (#FFFFFF) 129 // For publications without explicit pageBackground, use bgLeaflet 130 if (isStandalone && !theme?.pageBackground) { 131 bgPage = parseColor(StandalonePageBackground); 132 } else if (theme && !theme.pageBackground) { 133 bgPage = bgLeaflet; 134 } 135 let showPageBackground = theme?.showPageBackground; 136 let pageWidth = theme?.pageWidth; 137 138 let primary = useColor(theme, "primary"); 139 140 let accent1 = useColor(theme, "accentBackground"); 141 let accent2 = useColor(theme, "accentText"); 142 143 let highlight1 = useEntity(null, "theme/highlight-1")?.data.value; 144 let highlight2 = useColorAttribute(null, "theme/highlight-2"); 145 let highlight3 = useColorAttribute(null, "theme/highlight-3"); 146 147 return { 148 bgLeaflet, 149 bgPage, 150 primary, 151 accent1, 152 accent2, 153 highlight1, 154 highlight2, 155 highlight3, 156 showPageBackground, 157 pageWidth, 158 }; 159}; 160 161export const useLocalPubTheme = ( 162 theme: PubLeafletPublication.Record["theme"] | undefined, 163 showPageBackground?: boolean, 164) => { 165 const pubTheme = usePubTheme(theme); 166 const [localOverrides, setTheme] = useState<Partial<typeof pubTheme>>({}); 167 168 const mergedTheme = useMemo(() => { 169 let newTheme = { 170 ...pubTheme, 171 ...localOverrides, 172 showPageBackground, 173 }; 174 175 return { 176 ...newTheme, 177 }; 178 }, [pubTheme, localOverrides, showPageBackground]); 179 return { 180 theme: mergedTheme, 181 setTheme, 182 changes: Object.keys(localOverrides).length > 0, 183 }; 184};