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>