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