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 {@render settingHeader( 93 'cache management', 94 'clears cached data (records, DID documents, handles, etc.)' 95 )} 96 <button onclick={handleClearCache} class="action-button"> clear cache </button> 97 </div> 98 99 {@render divider()} 100 101 <div> 102 {@render settingHeader('reset settings', 'resets all settings to their default values')} 103 <button 104 onclick={handleReset} 105 class="action-button border-red-600 text-red-600 hover:bg-red-600/20" 106 > 107 reset to defaults 108 </button> 109 </div> 110 </div> 111{/snippet} 112 113{#snippet styleTab()} 114 <div class="space-y-5"> 115 <div> 116 <h3 class="mb-3 text-lg font-bold">colors</h3> 117 <div class="space-y-4"> 118 {#snippet color(name: string, desc: string)} 119 <div> 120 <label for={name} class="mb-2 block text-sm font-semibold text-(--nucleus-fg)/80"> 121 {desc} 122 </label> 123 <div class="color-picker"> 124 <ColorPicker 125 bind:hex={localSettings.theme[name]} 126 isAlpha={false} 127 position="responsive" 128 label={localSettings.theme[name]} 129 /> 130 </div> 131 </div> 132 {/snippet} 133 {@render color('fg', 'foreground color')} 134 {@render color('bg', 'background color')} 135 {@render color('accent', 'accent color')} 136 {@render color('accent2', 'secondary accent color')} 137 </div> 138 </div> 139 </div> 140{/snippet} 141 142<Popup 143 bind:isOpen 144 onClose={handleClose} 145 title="settings" 146 width="w-[42vmax] max-w-2xl" 147 height="60vh" 148 showHeaderDivider={true} 149> 150 {#snippet headerActions()} 151 {#if hasReloadChanges} 152 <button onclick={handleSave} class="shrink-0 action-button"> save & reload </button> 153 {/if} 154 {/snippet} 155 156 {#if activeTab === 'advanced'} 157 {@render advancedTab()} 158 {:else if activeTab === 'moderation'} 159 <div class="flex h-full items-center justify-center"> 160 <div class="text-center"> 161 <div class="mb-4 text-6xl opacity-50">🚧</div> 162 <h3 class="text-xl font-bold opacity-80">todo</h3> 163 </div> 164 </div> 165 {:else if activeTab === 'style'} 166 {@render styleTab()} 167 {/if} 168 169 {#snippet footer()} 170 <Tabs 171 tabs={['style', 'moderation', 'advanced']} 172 bind:activeTab 173 onTabChange={(tab) => (activeTab = tab)} 174 /> 175 {/snippet} 176</Popup>