SettingsModal.svelte 12.2 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
<script lang="ts">
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
2
	import { getContext, tick } from 'svelte';
Jannik Streidl's avatar
Jannik Streidl committed
3
	import { toast } from 'svelte-sonner';
Timothy J. Baek's avatar
Timothy J. Baek committed
4
	import { models, settings, user } from '$lib/stores';
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
5
	import { updateUserSettings } from '$lib/apis/users';
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
6
	import { getModels as _getModels } from '$lib/apis';
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
7
	import { goto } from '$app/navigation';
Timothy J. Baek's avatar
Timothy J. Baek committed
8
9

	import Modal from '../common/Modal.svelte';
10
	import Account from './Settings/Account.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
11
12
13
	import About from './Settings/About.svelte';
	import General from './Settings/General.svelte';
	import Interface from './Settings/Interface.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
14
	import Audio from './Settings/Audio.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
15
	import Chats from './Settings/Chats.svelte';
16
17
	import User from '../icons/User.svelte';
	import Personalization from './Settings/Personalization.svelte';
18

19
20
	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
21
	export let show = false;
22
23
24
25

	const saveSettings = async (updated) => {
		console.log(updated);
		await settings.set({ ...$settings, ...updated });
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
26
		await models.set(await getModels());
27
		await updateUserSettings(localStorage.token, { ui: $settings });
28
	};
29

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
30
31
	const getModels = async () => {
		return await _getModels(localStorage.token);
32
33
	};

34
	let selectedTab = 'general';
35
36

	// Function to handle sideways scrolling
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
37
	const scrollHandler = (event) => {
38
39
		const settingsTabsContainer = document.getElementById('settings-tabs-container');
		if (settingsTabsContainer) {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
40
41
			event.preventDefault(); // Prevent default vertical scrolling
			settingsTabsContainer.scrollLeft += event.deltaY; // Scroll sideways
42
43
44
		}
	};

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
45
46
47
48
49
	const addScrollListener = async () => {
		await tick();
		const settingsTabsContainer = document.getElementById('settings-tabs-container');
		if (settingsTabsContainer) {
			settingsTabsContainer.addEventListener('wheel', scrollHandler);
50
		}
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
51
52
53
54
55
56
57
58
59
	};

	const removeScrollListener = async () => {
		await tick();
		const settingsTabsContainer = document.getElementById('settings-tabs-container');
		if (settingsTabsContainer) {
			settingsTabsContainer.removeEventListener('wheel', scrollHandler);
		}
	};
60

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
61
62
63
64
65
	$: if (show) {
		addScrollListener();
	} else {
		removeScrollListener();
	}
Timothy J. Baek's avatar
Timothy J. Baek committed
66
67
68
</script>

<Modal bind:show>
Timothy J. Baek's avatar
Timothy J. Baek committed
69
	<div class="text-gray-700 dark:text-gray-100">
Timothy J. Baek's avatar
Timothy J. Baek committed
70
		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
71
			<div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
			<button
				class="self-center"
				on:click={() => {
					show = false;
				}}
			>
				<svg
					xmlns="http://www.w3.org/2000/svg"
					viewBox="0 0 20 20"
					fill="currentColor"
					class="w-5 h-5"
				>
					<path
						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
					/>
				</svg>
			</button>
		</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
91
92
		<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4">
			<div
93
				id="settings-tabs-container"
Timothy J. Baek's avatar
Timothy J. Baek committed
94
				class="tabs flex flex-row overflow-x-auto space-x-1 md:space-x-0 md:space-y-1 md:flex-col flex-1 md:flex-none md:w-40 dark:text-gray-200 text-xs text-left mb-3 md:mb-0"
Timothy J. Baek's avatar
Timothy J. Baek committed
95
96
			>
				<button
97
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
Timothy J. Baek's avatar
Timothy J. Baek committed
98
					'general'
Timothy J. Baek's avatar
Timothy J. Baek committed
99
100
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
Timothy J. Baek's avatar
Timothy J. Baek committed
101
					on:click={() => {
102
						selectedTab = 'general';
Timothy J. Baek's avatar
Timothy J. Baek committed
103
					}}
Timothy J. Baek's avatar
Timothy J. Baek committed
104
				>
Timothy J. Baek's avatar
Timothy J. Baek committed
105
106
107
108
109
110
111
112
113
114
115
116
117
118
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 20 20"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								fill-rule="evenodd"
								d="M8.34 1.804A1 1 0 019.32 1h1.36a1 1 0 01.98.804l.295 1.473c.497.144.971.342 1.416.587l1.25-.834a1 1 0 011.262.125l.962.962a1 1 0 01.125 1.262l-.834 1.25c.245.445.443.919.587 1.416l1.473.294a1 1 0 01.804.98v1.361a1 1 0 01-.804.98l-1.473.295a6.95 6.95 0 01-.587 1.416l.834 1.25a1 1 0 01-.125 1.262l-.962.962a1 1 0 01-1.262.125l-1.25-.834a6.953 6.953 0 01-1.416.587l-.294 1.473a1 1 0 01-.98.804H9.32a1 1 0 01-.98-.804l-.295-1.473a6.957 6.957 0 01-1.416-.587l-1.25.834a1 1 0 01-1.262-.125l-.962-.962a1 1 0 01-.125-1.262l.834-1.25a6.957 6.957 0 01-.587-1.416l-1.473-.294A1 1 0 011 10.68V9.32a1 1 0 01.804-.98l1.473-.295c.144-.497.342-.971.587-1.416l-.834-1.25a1 1 0 01.125-1.262l.962-.962A1 1 0 015.38 3.03l1.25.834a6.957 6.957 0 011.416-.587l.294-1.473zM13 10a3 3 0 11-6 0 3 3 0 016 0z"
								clip-rule="evenodd"
							/>
						</svg>
					</div>
119
					<div class=" self-center">{$i18n.t('General')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
120
121
				</button>

122
				<button
123
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
Timothy J. Baek's avatar
Timothy J. Baek committed
124
					'interface'
Timothy J. Baek's avatar
Timothy J. Baek committed
125
126
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
127
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
128
						selectedTab = 'interface';
129
130
131
132
133
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
134
							viewBox="0 0 16 16"
135
136
137
138
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
Timothy J. Baek's avatar
Timothy J. Baek committed
139
								fill-rule="evenodd"
Timothy J. Baek's avatar
Timothy J. Baek committed
140
								d="M2 4.25A2.25 2.25 0 0 1 4.25 2h7.5A2.25 2.25 0 0 1 14 4.25v5.5A2.25 2.25 0 0 1 11.75 12h-1.312c.1.128.21.248.328.36a.75.75 0 0 1 .234.545v.345a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1-.75-.75v-.345a.75.75 0 0 1 .234-.545c.118-.111.228-.232.328-.36H4.25A2.25 2.25 0 0 1 2 9.75v-5.5Zm2.25-.75a.75.75 0 0 0-.75.75v4.5c0 .414.336.75.75.75h7.5a.75.75 0 0 0 .75-.75v-4.5a.75.75 0 0 0-.75-.75h-7.5Z"
Timothy J. Baek's avatar
Timothy J. Baek committed
141
142
143
144
								clip-rule="evenodd"
							/>
						</svg>
					</div>
145
					<div class=" self-center">{$i18n.t('Interface')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
146
147
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
148
149
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
150
					'personalization'
Timothy J. Baek's avatar
Timothy J. Baek committed
151
152
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
Timothy J. Baek's avatar
Timothy J. Baek committed
153
					on:click={() => {
154
						selectedTab = 'personalization';
Timothy J. Baek's avatar
Timothy J. Baek committed
155
156
157
					}}
				>
					<div class=" self-center mr-2">
158
						<User />
Timothy J. Baek's avatar
Timothy J. Baek committed
159
					</div>
160
					<div class=" self-center">{$i18n.t('Personalization')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
161
162
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
163
164
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
Timothy J. Baek's avatar
Timothy J. Baek committed
165
					'audio'
Timothy J. Baek's avatar
Timothy J. Baek committed
166
167
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
Timothy J. Baek's avatar
Timothy J. Baek committed
168
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
169
						selectedTab = 'audio';
Timothy J. Baek's avatar
Timothy J. Baek committed
170
171
172
173
174
175
176
177
178
179
180
181
182
183
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 16 16"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								d="M7.557 2.066A.75.75 0 0 1 8 2.75v10.5a.75.75 0 0 1-1.248.56L3.59 11H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.59l3.162-2.81a.75.75 0 0 1 .805-.124ZM12.95 3.05a.75.75 0 1 0-1.06 1.06 5.5 5.5 0 0 1 0 7.78.75.75 0 1 0 1.06 1.06 7 7 0 0 0 0-9.9Z"
							/>
							<path
								d="M10.828 5.172a.75.75 0 1 0-1.06 1.06 2.5 2.5 0 0 1 0 3.536.75.75 0 1 0 1.06 1.06 4 4 0 0 0 0-5.656Z"
184
185
186
							/>
						</svg>
					</div>
187
					<div class=" self-center">{$i18n.t('Audio')}</div>
188
				</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
189

190
191
192
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'chats'
Timothy J. Baek's avatar
Timothy J. Baek committed
193
194
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
					on:click={() => {
						selectedTab = 'chats';
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 16 16"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								fill-rule="evenodd"
								d="M8 2C4.262 2 1 4.57 1 8c0 1.86.98 3.486 2.455 4.566a3.472 3.472 0 0 1-.469 1.26.75.75 0 0 0 .713 1.14 6.961 6.961 0 0 0 3.06-1.06c.403.062.818.094 1.241.094 3.738 0 7-2.57 7-6s-3.262-6-7-6ZM5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm7-1a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM8 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
								clip-rule="evenodd"
							/>
						</svg>
					</div>
213
					<div class=" self-center">{$i18n.t('Chats')}</div>
214
215
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
216
217
218
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'account'
Timothy J. Baek's avatar
Timothy J. Baek committed
219
220
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
Timothy J. Baek's avatar
Timothy J. Baek committed
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
					on:click={() => {
						selectedTab = 'account';
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 16 16"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								fill-rule="evenodd"
								d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0Zm-5-2a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM8 9c-1.825 0-3.422.977-4.295 2.437A5.49 5.49 0 0 0 8 13.5a5.49 5.49 0 0 0 4.294-2.063A4.997 4.997 0 0 0 8 9Z"
								clip-rule="evenodd"
							/>
						</svg>
					</div>
239
					<div class=" self-center">{$i18n.t('Account')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
240
241
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
242
243
244
245
				{#if $user.role === 'admin'}
					<button
						class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
						'admin'
Timothy J. Baek's avatar
Timothy J. Baek committed
246
247
							? 'bg-gray-200 dark:bg-gray-800'
							: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
Timothy J. Baek's avatar
Timothy J. Baek committed
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
						on:click={async () => {
							await goto('/admin/settings');
							show = false;
						}}
					>
						<div class=" self-center mr-2">
							<svg
								xmlns="http://www.w3.org/2000/svg"
								viewBox="0 0 24 24"
								fill="currentColor"
								class="size-4"
							>
								<path
									fill-rule="evenodd"
									d="M4.5 3.75a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h15a3 3 0 0 0 3-3V6.75a3 3 0 0 0-3-3h-15Zm4.125 3a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Zm-3.873 8.703a4.126 4.126 0 0 1 7.746 0 .75.75 0 0 1-.351.92 7.47 7.47 0 0 1-3.522.877 7.47 7.47 0 0 1-3.522-.877.75.75 0 0 1-.351-.92ZM15 8.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15ZM14.25 12a.75.75 0 0 1 .75-.75h3.75a.75.75 0 0 1 0 1.5H15a.75.75 0 0 1-.75-.75Zm.75 2.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15Z"
									clip-rule="evenodd"
								/>
							</svg>
						</div>
						<div class=" self-center">{$i18n.t('Admin Settings')}</div>
					</button>
				{/if}

Timothy J. Baek's avatar
Timothy J. Baek committed
271
272
273
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'about'
Timothy J. Baek's avatar
Timothy J. Baek committed
274
275
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
Timothy J. Baek's avatar
Timothy J. Baek committed
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
					on:click={() => {
						selectedTab = 'about';
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 20 20"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								fill-rule="evenodd"
								d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
								clip-rule="evenodd"
							/>
						</svg>
					</div>
294
					<div class=" self-center">{$i18n.t('About')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
295
				</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
296
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
297
			<div class="flex-1 md:min-h-[28rem]">
298
				{#if selectedTab === 'general'}
Timothy J. Baek's avatar
Timothy J. Baek committed
299
					<General
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
300
						{getModels}
Timothy J. Baek's avatar
Timothy J. Baek committed
301
302
						{saveSettings}
						on:save={() => {
303
							toast.success($i18n.t('Settings saved successfully!'));
Timothy J. Baek's avatar
Timothy J. Baek committed
304
305
						}}
					/>
306
				{:else if selectedTab === 'interface'}
Timothy J. Baek's avatar
Timothy J. Baek committed
307
					<Interface
Timothy J. Baek's avatar
Timothy J. Baek committed
308
						{saveSettings}
Timothy J. Baek's avatar
Timothy J. Baek committed
309
						on:save={() => {
310
							toast.success($i18n.t('Settings saved successfully!'));
311
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
312
					/>
313
314
315
316
317
318
319
				{:else if selectedTab === 'personalization'}
					<Personalization
						{saveSettings}
						on:save={() => {
							toast.success($i18n.t('Settings saved successfully!'));
						}}
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
320
321
				{:else if selectedTab === 'audio'}
					<Audio
Timothy J. Baek's avatar
Timothy J. Baek committed
322
323
						{saveSettings}
						on:save={() => {
324
							toast.success($i18n.t('Settings saved successfully!'));
Timothy J. Baek's avatar
Timothy J. Baek committed
325
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
326
					/>
327
				{:else if selectedTab === 'chats'}
Timothy J. Baek's avatar
Timothy J. Baek committed
328
					<Chats {saveSettings} />
329
				{:else if selectedTab === 'account'}
330
331
					<Account
						saveHandler={() => {
332
							toast.success($i18n.t('Settings saved successfully!'));
333
						}}
334
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
335
				{:else if selectedTab === 'about'}
Timothy J. Baek's avatar
Timothy J. Baek committed
336
					<About />
Timothy J. Baek's avatar
Timothy J. Baek committed
337
338
				{/if}
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
339
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
340
341
	</div>
</Modal>
342
343
344
345
346
347
348
349
350

<style>
	input::-webkit-outer-spin-button,
	input::-webkit-inner-spin-button {
		/* display: none; <- Crashes Chrome on hover */
		-webkit-appearance: none;
		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
	}

351
352
353
354
355
356
357
358
359
	.tabs::-webkit-scrollbar {
		display: none; /* for Chrome, Safari and Opera */
	}

	.tabs {
		-ms-overflow-style: none; /* IE and Edge */
		scrollbar-width: none; /* Firefox */
	}

360
361
362
363
	input[type='number'] {
		-moz-appearance: textfield; /* Firefox */
	}
</style>