a tool for shared writing and social publishing
at update/reader 314 lines 11 kB view raw
1import { pickers } from "../ThemeSetter"; 2import { theme } from "tailwind.config"; 3import { PageBackgroundColorPicker } from "../Pickers/PageThemePickers"; 4import { Color, ColorSwatch } from "react-aria-components"; 5import { BlockImageSmall } from "components/Icons/BlockImageSmall"; 6import { ColorPicker } from "../Pickers/ColorPicker"; 7import { CloseContrastSmall } from "components/Icons/CloseContrastSmall"; 8import * as Slider from "@radix-ui/react-slider"; 9import { Toggle } from "components/Toggle"; 10import { DeleteSmall } from "components/Icons/DeleteSmall"; 11import { ImageState } from "../PubThemeSetter"; 12import { Radio } from "components/Checkbox"; 13import { Input } from "components/Input"; 14 15export const BackgroundPicker = (props: { 16 backgroundColor: Color; 17 setBackgroundColor: (c: Color) => void; 18 pageBackground: Color; 19 setPageBackground: (c: Color) => void; 20 openPicker: pickers; 21 setOpenPicker: (p: pickers) => void; 22 bgImage: ImageState | null; 23 setBgImage: (i: ImageState | null) => void; 24 hasPageBackground: boolean; 25 setHasPageBackground: (s: boolean) => void; 26}) => { 27 // When showPageBackground is false (hasPageBackground=false) and no background image, show leafletBg picker 28 let showLeafletBgPicker = !props.hasPageBackground && !props.bgImage; 29 30 return ( 31 <> 32 {props.bgImage && props.bgImage !== null ? ( 33 <BackgroundImagePicker 34 bgColor={props.backgroundColor} 35 bgImage={props.bgImage} 36 setBgImage={props.setBgImage} 37 thisPicker={"page-background-image"} 38 openPicker={props.openPicker} 39 setOpenPicker={props.setOpenPicker} 40 closePicker={() => props.setOpenPicker("null")} 41 setValue={props.setBackgroundColor} 42 /> 43 ) : ( 44 <div className="relative"> 45 <ColorPicker 46 label={"Background"} 47 value={props.backgroundColor} 48 setValue={props.setBackgroundColor} 49 thisPicker={"leaflet"} 50 openPicker={props.openPicker} 51 setOpenPicker={props.setOpenPicker} 52 closePicker={() => props.setOpenPicker("null")} 53 alpha={!!props.bgImage} 54 /> 55 {!props.bgImage && ( 56 <label 57 className={` 58 text-[#969696] hover:cursor-pointer shrink-0 59 absolute top-0 right-0 60 `} 61 > 62 <BlockImageSmall /> 63 <div className="hidden"> 64 <input 65 type="file" 66 accept="image/*" 67 hidden 68 onChange={async (e) => { 69 let file = e.currentTarget.files?.[0]; 70 if (file) { 71 const reader = new FileReader(); 72 reader.onload = (e) => { 73 props.setBgImage({ 74 src: e.target?.result as string, 75 file, 76 repeat: null, 77 }); 78 props.setOpenPicker("page-background-image"); 79 }; 80 reader.readAsDataURL(file); 81 } 82 }} 83 /> 84 </div> 85 </label> 86 )} 87 </div> 88 )} 89 {!showLeafletBgPicker && ( 90 // When there's a background image and page background hidden, label should say "Containers" 91 <PageBackgroundColorPicker 92 label={props.hasPageBackground ? "Page" : "Containers"} 93 helpText={ 94 props.hasPageBackground 95 ? undefined 96 : "Affects menus, tooltips and some block backgrounds" 97 } 98 value={props.pageBackground} 99 setValue={props.setPageBackground} 100 thisPicker={"page"} 101 openPicker={props.openPicker} 102 setOpenPicker={props.setOpenPicker} 103 alpha={props.hasPageBackground ? true : false} 104 /> 105 )} 106 <hr className="border-border-light" /> 107 <div className="flex gap-2 items-center"> 108 <Toggle 109 toggle={props.hasPageBackground} 110 onToggle={() => { 111 props.setHasPageBackground(!props.hasPageBackground); 112 props.hasPageBackground && 113 props.openPicker === "page" && 114 props.setOpenPicker("null"); 115 }} 116 disabledColor1="#8C8C8C" 117 disabledColor2="#DBDBDB" 118 > 119 <div className="flex gap-2"> 120 <div className="font-bold">Page Background</div> 121 <div className="italic text-[#8C8C8C]"> 122 {props.hasPageBackground ? "" : "none"} 123 </div> 124 </div> 125 </Toggle> 126 </div> 127 </> 128 ); 129}; 130 131const BackgroundImagePicker = (props: { 132 disabled?: boolean; 133 bgImage: ImageState | null; 134 setBgImage: (i: ImageState | null) => void; 135 bgColor: Color; 136 openPicker: pickers; 137 thisPicker: pickers; 138 setOpenPicker: (thisPicker: pickers) => void; 139 closePicker: () => void; 140 setValue: (c: Color) => void; 141}) => { 142 let open = props.openPicker == props.thisPicker; 143 144 return ( 145 <> 146 <div className="bgPickerColorLabel flex gap-2 items-center"> 147 <button 148 disabled={props.disabled} 149 onClick={() => { 150 if (props.openPicker === props.thisPicker) { 151 props.setOpenPicker("null"); 152 } else { 153 props.setOpenPicker(props.thisPicker); 154 } 155 }} 156 className="flex gap-2 items-center disabled:text-tertiary grow" 157 > 158 <ColorSwatch 159 color={props.bgColor} 160 className={`w-6 h-6 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C] ${props.disabled ? "opacity-50" : ""}`} 161 style={{ 162 backgroundImage: props.bgImage 163 ? `url(${props.bgImage.src})` 164 : undefined, 165 backgroundPosition: "center", 166 backgroundSize: "cover", 167 }} 168 /> 169 <strong className={` text-[#595959]`}>Background</strong> 170 <div className="italic text-[#8C8C8C]">image</div> 171 </button> 172 <div className="flex gap-1 text-[#8C8C8C]"> 173 <button onClick={() => props.setBgImage(null)}> 174 <DeleteSmall /> 175 </button> 176 <label className="hover:cursor-pointer "> 177 <BlockImageSmall /> 178 <div className="hidden"> 179 <input 180 type="file" 181 accept="image/*" 182 hidden 183 onChange={async (e) => { 184 let file = e.currentTarget.files?.[0]; 185 if (file) { 186 const reader = new FileReader(); 187 reader.onload = (e) => { 188 if (!props.bgImage) return; 189 props.setBgImage({ 190 ...props.bgImage, 191 src: e.target?.result as string, 192 file, 193 }); 194 }; 195 reader.readAsDataURL(file); 196 } 197 }} 198 /> 199 </div> 200 </label> 201 </div> 202 </div> 203 {open && ( 204 <div className="pageImagePicker flex flex-col gap-2"> 205 <ImageSettings 206 bgImage={props.bgImage} 207 setBgImage={props.setBgImage} 208 /> 209 </div> 210 )} 211 </> 212 ); 213}; 214 215export const ImageSettings = (props: { 216 bgImage: ImageState | null; 217 setBgImage: (i: ImageState | null) => void; 218}) => { 219 return ( 220 <> 221 <div className="themeBGImageControls font-bold flex flex-col gap-1 items-center px-3"> 222 <label htmlFor="cover" className="w-full"> 223 <Radio 224 radioCheckedClassName="text-[#595959]!" 225 radioEmptyClassName="text-[#969696]!" 226 type="radio" 227 id="cover" 228 name="bg-image-options" 229 value="cover" 230 checked={!props.bgImage?.repeat} 231 onChange={async (e) => { 232 if (!e.currentTarget.checked) return; 233 if (!props.bgImage) return; 234 props.setBgImage({ ...props.bgImage, repeat: null }); 235 }} 236 > 237 <div 238 className={`w-full cursor-pointer ${!props.bgImage?.repeat ? "text-[#595959]" : " text-[#969696]"}`} 239 > 240 cover 241 </div> 242 </Radio> 243 </label> 244 <label htmlFor="repeat" className="pb-3 w-full"> 245 <Radio 246 type="radio" 247 id="repeat" 248 name="bg-image-options" 249 value="repeat" 250 radioCheckedClassName="text-[#595959]!" 251 radioEmptyClassName="text-[#969696]!" 252 checked={!!props.bgImage?.repeat} 253 onChange={async (e) => { 254 if (!e.currentTarget.checked) return; 255 if (!props.bgImage) return; 256 props.setBgImage({ ...props.bgImage, repeat: 500 }); 257 }} 258 > 259 <div className="flex flex-col w-full"> 260 <div className="flex gap-2"> 261 <div 262 className={`shink-0 grow-0 w-fit z-10 cursor-pointer ${props.bgImage?.repeat ? "text-[#595959]" : " text-[#969696]"}`} 263 > 264 repeat 265 </div> 266 <div 267 className={`flex font-normal ${props.bgImage?.repeat ? "text-[#969696]" : " text-[#C3C3C3]"}`} 268 > 269 <Input 270 type="number" 271 className="w-10 text-right appearance-none" 272 max={3000} 273 min={10} 274 value={props.bgImage?.repeat || 500} 275 onChange={(e) => { 276 if (!props.bgImage) return; 277 props.setBgImage({ 278 ...props.bgImage, 279 repeat: parseInt(e.currentTarget.value), 280 }); 281 }} 282 />{" "} 283 px 284 </div> 285 </div> 286 <Slider.Root 287 className={`relative grow flex items-center select-none touch-none w-full h-fit px-1 `} 288 value={[props.bgImage?.repeat || 500]} 289 max={3000} 290 min={10} 291 step={10} 292 onValueChange={(value) => { 293 if (!props.bgImage) return; 294 props.setBgImage({ ...props.bgImage, repeat: value[0] }); 295 }} 296 > 297 <Slider.Track 298 className={`${props.bgImage?.repeat ? "bg-[#595959]" : " bg-[#C3C3C3]"} relative grow rounded-full h-[3px] my-2`} 299 ></Slider.Track> 300 <Slider.Thumb 301 className={` 302 flex w-4 h-4 rounded-full border-2 border-white cursor-pointer 303 ${props.bgImage?.repeat ? "bg-[#595959]" : " bg-[#C3C3C3] "} 304 ${props.bgImage?.repeat && "shadow-[0_0_0_1px_#8C8C8C,inset_0_0_0_1px_#8C8C8C]"} `} 305 aria-label="Volume" 306 /> 307 </Slider.Root> 308 </div> 309 </Radio> 310 </label> 311 </div> 312 </> 313 ); 314};