replies timeline only, appview-less bluesky client
1<script lang="ts"> 2 import { defaultSettings, needsReload, settings } from '$lib/settings'; 3 import { handleCache, didDocCache, recordCache } from '$lib/at/client'; 4 import { get } from 'svelte/store'; 5 import ColorPicker from 'svelte-awesome-color-picker'; 6 import Popup from './Popup.svelte'; 7 import Tabs from './Tabs.svelte'; 8 9 interface Props { 10 isOpen: boolean; 11 onClose: () => void; 12 } 13 14 let { isOpen = $bindable(false), onClose }: Props = $props(); 15 16 type Tab = 'style' | 'moderation' | 'advanced'; 17 let activeTab = $state<Tab>('advanced'); 18 19 let localSettings = $state(get(settings)); 20 let hasReloadChanges = $derived(needsReload($settings, localSettings)); 21 22 $effect(() => { 23 $settings.theme = localSettings.theme; 24 }); 25 26 const resetSettingsToSaved = () => { 27 localSettings = $settings; 28 }; 29 30 const handleClose = () => { 31 resetSettingsToSaved(); 32 onClose(); 33 }; 34 35 const handleSave = () => { 36 settings.set(localSettings); 37 window.location.reload(); 38 }; 39 40 const handleReset = () => { 41 const confirmed = confirm('reset all settings to defaults?'); 42 if (!confirmed) return; 43 settings.reset(); 44 window.location.reload(); 45 }; 46 47 const handleClearCache = () => { 48 handleCache.clear(); 49 didDocCache.clear(); 50 recordCache.clear(); 51 alert('cache cleared!'); 52 }; 53</script> 54 55{#snippet divider()} 56 <div class="h-px bg-linear-to-r from-(--nucleus-accent) to-(--nucleus-accent2)"></div> 57{/snippet} 58 59{#snippet settingHeader(name: string, desc: string)} 60 <h3 class="mb-3 text-lg font-bold">{name}</h3> 61 <p class="mb-4 text-sm opacity-80">{desc}</p> 62{/snippet} 63 64{#snippet advancedTab()} 65 <div class="space-y-5"> 66 <div> 67 <h3 class="mb-3 text-lg font-bold">api endpoints</h3> 68 <div class="space-y-4"> 69 {#snippet _input(name: string, desc: string)} 70 <div> 71 <label for={name} class="mb-2 block text-sm font-semibold text-(--nucleus-fg)/80"> 72 {desc} 73 </label> 74 <input 75 id={name} 76 type="url" 77 bind:value={localSettings.endpoints[name]} 78 placeholder={defaultSettings.endpoints[name]} 79 class="single-line-input border-(--nucleus-accent)/40 bg-(--nucleus-accent)/3" 80 /> 81 </div> 82 {/snippet} 83 {@render _input('slingshot', 'slingshot url (for fetching records & resolving identity)')} 84 {@render _input('spacedust', 'spacedust url (for notifications)')} 85 {@render _input('constellation', 'constellation url (for backlinks)')} 86 </div> 87 </div> 88 89 {@render divider()} 90 91 <div> 92 <label for="social-app-url" class="mb-2 block text-sm font-semibold text-(--nucleus-fg)/80"> 93 social-app url (for when copying links to posts / profiles) 94 </label> 95 <input 96 id="social-app-url" 97 type="url" 98 bind:value={localSettings.socialAppUrl} 99 placeholder={defaultSettings.socialAppUrl} 100 class="single-line-input border-(--nucleus-accent)/40 bg-(--nucleus-accent)/3" 101 /> 102 </div> 103 104 {@render divider()} 105 106 <div> 107 {@render settingHeader( 108 'cache management', 109 'clears cached data (records, DID documents, handles, etc.)' 110 )} 111 <button onclick={handleClearCache} class="action-button"> clear cache </button> 112 </div> 113 114 {@render divider()} 115 116 <div> 117 {@render settingHeader('reset settings', 'resets all settings to their default values')} 118 <button 119 onclick={handleReset} 120 class="action-button border-red-600 text-red-600 hover:bg-red-600/20" 121 > 122 reset to defaults 123 </button> 124 </div> 125 </div> 126{/snippet} 127 128{#snippet styleTab()} 129 <div class="space-y-5"> 130 <div> 131 <h3 class="mb-3 text-lg font-bold">colors</h3> 132 <div class="space-y-4"> 133 {#snippet color(name: string, desc: string)} 134 <div> 135 <label for={name} class="mb-2 block text-sm font-semibold text-(--nucleus-fg)/80"> 136 {desc} 137 </label> 138 <div class="color-picker"> 139 <ColorPicker 140 bind:hex={localSettings.theme[name]} 141 isAlpha={false} 142 position="responsive" 143 label={localSettings.theme[name]} 144 /> 145 </div> 146 </div> 147 {/snippet} 148 {@render color('fg', 'foreground color')} 149 {@render color('bg', 'background color')} 150 {@render color('accent', 'accent color')} 151 {@render color('accent2', 'secondary accent color')} 152 </div> 153 </div> 154 </div> 155{/snippet} 156 157<Popup 158 bind:isOpen 159 onClose={handleClose} 160 title="settings" 161 width="w-[42vmax] max-w-2xl" 162 height="60vh" 163 showHeaderDivider={true} 164> 165 {#snippet headerActions()} 166 {#if hasReloadChanges} 167 <button onclick={handleSave} class="shrink-0 action-button"> save & reload </button> 168 {/if} 169 {/snippet} 170 171 {#if activeTab === 'advanced'} 172 {@render advancedTab()} 173 {:else if activeTab === 'moderation'} 174 <div class="flex h-full items-center justify-center"> 175 <div class="text-center"> 176 <div class="mb-4 text-6xl opacity-50">🚧</div> 177 <h3 class="text-xl font-bold opacity-80">todo</h3> 178 </div> 179 </div> 180 {:else if activeTab === 'style'} 181 {@render styleTab()} 182 {/if} 183 184 {#snippet footer()} 185 <Tabs 186 tabs={['style', 'moderation', 'advanced']} 187 bind:activeTab 188 onTabChange={(tab) => (activeTab = tab)} 189 /> 190 {/snippet} 191</Popup>