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"
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"
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>