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