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