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