a tool for shared writing and social publishing
at fix/bottom-scroll-margin-on-docs 387 lines 14 kB view raw
1import { usePublicationData } from "app/lish/[did]/[publication]/dashboard/PublicationSWRProvider"; 2import { useState } from "react"; 3import { pickers, SectionArrow } from "./ThemeSetter"; 4import { Color } from "react-aria-components"; 5import { 6 PubLeafletPublication, 7 PubLeafletThemeBackgroundImage, 8} from "lexicons/api"; 9import { AtUri } from "@atproto/syntax"; 10import { useLocalPubTheme } from "./PublicationThemeProvider"; 11import { BaseThemeProvider } from "./ThemeProvider"; 12import { blobRefToSrc } from "src/utils/blobRefToSrc"; 13import { updatePublicationTheme } from "app/lish/createPub/updatePublication"; 14import { PagePickers } from "./PubPickers/PubTextPickers"; 15import { BackgroundPicker } from "./PubPickers/PubBackgroundPickers"; 16import { PubAccentPickers } from "./PubPickers/PubAcccentPickers"; 17import { Separator } from "components/Layout"; 18import { PubSettingsHeader } from "app/lish/[did]/[publication]/dashboard/PublicationSettings"; 19 20export type ImageState = { 21 src: string; 22 file?: File; 23 repeat: number | null; 24}; 25export const PubThemeSetter = (props: { 26 backToMenu: () => void; 27 loading: boolean; 28 setLoading: (l: boolean) => void; 29}) => { 30 let [sample, setSample] = useState<"pub" | "post">("pub"); 31 let [openPicker, setOpenPicker] = useState<pickers>("null"); 32 let { data, mutate } = usePublicationData(); 33 let { publication: pub } = data || {}; 34 let record = pub?.record as PubLeafletPublication.Record | undefined; 35 let [showPageBackground, setShowPageBackground] = useState( 36 !!record?.theme?.showPageBackground, 37 ); 38 let { 39 theme: localPubTheme, 40 setTheme, 41 changes, 42 } = useLocalPubTheme(record, showPageBackground); 43 let [image, setImage] = useState<ImageState | null>( 44 PubLeafletThemeBackgroundImage.isMain(record?.theme?.backgroundImage) 45 ? { 46 src: blobRefToSrc( 47 record.theme.backgroundImage.image.ref, 48 pub?.identity_did!, 49 ), 50 repeat: record.theme.backgroundImage.repeat 51 ? record.theme.backgroundImage.width || 500 52 : null, 53 } 54 : null, 55 ); 56 57 let pubBGImage = image?.src || null; 58 let leafletBGRepeat = image?.repeat || null; 59 60 return ( 61 <BaseThemeProvider local {...localPubTheme}> 62 <form 63 onSubmit={async (e) => { 64 e.preventDefault(); 65 if (!pub) return; 66 props.setLoading(true); 67 let result = await updatePublicationTheme({ 68 uri: pub.uri, 69 theme: { 70 pageBackground: ColorToRGBA(localPubTheme.bgPage), 71 showPageBackground: showPageBackground, 72 backgroundColor: image 73 ? ColorToRGBA(localPubTheme.bgLeaflet) 74 : ColorToRGB(localPubTheme.bgLeaflet), 75 backgroundRepeat: image?.repeat, 76 backgroundImage: image ? image.file : null, 77 primary: ColorToRGB(localPubTheme.primary), 78 accentBackground: ColorToRGB(localPubTheme.accent1), 79 accentText: ColorToRGB(localPubTheme.accent2), 80 }, 81 }); 82 mutate((pub) => { 83 if (result?.publication && pub?.publication) 84 return { 85 ...pub, 86 publication: { ...pub.publication, ...result.publication }, 87 }; 88 return pub; 89 }, false); 90 props.setLoading(false); 91 }} 92 > 93 <PubSettingsHeader 94 loading={props.loading} 95 setLoadingAction={props.setLoading} 96 backToMenuAction={props.backToMenu} 97 state={"theme"} 98 /> 99 </form> 100 101 <div className="themeSetterContent flex flex-col w-full overflow-y-scroll -mb-2 "> 102 <div className="themeBGLeaflet flex"> 103 <div 104 className={`bgPicker flex flex-col gap-0 -mb-[6px] z-10 w-full `} 105 > 106 <div className="bgPickerBody w-full flex flex-col gap-2 p-2 mt-1 border border-[#CCCCCC] rounded-md text-[#595959] bg-white"> 107 <BackgroundPicker 108 bgImage={image} 109 setBgImage={setImage} 110 backgroundColor={localPubTheme.bgLeaflet} 111 pageBackground={localPubTheme.bgPage} 112 setPageBackground={(color) => { 113 setTheme((t) => ({ ...t, bgPage: color })); 114 }} 115 setBackgroundColor={(color) => { 116 setTheme((t) => ({ ...t, bgLeaflet: color })); 117 }} 118 openPicker={openPicker} 119 setOpenPicker={setOpenPicker} 120 hasPageBackground={!!showPageBackground} 121 setHasPageBackground={setShowPageBackground} 122 /> 123 </div> 124 125 <SectionArrow 126 fill="white" 127 stroke="#CCCCCC" 128 className="ml-2 -mt-[1px]" 129 /> 130 </div> 131 </div> 132 133 <div 134 style={{ 135 backgroundImage: pubBGImage ? `url(${pubBGImage})` : undefined, 136 backgroundRepeat: leafletBGRepeat ? "repeat" : "no-repeat", 137 backgroundPosition: "center", 138 backgroundSize: !leafletBGRepeat 139 ? "cover" 140 : `calc(${leafletBGRepeat}px / 2 )`, 141 }} 142 className={` relative bg-bg-leaflet px-3 py-4 flex flex-col rounded-md border border-border `} 143 > 144 <div className={`flex flex-col gap-3 z-10`}> 145 <PagePickers 146 pageBackground={localPubTheme.bgPage} 147 primary={localPubTheme.primary} 148 setPageBackground={(color) => { 149 setTheme((t) => ({ ...t, bgPage: color })); 150 }} 151 setPrimary={(color) => { 152 setTheme((t) => ({ ...t, primary: color })); 153 }} 154 openPicker={openPicker} 155 setOpenPicker={(pickers) => setOpenPicker(pickers)} 156 hasPageBackground={showPageBackground} 157 /> 158 <PubAccentPickers 159 accent1={localPubTheme.accent1} 160 setAccent1={(color) => { 161 setTheme((t) => ({ ...t, accent1: color })); 162 }} 163 accent2={localPubTheme.accent2} 164 setAccent2={(color) => { 165 setTheme((t) => ({ ...t, accent2: color })); 166 }} 167 openPicker={openPicker} 168 setOpenPicker={(pickers) => setOpenPicker(pickers)} 169 /> 170 </div> 171 </div> 172 <div className="flex flex-col mt-4 "> 173 <div className="flex gap-2 items-center text-sm text-[#8C8C8C]"> 174 <div className="text-sm">Preview</div> 175 <Separator classname="h-4!" />{" "} 176 <button 177 className={`${sample === "pub" ? "font-bold text-[#595959]" : ""}`} 178 onClick={() => setSample("pub")} 179 > 180 Pub 181 </button> 182 <button 183 className={`${sample === "post" ? "font-bold text-[#595959]" : ""}`} 184 onClick={() => setSample("post")} 185 > 186 Post 187 </button> 188 </div> 189 {sample === "pub" ? ( 190 <SamplePub 191 pubBGImage={pubBGImage} 192 pubBGRepeat={leafletBGRepeat} 193 showPageBackground={showPageBackground} 194 /> 195 ) : ( 196 <SamplePost 197 pubBGImage={pubBGImage} 198 pubBGRepeat={leafletBGRepeat} 199 showPageBackground={showPageBackground} 200 /> 201 )} 202 </div> 203 </div> 204 </BaseThemeProvider> 205 ); 206}; 207 208const SamplePub = (props: { 209 pubBGImage: string | null; 210 pubBGRepeat: number | null; 211 showPageBackground: boolean; 212}) => { 213 let { data } = usePublicationData(); 214 let { publication } = data || {}; 215 let record = publication?.record as PubLeafletPublication.Record | null; 216 217 return ( 218 <div 219 style={{ 220 backgroundImage: props.pubBGImage 221 ? `url(${props.pubBGImage})` 222 : undefined, 223 backgroundRepeat: props.pubBGRepeat ? "repeat" : "no-repeat", 224 backgroundPosition: "center", 225 backgroundSize: !props.pubBGRepeat 226 ? "cover" 227 : `calc(${props.pubBGRepeat}px / 2 )`, 228 }} 229 className={`bg-bg-leaflet p-3 pb-0 flex flex-col gap-3 rounded-t-md border border-border border-b-0 h-[148px] overflow-hidden `} 230 > 231 <div 232 className="sampleContent rounded-t-md border-border pb-4 px-[10px] flex flex-col gap-[14px] w-[250px] mx-auto" 233 style={{ 234 background: props.showPageBackground 235 ? "rgba(var(--bg-page), var(--bg-page-alpha))" 236 : undefined, 237 }} 238 > 239 <div className="flex flex-col justify-center text-center pt-2"> 240 {record?.icon && publication?.uri && ( 241 <div 242 style={{ 243 backgroundRepeat: "no-repeat", 244 backgroundPosition: "center", 245 backgroundSize: "cover", 246 backgroundImage: `url(/api/atproto_images?did=${new AtUri(publication.uri).host}&cid=${(record.icon?.ref as unknown as { $link: string })["$link"]})`, 247 }} 248 className="w-4 h-4 rounded-full place-self-center" 249 /> 250 )} 251 252 <div className="text-[11px] font-bold pt-[5px] text-accent-contrast"> 253 {record?.name} 254 </div> 255 <div className="text-[7px] font-normal text-tertiary"> 256 {record?.description} 257 </div> 258 <div className=" flex gap-1 items-center mt-[6px] bg-accent-1 text-accent-2 py-px px-[4px] text-[7px] w-fit font-bold rounded-[2px] mx-auto"> 259 <div className="h-[7px] w-[7px] rounded-full bg-accent-2" /> 260 Subscribe with Bluesky 261 </div> 262 </div> 263 264 <div className="flex flex-col text-[8px] rounded-md "> 265 <div className="font-bold">A Sample Post</div> 266 <div className="text-secondary italic text-[6px]"> 267 This is a sample description about the sample post 268 </div> 269 <div className="text-tertiary text-[5px] pt-[2px]">Jan 1, 20XX </div> 270 </div> 271 </div> 272 </div> 273 ); 274}; 275 276const SamplePost = (props: { 277 pubBGImage: string | null; 278 pubBGRepeat: number | null; 279 showPageBackground: boolean; 280}) => { 281 let { data } = usePublicationData(); 282 let { publication } = data || {}; 283 let record = publication?.record as PubLeafletPublication.Record | null; 284 return ( 285 <div 286 style={{ 287 backgroundImage: props.pubBGImage 288 ? `url(${props.pubBGImage})` 289 : undefined, 290 backgroundRepeat: props.pubBGRepeat ? "repeat" : "no-repeat", 291 backgroundPosition: "center", 292 backgroundSize: !props.pubBGRepeat 293 ? "cover" 294 : `calc(${props.pubBGRepeat}px / 2 )`, 295 }} 296 className={`bg-bg-leaflet p-3 max-w-full flex flex-col gap-3 rounded-t-md border border-border border-b-0 pb-0 h-[148px] overflow-hidden`} 297 > 298 <div 299 className="sampleContent rounded-t-md border-border pb-0 px-[6px] flex flex-col w-[250px] mx-auto" 300 style={{ 301 background: props.showPageBackground 302 ? "rgba(var(--bg-page), var(--bg-page-alpha))" 303 : undefined, 304 }} 305 > 306 <div className="flex flex-col "> 307 <div className="text-[6px] font-bold pt-[6px] text-accent-contrast"> 308 {record?.name} 309 </div> 310 <div className="text-[11px] font-bold text-primary"> 311 A Sample Post 312 </div> 313 <div className="text-[7px] font-normal text-secondary italic"> 314 A short sample description about the sample post 315 </div> 316 <div className="text-tertiary text-[5px] pt-[2px]">Jan 1, 20XX </div> 317 </div> 318 <div className="text-[6px] pt-[8px] flex flex-col gap-[6px]"> 319 <div> 320 Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque 321 faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi 322 pretium tellus duis convallis. Tempus leo eu aenean sed diam urna 323 tempor. 324 </div> 325 326 <div> 327 Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis 328 massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit 329 semper vel class aptent taciti sociosqu. Ad litora torquent per 330 conubia nostra inceptos himenaeos. 331 </div> 332 <div> 333 Sed et nisi semper, egestas purus a, egestas nulla. Nulla ultricies, 334 purus non dapibus tincidunt, nunc sem rhoncus sem, vel malesuada 335 tellus enim sit amet magna. Donec ac justo a ipsum fermentum 336 vulputate. Etiam sit amet viverra leo. Aenean accumsan consectetur 337 velit. Vivamus at justo a nisl imperdiet dictum. Donec scelerisque 338 ex eget turpis scelerisque tincidunt. Proin non convallis nibh, eget 339 aliquet ex. Curabitur ornare a ipsum in ultrices. 340 </div> 341 </div> 342 </div> 343 </div> 344 ); 345}; 346 347export function ColorToRGBA(color: Color) { 348 if (!color) 349 return { 350 $type: "pub.leaflet.theme.color#rgba" as const, 351 r: 0, 352 g: 0, 353 b: 0, 354 a: 1, 355 }; 356 let c = color.toFormat("rgba"); 357 const r = c.getChannelValue("red"); 358 const g = c.getChannelValue("green"); 359 const b = c.getChannelValue("blue"); 360 const a = c.getChannelValue("alpha"); 361 return { 362 $type: "pub.leaflet.theme.color#rgba" as const, 363 r: Math.round(r), 364 g: Math.round(g), 365 b: Math.round(b), 366 a: Math.round(a * 100), 367 }; 368} 369function ColorToRGB(color: Color) { 370 if (!color) 371 return { 372 $type: "pub.leaflet.theme.color#rgb" as const, 373 r: 0, 374 g: 0, 375 b: 0, 376 }; 377 let c = color.toFormat("rgb"); 378 const r = c.getChannelValue("red"); 379 const g = c.getChannelValue("green"); 380 const b = c.getChannelValue("blue"); 381 return { 382 $type: "pub.leaflet.theme.color#rgb" as const, 383 r: Math.round(r), 384 g: Math.round(g), 385 b: Math.round(b), 386 }; 387}