a tool for shared writing and social publishing
at feature/set-page-width 341 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 { 8 LeafletBackgroundPicker, 9 PageThemePickers, 10} from "./Pickers/PageThemePickers"; 11import { PageWidthSetter } from "./Pickers/PageWidthSetter"; 12import { useMemo, useState } from "react"; 13import { ReplicacheMutators, useEntity, useReplicache } from "src/replicache"; 14import { Replicache } from "replicache"; 15import { FilterAttributes } from "src/replicache/attributes"; 16import { colorToString } from "components/ThemeManager/useColorAttribute"; 17import { useEntitySetContext } from "components/EntitySetProvider"; 18import { ActionButton } from "components/ActionBar/ActionButton"; 19import { CheckboxChecked } from "components/Icons/CheckboxChecked"; 20import { CheckboxEmpty } from "components/Icons/CheckboxEmpty"; 21import { PaintSmall } from "components/Icons/PaintSmall"; 22import { AccentPickers } from "./Pickers/AccentPickers"; 23import { useLeafletPublicationData } from "components/PageSWRDataProvider"; 24import { useIsMobile } from "src/hooks/isMobile"; 25import { Toggle } from "components/Toggle"; 26 27export type pickers = 28 | "null" 29 | "leaflet" 30 | "page" 31 | "accent-1" 32 | "accent-2" 33 | "text" 34 | "highlight-1" 35 | "highlight-2" 36 | "highlight-3" 37 | "page-background-image" 38 | "page-width"; 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?.publications) return null; 74 75 return ( 76 <> 77 <Popover 78 className="w-80 bg-white py-3!" 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?.publications) return null; 115 return ( 116 <div className="themeSetterContent flex flex-col w-full overflow-y-scroll no-scrollbar"> 117 {!props.home && ( 118 <PageWidthSetter 119 entityID={props.entityID} 120 thisPicker={"page-width"} 121 openPicker={openPicker} 122 setOpenPicker={setOpenPicker} 123 closePicker={() => setOpenPicker("null")} 124 /> 125 )} 126 <div className="themeBGLeaflet flex"> 127 <div className={`bgPicker flex flex-col gap-0 -mb-[6px] z-10 w-full `}> 128 <div className="bgPickerBody w-full flex flex-col gap-2 p-2 mt-1 border border-[#CCCCCC] rounded-md"> 129 <LeafletBackgroundPicker 130 entityID={props.entityID} 131 openPicker={openPicker} 132 setOpenPicker={setOpenPicker} 133 /> 134 </div> 135 136 <SectionArrow fill="white" stroke="#CCCCCC" className="ml-2 -mt-px" /> 137 </div> 138 </div> 139 140 <div 141 onClick={(e) => { 142 e.currentTarget === e.target && setOpenPicker("leaflet"); 143 }} 144 style={{ 145 backgroundImage: leafletBGImage 146 ? `url(${leafletBGImage.data.src})` 147 : undefined, 148 backgroundRepeat: leafletBGRepeat ? "repeat" : "no-repeat", 149 backgroundPosition: "center", 150 backgroundSize: !leafletBGRepeat 151 ? "cover" 152 : `calc(${leafletBGRepeat.data.value}px / 2 )`, 153 }} 154 className={`bg-bg-leaflet px-3 pt-4 pb-0 mb-2 flex flex-col gap-4 rounded-md border border-border`} 155 > 156 <PageThemePickers 157 entityID={props.entityID} 158 openPicker={openPicker} 159 setOpenPicker={(pickers) => setOpenPicker(pickers)} 160 /> 161 <div className="flex flex-col -gap-[6px]"> 162 <div className={`flex flex-col z-10 -mb-[6px] `}> 163 <AccentPickers 164 entityID={props.entityID} 165 openPicker={openPicker} 166 setOpenPicker={(pickers) => setOpenPicker(pickers)} 167 /> 168 <SectionArrow 169 fill={theme.colors["accent-2"]} 170 stroke={theme.colors["accent-1"]} 171 className="ml-2" 172 /> 173 </div> 174 175 <SampleButton 176 entityID={props.entityID} 177 setOpenPicker={setOpenPicker} 178 /> 179 </div> 180 181 <SamplePage 182 setOpenPicker={setOpenPicker} 183 home={props.home} 184 entityID={props.entityID} 185 /> 186 </div> 187 {!props.home && <WatermarkSetter entityID={props.entityID} />} 188 </div> 189 ); 190}; 191function WatermarkSetter(props: { entityID: string }) { 192 let { rep } = useReplicache(); 193 let checked = useEntity(props.entityID, "theme/page-leaflet-watermark"); 194 195 function handleToggle() { 196 rep?.mutate.assertFact({ 197 entity: props.entityID, 198 attribute: "theme/page-leaflet-watermark", 199 data: { type: "boolean", value: !checked?.data.value }, 200 }); 201 } 202 return ( 203 <div className="flex gap-2 items-start mt-0.5"> 204 <Toggle 205 toggle={!!checked?.data.value} 206 onToggle={() => { 207 handleToggle(); 208 }} 209 disabledColor1="#8C8C8C" 210 disabledColor2="#DBDBDB" 211 > 212 <div className="flex flex-col gap-0 items-start "> 213 <div className="font-bold">Show Leaflet Watermark</div> 214 <div className="text-sm text-[#969696]">Help us spread the word!</div> 215 </div> 216 </Toggle> 217 </div> 218 ); 219} 220 221const SampleButton = (props: { 222 entityID: string; 223 setOpenPicker: (thisPicker: pickers) => void; 224}) => { 225 return ( 226 <div 227 onClick={(e) => { 228 e.target === e.currentTarget && props.setOpenPicker("accent-1"); 229 }} 230 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" 231 > 232 <div 233 className="cursor-pointer w-fit" 234 onClick={() => { 235 props.setOpenPicker("accent-2"); 236 }} 237 > 238 Example Button 239 </div> 240 </div> 241 ); 242}; 243const SamplePage = (props: { 244 entityID: string; 245 home: boolean | undefined; 246 setOpenPicker: (picker: "page" | "text") => void; 247}) => { 248 let pageBGImage = useEntity(props.entityID, "theme/card-background-image"); 249 let pageBGRepeat = useEntity( 250 props.entityID, 251 "theme/card-background-image-repeat", 252 ); 253 let pageBGOpacity = useEntity( 254 props.entityID, 255 "theme/card-background-image-opacity", 256 ); 257 let pageBorderHidden = useEntity(props.entityID, "theme/card-border-hidden") 258 ?.data.value; 259 260 return ( 261 <div 262 onClick={(e) => { 263 e.currentTarget === e.target && props.setOpenPicker("page"); 264 }} 265 className={` 266 text-primary relative 267 ${ 268 pageBorderHidden 269 ? "py-2 px-0 border border-transparent" 270 : `cursor-pointer p-2 border border-border border-b-transparent shadow-md 271 ${props.home ? "rounded-md " : "rounded-t-lg "}` 272 }`} 273 style={ 274 pageBorderHidden 275 ? undefined 276 : { 277 backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha))", 278 } 279 } 280 > 281 <div 282 className="background absolute top-0 right-0 bottom-0 left-0 z-0 rounded-t-lg" 283 style={ 284 pageBorderHidden 285 ? undefined 286 : { 287 backgroundImage: pageBGImage 288 ? `url(${pageBGImage.data.src})` 289 : undefined, 290 291 backgroundRepeat: pageBGRepeat ? "repeat" : "no-repeat", 292 opacity: pageBGOpacity?.data.value || 1, 293 backgroundSize: !pageBGRepeat 294 ? "cover" 295 : `calc(${pageBGRepeat.data.value}px / 2 )`, 296 } 297 } 298 /> 299 <div className="z-10 relative"> 300 <p 301 onClick={() => { 302 props.setOpenPicker("text"); 303 }} 304 className="cursor-pointer font-bold w-fit" 305 > 306 Hello! 307 </p> 308 <small onClick={() => props.setOpenPicker("text")}> 309 Welcome to{" "} 310 <span className="font-bold text-accent-contrast">Leaflet</span> a 311 fun and easy way to make, share, and collab on little bits of paper 312 </small> 313 </div> 314 </div> 315 ); 316}; 317 318export const SectionArrow = (props: { 319 fill: string; 320 stroke: string; 321 className: string; 322}) => { 323 return ( 324 <svg 325 width="24" 326 height="12" 327 viewBox="0 0 24 12" 328 fill="none" 329 xmlns="http://www.w3.org/2000/svg" 330 className={props.className} 331 > 332 <path d="M11.9999 12L24 0H0L11.9999 12Z" fill={props.fill} /> 333 <path 334 fillRule="evenodd" 335 clipRule="evenodd" 336 d="M1.33552 0L12 10.6645L22.6645 0H24L12 12L0 0H1.33552Z" 337 fill={props.stroke} 338 /> 339 </svg> 340 ); 341};