a tool for shared writing and social publishing
at debug/datetime 354 lines 11 kB view raw
1"use client"; 2import { Popover } from "components/Popover"; 3import { theme } from "../../tailwind.config"; 4 5import { Color } from "react-aria-components"; 6 7import { LeafletBGPicker } from "./Pickers/LeafletBGPicker"; 8import { 9 PageBackgroundPicker, 10 PageBorderHider, 11 PageThemePickers, 12} from "./Pickers/PageThemePickers"; 13import { useMemo, useState } from "react"; 14import { ReplicacheMutators, useEntity, useReplicache } from "src/replicache"; 15import { Replicache } from "replicache"; 16import { FilterAttributes } from "src/replicache/attributes"; 17import { colorToString } from "components/ThemeManager/useColorAttribute"; 18import { useEntitySetContext } from "components/EntitySetProvider"; 19import { ActionButton } from "components/ActionBar/ActionButton"; 20import { CheckboxChecked } from "components/Icons/CheckboxChecked"; 21import { CheckboxEmpty } from "components/Icons/CheckboxEmpty"; 22import { PaintSmall } from "components/Icons/PaintSmall"; 23import { AccentPickers } from "./Pickers/AccentPickers"; 24import { useLeafletPublicationData } from "components/PageSWRDataProvider"; 25import { useIsMobile } from "src/hooks/isMobile"; 26import { Toggle } from "components/Toggle"; 27 28export type pickers = 29 | "null" 30 | "leaflet" 31 | "page" 32 | "accent-1" 33 | "accent-2" 34 | "text" 35 | "highlight-1" 36 | "highlight-2" 37 | "highlight-3" 38 | "page-background-image"; 39 40export function setColorAttribute( 41 rep: Replicache<ReplicacheMutators> | null, 42 entity: string, 43) { 44 return (attribute: keyof FilterAttributes<{ type: "color" }>) => 45 (color: Color) => 46 rep?.mutate.assertFact({ 47 entity, 48 attribute, 49 data: { type: "color", value: colorToString(color, "hsba") }, 50 }); 51} 52export const ThemePopover = (props: { entityID: string; home?: boolean }) => { 53 let { rep } = useReplicache(); 54 let { data: pub } = useLeafletPublicationData(); 55 let isMobile = useIsMobile(); 56 57 // I need to get these variables from replicache and then write them to the DB. I also need to parse them into a state that can be used here. 58 let permission = useEntitySetContext().permissions.write; 59 let leafletBGImage = useEntity(props.entityID, "theme/background-image"); 60 let leafletBGRepeat = useEntity( 61 props.entityID, 62 "theme/background-image-repeat", 63 ); 64 65 let [openPicker, setOpenPicker] = useState<pickers>( 66 props.home === true ? "leaflet" : "null", 67 ); 68 let set = useMemo(() => { 69 return setColorAttribute(rep, props.entityID); 70 }, [rep, props.entityID]); 71 72 if (!permission) return null; 73 if (pub) return null; 74 75 return ( 76 <> 77 <Popover 78 className="w-80 bg-white" 79 arrowFill="#FFFFFF" 80 asChild 81 side={isMobile ? "top" : "right"} 82 align={isMobile ? "center" : "start"} 83 trigger={<ActionButton icon={<PaintSmall />} label="Theme" />} 84 > 85 <ThemeSetterContent {...props} /> 86 </Popover> 87 </> 88 ); 89}; 90 91export const ThemeSetterContent = (props: { 92 entityID: string; 93 home?: boolean; 94}) => { 95 let { rep } = useReplicache(); 96 let { data: pub } = useLeafletPublicationData(); 97 98 // I need to get these variables from replicache and then write them to the DB. I also need to parse them into a state that can be used here. 99 let permission = useEntitySetContext().permissions.write; 100 let leafletBGImage = useEntity(props.entityID, "theme/background-image"); 101 let leafletBGRepeat = useEntity( 102 props.entityID, 103 "theme/background-image-repeat", 104 ); 105 106 let [openPicker, setOpenPicker] = useState<pickers>( 107 props.home === true ? "leaflet" : "null", 108 ); 109 let set = useMemo(() => { 110 return setColorAttribute(rep, props.entityID); 111 }, [rep, props.entityID]); 112 113 if (!permission) return null; 114 if (pub) return null; 115 return ( 116 <div className="themeSetterContent flex flex-col w-full overflow-y-scroll no-scrollbar"> 117 <div className="themeBGLeaflet flex"> 118 <div className={`bgPicker flex flex-col gap-0 -mb-[6px] z-10 w-full `}> 119 <div className="bgPickerBody w-full flex flex-col gap-2 p-2 mt-1 border border-[#CCCCCC] rounded-md"> 120 <LeafletBGPicker 121 entityID={props.entityID} 122 thisPicker={"leaflet"} 123 openPicker={openPicker} 124 setOpenPicker={setOpenPicker} 125 closePicker={() => setOpenPicker("null")} 126 setValue={set("theme/page-background")} 127 /> 128 <PageBackgroundPicker 129 entityID={props.entityID} 130 setValue={set("theme/card-background")} 131 openPicker={openPicker} 132 setOpenPicker={setOpenPicker} 133 home={props.home} 134 /> 135 <hr className=" border-[#CCCCCC]" /> 136 <PageBorderHider 137 entityID={props.entityID} 138 openPicker={openPicker} 139 setOpenPicker={setOpenPicker} 140 /> 141 </div> 142 143 <SectionArrow fill="white" stroke="#CCCCCC" className="ml-2 -mt-px" /> 144 </div> 145 </div> 146 147 <div 148 onClick={(e) => { 149 e.currentTarget === e.target && setOpenPicker("leaflet"); 150 }} 151 style={{ 152 backgroundImage: leafletBGImage 153 ? `url(${leafletBGImage.data.src})` 154 : undefined, 155 backgroundRepeat: leafletBGRepeat ? "repeat" : "no-repeat", 156 backgroundPosition: "center", 157 backgroundSize: !leafletBGRepeat 158 ? "cover" 159 : `calc(${leafletBGRepeat.data.value}px / 2 )`, 160 }} 161 className={`bg-bg-leaflet px-3 pt-4 pb-0 mb-2 flex flex-col gap-4 rounded-md border border-border`} 162 > 163 <PageThemePickers 164 entityID={props.entityID} 165 openPicker={openPicker} 166 setOpenPicker={(pickers) => setOpenPicker(pickers)} 167 /> 168 <div className="flex flex-col -gap-[6px]"> 169 <div className={`flex flex-col z-10 -mb-[6px] `}> 170 <AccentPickers 171 entityID={props.entityID} 172 openPicker={openPicker} 173 setOpenPicker={(pickers) => setOpenPicker(pickers)} 174 /> 175 <SectionArrow 176 fill={theme.colors["accent-2"]} 177 stroke={theme.colors["accent-1"]} 178 className="ml-2" 179 /> 180 </div> 181 182 <SampleButton 183 entityID={props.entityID} 184 setOpenPicker={setOpenPicker} 185 /> 186 </div> 187 188 <SamplePage 189 setOpenPicker={setOpenPicker} 190 home={props.home} 191 entityID={props.entityID} 192 /> 193 </div> 194 {!props.home && <WatermarkSetter entityID={props.entityID} />} 195 </div> 196 ); 197}; 198function WatermarkSetter(props: { entityID: string }) { 199 let { rep } = useReplicache(); 200 let checked = useEntity(props.entityID, "theme/page-leaflet-watermark"); 201 202 function handleToggle() { 203 rep?.mutate.assertFact({ 204 entity: props.entityID, 205 attribute: "theme/page-leaflet-watermark", 206 data: { type: "boolean", value: !checked?.data.value }, 207 }); 208 } 209 return ( 210 <div className="flex gap-2 items-start mt-0.5"> 211 <Toggle 212 toggleOn={!!checked?.data.value} 213 setToggleOn={() => { 214 handleToggle(); 215 }} 216 disabledColor1="#8C8C8C" 217 disabledColor2="#DBDBDB" 218 /> 219 <button 220 className="flex gap-2 items-center -mt-0.5" 221 onClick={() => { 222 handleToggle(); 223 }} 224 > 225 <div className="flex flex-col gap-0 items-start"> 226 <div className="font-bold">Show Leaflet Watermark</div> 227 <div className="text-sm text-[#969696]">Help us spread the word!</div> 228 </div> 229 </button> 230 </div> 231 ); 232} 233 234const SampleButton = (props: { 235 entityID: string; 236 setOpenPicker: (thisPicker: pickers) => void; 237}) => { 238 return ( 239 <div 240 onClick={(e) => { 241 e.target === e.currentTarget && props.setOpenPicker("accent-1"); 242 }} 243 className="pointer-cursor font-bold relative text-center text-lg py-2 rounded-md bg-accent-1 text-accent-2 shadow-md flex items-center justify-center" 244 > 245 <div 246 className="cursor-pointer w-fit" 247 onClick={() => { 248 props.setOpenPicker("accent-2"); 249 }} 250 > 251 Example Button 252 </div> 253 </div> 254 ); 255}; 256const SamplePage = (props: { 257 entityID: string; 258 home: boolean | undefined; 259 setOpenPicker: (picker: "page" | "text") => void; 260}) => { 261 let pageBGImage = useEntity(props.entityID, "theme/card-background-image"); 262 let pageBGRepeat = useEntity( 263 props.entityID, 264 "theme/card-background-image-repeat", 265 ); 266 let pageBGOpacity = useEntity( 267 props.entityID, 268 "theme/card-background-image-opacity", 269 ); 270 let pageBorderHidden = useEntity(props.entityID, "theme/card-border-hidden") 271 ?.data.value; 272 273 return ( 274 <div 275 onClick={(e) => { 276 e.currentTarget === e.target && props.setOpenPicker("page"); 277 }} 278 className={` 279 text-primary relative 280 ${ 281 pageBorderHidden 282 ? "py-2 px-0 border border-transparent" 283 : `cursor-pointer p-2 border border-border border-b-transparent shadow-md 284 ${props.home ? "rounded-md " : "rounded-t-lg "}` 285 }`} 286 style={ 287 pageBorderHidden 288 ? undefined 289 : { 290 backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha))", 291 } 292 } 293 > 294 <div 295 className="background absolute top-0 right-0 bottom-0 left-0 z-0 rounded-t-lg" 296 style={ 297 pageBorderHidden 298 ? undefined 299 : { 300 backgroundImage: pageBGImage 301 ? `url(${pageBGImage.data.src})` 302 : undefined, 303 304 backgroundRepeat: pageBGRepeat ? "repeat" : "no-repeat", 305 opacity: pageBGOpacity?.data.value || 1, 306 backgroundSize: !pageBGRepeat 307 ? "cover" 308 : `calc(${pageBGRepeat.data.value}px / 2 )`, 309 } 310 } 311 /> 312 <div className="z-10 relative"> 313 <p 314 onClick={() => { 315 props.setOpenPicker("text"); 316 }} 317 className="cursor-pointer font-bold w-fit" 318 > 319 Hello! 320 </p> 321 <small onClick={() => props.setOpenPicker("text")}> 322 Welcome to{" "} 323 <span className="font-bold text-accent-contrast">Leaflet</span> a 324 fun and easy way to make, share, and collab on little bits of paper 325 </small> 326 </div> 327 </div> 328 ); 329}; 330 331export const SectionArrow = (props: { 332 fill: string; 333 stroke: string; 334 className: string; 335}) => { 336 return ( 337 <svg 338 width="24" 339 height="12" 340 viewBox="0 0 24 12" 341 fill="none" 342 xmlns="http://www.w3.org/2000/svg" 343 className={props.className} 344 > 345 <path d="M11.9999 12L24 0H0L11.9999 12Z" fill={props.fill} /> 346 <path 347 fillRule="evenodd" 348 clipRule="evenodd" 349 d="M1.33552 0L12 10.6645L22.6645 0H24L12 12L0 0H1.33552Z" 350 fill={props.stroke} 351 /> 352 </svg> 353 ); 354};