an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm
at button 5.3 kB view raw
1import { createFileRoute } from "@tanstack/react-router"; 2import { useAtom } from "jotai"; 3import { Slider } from "radix-ui"; 4 5import { Header } from "~/components/Header"; 6import Login from "~/components/Login"; 7import { 8 constellationURLAtom, 9 defaultconstellationURL, 10 defaulthue, 11 defaultImgCDN, 12 defaultslingshotURL, 13 defaultVideoCDN, 14 hueAtom, 15 imgCDNAtom, 16 slingshotURLAtom, 17 videoCDNAtom, 18} from "~/utils/atoms"; 19 20export const Route = createFileRoute("/settings")({ 21 component: Settings, 22}); 23 24export function Settings() { 25 return ( 26 <> 27 <Header 28 title="Settings" 29 backButtonCallback={() => { 30 if (window.history.length > 1) { 31 window.history.back(); 32 } else { 33 window.location.assign("/"); 34 } 35 }} 36 /> 37 <div className="lg:hidden"> 38 <Login /> 39 </div> 40 <div className="h-4" /> 41 <TextInputSetting 42 atom={constellationURLAtom} 43 title={"Constellation"} 44 description={ 45 "Customize the Constellation instance to be used by Red Dwarf" 46 } 47 init={defaultconstellationURL} 48 /> 49 <TextInputSetting 50 atom={slingshotURLAtom} 51 title={"Slingshot"} 52 description={"Customize the Slingshot instance to be used by Red Dwarf"} 53 init={defaultslingshotURL} 54 /> 55 <TextInputSetting 56 atom={imgCDNAtom} 57 title={"Image CDN"} 58 description={ 59 "Customize the Constellation instance to be used by Red Dwarf" 60 } 61 init={defaultImgCDN} 62 /> 63 <TextInputSetting 64 atom={videoCDNAtom} 65 title={"Video CDN"} 66 description={"Customize the Slingshot instance to be used by Red Dwarf"} 67 init={defaultVideoCDN} 68 /> 69 70 <Hue /> 71 <p className="text-gray-500 dark:text-gray-400 py-4 px-6 text-sm"> 72 please restart/refresh the app if changes arent applying correctly 73 </p> 74 </> 75 ); 76} 77function Hue() { 78 const [hue, setHue] = useAtom(hueAtom); 79 return ( 80 <div className="flex flex-col px-4 mt-4 "> 81 <span className="z-10">Hue</span> 82 <div className="flex flex-row items-center gap-4"> 83 <SliderComponent 84 atom={hueAtom} 85 max={360} 86 /> 87 <button 88 onClick={() => setHue(defaulthue ?? 28)} 89 className="px-6 py-2 h-12 rounded-full bg-gray-100 dark:bg-gray-800 90 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition" 91 > 92 Reset 93 </button> 94 </div> 95 </div> 96 ); 97} 98 99export function TextInputSetting({ 100 atom, 101 title, 102 description, 103 init, 104}: { 105 atom: typeof constellationURLAtom; 106 title?: string; 107 description?: string; 108 init?: string; 109}) { 110 const [value, setValue] = useAtom(atom); 111 return ( 112 <div className="flex flex-col gap-2 px-4 py-2"> 113 {/* <div> 114 {title && ( 115 <h3 className="text-sm font-medium text-gray-900 dark:text-gray-100"> 116 {title} 117 </h3> 118 )} 119 {description && ( 120 <p className="text-sm text-gray-500 dark:text-gray-400"> 121 {description} 122 </p> 123 )} 124 </div> */} 125 126 <div className="flex flex-row gap-2 items-center"> 127 <div className="m3input-field m3input-label m3input-border size-md flex-1"> 128 <input 129 type="text" 130 placeholder=" " 131 value={value} 132 onChange={(e) => setValue(e.target.value)} 133 /> 134 <label>{title}</label> 135 </div> 136 {/* <input 137 type="text" 138 value={value} 139 onChange={(e) => setValue(e.target.value)} 140 className="flex-1 px-3 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 141 text-gray-900 dark:text-gray-100 placeholder:text-gray-500 dark:placeholder:text-gray-400 142 focus:outline-none focus:ring-2 focus:ring-gray-400 dark:focus:ring-gray-600" 143 placeholder="Enter value..." 144 /> */} 145 <button 146 onClick={() => setValue(init ?? "")} 147 className="px-6 py-2 h-12 rounded-full bg-gray-100 dark:bg-gray-800 148 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition" 149 > 150 Reset 151 </button> 152 </div> 153 </div> 154 ); 155} 156 157 158interface SliderProps { 159 atom: typeof hueAtom; 160 min?: number; 161 max?: number; 162 step?: number; 163} 164 165export const SliderComponent: React.FC<SliderProps> = ({ 166 atom, 167 min = 0, 168 max = 100, 169 step = 1, 170}) => { 171 172 const [value, setValue] = useAtom(atom) 173 174 return ( 175 <Slider.Root 176 className="relative flex items-center w-full h-4" 177 value={[value]} 178 min={min} 179 max={max} 180 step={step} 181 onValueChange={(v: number[]) => setValue(v[0])} 182 > 183 <Slider.Track className="relative flex-grow h-4 bg-gray-300 dark:bg-gray-700 rounded-full"> 184 <Slider.Range className="absolute h-full bg-gray-500 dark:bg-gray-400 rounded-l-full rounded-r-none" /> 185 </Slider.Track> 186 <Slider.Thumb className="shadow-[0_0_0_8px_var(--color-white)] dark:shadow-[0_0_0_8px_var(--color-gray-950)] block w-[3px] h-12 bg-gray-500 dark:bg-gray-400 rounded-md focus:outline-none" /> 187 </Slider.Root> 188 ); 189};