its for when you want to get like notifications for your reposts
1import { createSignal, onCleanup, For, type Component } from "solid-js"; 2 3import type {} from "@atcute/bluesky"; 4import type {} from "@atcute/atproto"; 5import { AppProps, ConnectionStatus, Notification } from "./types.js"; 6import { ActivityItem } from "./ActivityItem.jsx"; 7import { connect as connectService } from "./ws.ts"; 8 9const Wrapped: Component = () => { 10 const [actorId, setActorId] = createSignal<string>(""); 11 const [serviceDomain, setWsUrl] = createSignal<string>("likes.gaze.systems"); 12 const [items, setItems] = createSignal<Notification[]>([]); 13 const [connectionStatus, setConnectionStatus] = 14 createSignal<ConnectionStatus>("disconnected"); 15 const [error, setError] = createSignal<string | null>(null); 16 const [ws, setWs] = createSignal<WebSocket | null>(null); 17 18 const connect = async () => { 19 // close existing connection if any 20 ws()?.close(); 21 setWs( 22 (await connectService({ 23 actorId: actorId(), 24 serviceDomain: serviceDomain(), 25 pushNotification: (item) => setItems((prev) => [item, ...prev]), 26 setConnectionStatus, 27 setError, 28 })) ?? null, 29 ); 30 }; 31 32 const disconnect = (): void => { 33 setConnectionStatus("disconnecting..."); 34 ws()?.close(); 35 setWs(null); 36 }; 37 38 onCleanup(disconnect); 39 40 const props: AppProps = { 41 actorIdSignal: [actorId, setActorId], 42 serviceDomainSignal: [serviceDomain, setWsUrl], 43 items, 44 clearItems: () => { 45 setItems([]); 46 }, 47 connectionStatus, 48 error, 49 connect, 50 disconnect, 51 isExtension: false, 52 }; 53 54 return <App {...props} />; 55}; 56export default Wrapped; 57 58export const App: Component<AppProps> = (props) => { 59 const [actorId, setActorId] = props.actorIdSignal; 60 const [serviceDomain, setWsUrl] = props.serviceDomainSignal; 61 const { disconnect, connect, error, connectionStatus, items, clearItems } = 62 props; 63 64 const isConnected = () => { 65 return ( 66 connectionStatus() == "connecting..." || connectionStatus() == "connected" 67 ); 68 }; 69 70 const inputStyle = `flex-1 ${props.isExtension ? "px-2 py-1" : "px-4 py-2"} border border-gray-300 rounded-none bg-white focus:(outline-none ring-2)`; 71 72 return ( 73 <div max-w-4xl mx-auto p-2 bg-gray-50 min-h-screen> 74 {props.isExtension ? ( 75 <></> 76 ) : ( 77 <h1 border="l-16 blue" font-bold text="3xl gray-800" pl-2 mb-6> 78 monitor bluesky repost likes 79 </h1> 80 )} 81 82 {/* connection */} 83 <div mb-6> 84 <div flex gap-2 mb-2> 85 <input 86 type="text" 87 value={serviceDomain()} 88 onInput={(e) => setWsUrl((e.target as HTMLInputElement).value)} 89 placeholder="enter service host (e.g., likes.gaze.systems)" 90 class={`${inputStyle} focus-ring-purple-500`} 91 disabled={isConnected()} 92 /> 93 </div> 94 <div flex gap-2 mb-2> 95 <input 96 type="text" 97 value={actorId()} 98 onInput={(e) => setActorId((e.target as HTMLInputElement).value)} 99 onKeyPress={(e) => { 100 if (!isConnected() && e.key == "Enter") { 101 connect(); 102 e.preventDefault(); 103 } 104 }} 105 placeholder="enter handle or DID" 106 class={`${inputStyle} focus-ring-blue-500`} 107 disabled={isConnected()} 108 /> 109 <button 110 onClick={() => (isConnected() ? disconnect() : connect())} 111 class={`${props.isExtension ? "px-3 py-1" : "px-6 py-2"} rounded-none font-medium transition-colors ${ 112 isConnected() 113 ? "bg-red-500 hover:bg-red-600 text-white" 114 : "bg-blue-500 hover:bg-blue-600 text-white" 115 }`} 116 > 117 {isConnected() ? "disconnect" : "connect"} 118 </button> 119 </div> 120 121 {/* Status indicator */} 122 <div flex gap-2 items-center> 123 <div w-fit border border-gray-300 bg-gray-80 px-1 py="0.5"> 124 <div 125 inline-block 126 w-3 127 h-3 128 rounded-full 129 class={ 130 connectionStatus() === "connected" 131 ? "bg-green-500" 132 : connectionStatus() === "connecting..." 133 ? "bg-yellow-500" 134 : connectionStatus() === "error" 135 ? "bg-red-500" 136 : "bg-gray-400" 137 } 138 /> 139 <span 140 ml-2 141 align="10%" 142 text={`${props.isExtension ? "xs" : "sm"} gray-600`} 143 > 144 status: {connectionStatus()} 145 </span> 146 </div> 147 {error() && ( 148 <div w-fit border border-gray-300 bg-gray-80 p-1> 149 <div text={`${props.isExtension ? "xs" : "sm"} red-500`}> 150 {error()} 151 </div> 152 </div> 153 )} 154 </div> 155 </div> 156 157 {/* feed */} 158 <div> 159 <div class="flex justify-between items-center mb-4"> 160 <h2 161 border="l-8 blue" 162 pl-2 163 text={`${props.isExtension ? "base" : "xl"} gray-700`} 164 font-semibold 165 > 166 activity feed ({items().length}) 167 </h2> 168 <button 169 onClick={clearItems} 170 text={`white ${props.isExtension ? "xs" : "sm"}`} 171 class={`${props.isExtension ? "px-2 py-1" : "px-4 py-2"} ml-1 bg-gray-500 hover:bg-gray-600 rounded-none transition-colors disabled:opacity-50`} 172 disabled={items().length === 0} 173 > 174 clear feed 175 </button> 176 </div> 177 178 <div 179 class={`${props.isExtension ? "p-2" : "p-4"} h-[60vh] max-h-[60vh] overflow-y-auto border border-gray-200 rounded-none bg-white`} 180 > 181 {items().length === 0 ? ( 182 <div flex items-center w-full h-full> 183 <div mx-auto text="center gray-500"> 184 <div text-lg mb-2> 185 👀 186 </div> 187 <div> 188 nothing yet. connect and wait for someone to like a repost of 189 yours! 190 </div> 191 </div> 192 </div> 193 ) : ( 194 <For each={items()}> 195 {(item, index) => ( 196 <div mb={index() == items().length - 1 ? "0" : "2"}> 197 <ActivityItem data={item} isExtension={props.isExtension} /> 198 </div> 199 )} 200 </For> 201 )} 202 </div> 203 </div> 204 205 {props.isExtension ? ( 206 <></> 207 ) : ( 208 <div border bg-blue-50 border-blue-200 rounded-none pl="1.5" p-1 mt-4> 209 <span text="xs blue-800" align="10%"> 210 <span text-pink-400>source</span> <span text-gray>=</span>{" "} 211 <a 212 href="https://tangled.sh/@poor.dog/bsky-repost-likes" 213 text-orange-700 214 hover:text-orange-400 215 > 216 "https://tangled.sh/@poor.dog/bsky-repost-likes" 217 </a>{" "} 218 // made by{" "} 219 <a text-purple-700 hover:text-purple href="https://gaze.systems"> 220 dusk 221 </a> 222 </span> 223 </div> 224 )} 225 </div> 226 ); 227};