A frontend for your PDS
at main 201 lines 6.6 kB view raw
1<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> 2 3<script lang="ts"> 4 window.ZOHOIM=window.ZOHOIM||function(a,b){ZOHOIM[a]=b;};window.ZOHOIM.prefilledMessage="";(function(){var d=document;var s=d.createElement('script');s.type='text/javascript';s.nonce='7232560246';s.defer=true;s.src="https://im.zoho.eu/api/v1/public/channel/3cd7bfa34e04555089f0c7dbfe57ed64/widget";d.getElementsByTagName('head')[0].appendChild(s); })() 5 6 import PostComponent from "./lib/PostComponent.svelte"; 7 import AccountComponent from "./lib/AccountComponent.svelte"; 8 import InfiniteLoading from "svelte-infinite-loading"; 9 import { getNextPosts, Post, getAllMetadataFromPds, fetchPostsForUser } from "./lib/pdsfetch"; 10 import { getContributors, getHeatmapData } from "./lib/tcapifetch" 11 import { Config } from "../config"; 12 import { onMount } from "svelte"; 13 import type { ComAtprotoRepoListRecords } from "@atcute/client/lexicons"; 14 import Heatmap from "svelte5-heatmap"; 15 import ContributorsModal from "./lib/ContributorsModal.svelte"; 16 import DarkModeToggle from "./lib/DarkModeToggle.svelte"; 17 import { isDark } from './lib/theme'; 18 19 let showModal = false; 20 21 let posts: Post[] = []; 22 let postsLoaded = false; 23 24 let heatmapData: Record<string, number> = {}; 25 let year = new Date().getFullYear(); 26 let accountsData: any[] = []; 27 $: latestVisibleAccount = accountsData 28 ?.slice() 29 .reverse() 30 .find(a => a?.hiddenFromHomepage !== true); 31 let accountsError: Error | null = null; 32 let accountsLoaded = false; 33 34 let contributors: any[] = []; 35 36 let hue: number = 1; 37 const cycleColors = async () => { 38 while (true) { 39 hue += 1; 40 if (hue > 360) { 41 hue = 0; 42 } 43 document.documentElement.style.setProperty("--primary-h", hue.toString()); 44 await new Promise((resolve) => setTimeout(resolve, 10)); 45 } 46 }; 47 48 let clickCounter = 0; 49 const carameldansenfusion = async () => { 50 clickCounter++; 51 if (clickCounter >= 10) { 52 clickCounter = 0; 53 cycleColors(); 54 } 55 }; 56 57onMount(async () => { 58 try { 59 // Load critical data first 60 accountsData = await getAllMetadataFromPds(); 61 accountsLoaded = true; 62 } catch (error: unknown) { 63 accountsError = error instanceof Error ? error : new Error(String(error)); 64 } 65 66 getNextPosts() 67 .then(initialPosts => { 68 posts = [...posts, ...initialPosts]; 69 postsLoaded = true; 70 }) 71 .catch(err => console.error("Error fetching posts:", err)); 72 73 getHeatmapData() 74 .then(data => { 75 heatmapData = data; 76 }) 77 .catch(err => console.error("Error fetching heatmap data:", err)); 78 79 getContributors() 80 .then(data => { 81 contributors = data; 82 }) 83 .catch(err => console.error("Error fetching contributors:", err)); 84}); 85 86 87 88 const onInfinite = ({ 89 detail: { loaded, complete }, 90 }: { 91 detail: { loaded: () => void; complete: () => void }; 92 }) => { 93 if (!postsLoaded) { 94 console.warn("Infinite scroll triggered before initial posts loaded."); 95 return; 96 } 97 98 getNextPosts().then((newPosts) => { 99 if (newPosts.length > 0) { 100 posts = [...posts, ...newPosts]; 101 loaded(); 102 } else { 103 complete(); 104 } 105 }); 106 }; 107 108</script> 109 110<main> 111 <div id="Content"> 112 {#if !accountsLoaded && !accountsError} 113 <p>Loading...</p> 114 {:else if accountsError} 115 <p>Error: {accountsError.message}</p> 116 {:else} 117 <div id="Account"> 118 <div> 119 120 <img 121 src={ 122 $isDark 123 ? "https://public-blob.tophhie.cloud/logos/tophhiecloud-white.png" 124 : "https://public-blob.tophhie.cloud/logos/tophhiecloud-colour.png" 125 } 126 height="50" 127 alt="Tophhie Social Logo" 128 id="Logo" 129 style="padding-top:15px;" 130 /> 131 <h1 onclick={carameldansenfusion} id="Header">Tophhie Social</h1> 132 <p>Home to {accountsData.length} accounts/repos 🎉</p> 133 </div> 134 <div class="button-group"> 135 <a href="https://signup.tophhie.social" class="call-to-action">Sign up now!</a> 136 <a href="https://migrate.tophhie.social" class="call-to-action">Migrate your Bluesky account!</a> 137 <a href="https://discord.gg/YD8sF8JsCJ" class="call-to-action">Join the Tophhie Cloud Discord server!</a> 138 <a href="https://status.tophhie.social" class="call-to-action">Server Status</a> 139 <a href="https://ko-fi.com/tophhie" class="call-to-action">Support us and donate</a> 140 </div> 141 <div id="accountsList"> 142 {#each accountsData as accountObject} 143 {#if !accountObject.hiddenFromHomepage} 144 <AccountComponent account={accountObject} /> 145 {/if} 146 {/each} 147 </div> 148 <p class="disclaimer-footer"> 149 {@html Config.FOOTER_TEXT} 150 <br /> 151 Thank you also to our <a href="/#" onclick={() => (showModal = true)}>contributors!</a> 152 </p> 153 </div> 154 {/if} 155 156 <div id="Feed"> 157 {#if heatmapData} 158 <div id="postContainer" style="padding:20px;"> 159 <a style={window.innerWidth <= 768 ? "padding-bottom: 10px" : ""}>Tophhie Social Posts</a> 160 <Heatmap data={heatmapData} {year} lday={false} lmonth={window.innerWidth >= 768} /> 161 </div> 162 {/if} 163 {#if accountsLoaded && latestVisibleAccount} 164 <AccountComponent account={latestVisibleAccount} welcome /> 165 {/if} 166 {#each posts as postObject} 167 <PostComponent post={postObject as Post} /> 168 {/each} 169 <InfiniteLoading on:infinite={onInfinite} distance={3000} /> 170 </div> 171 </div> 172 <ContributorsModal bind:showModal> 173 {#snippet header()} 174 <p id="Header" style="font-size:20px; padding: 10px;"> 175 Thank you to everyone who has contributed to the Tophhie Social dashboard! 176 </p> 177 {/snippet} 178 179 <ul class="contributor-list"> 180 {#each contributors as contributor} 181 <li> 182 {#if contributor.avatar_url} 183 <img 184 alt="Avatar of {contributor.login}" 185 src="{contributor.avatar_url}" 186 id="avatar" 187 /> 188 {/if} 189 <p>{contributor.login}{contributor.contributions} contributions</p> 190 <a href="https://github.com/{contributor.login}"><i class="fa fa-brands fa-github" style="font-size: 20px"></i></a> 191 </li> 192 {/each} 193 </ul> 194 </ContributorsModal> 195 196 <!-- Dark Mode Toggle --> 197 <DarkModeToggle /> 198</main> 199 200<style> 201</style>