SettingsModal.svelte 11.3 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
<script lang="ts">
2
	import { getContext } 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';
5

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
6
	import { getModels as _getModels } from '$lib/apis';
Timothy J. Baek's avatar
Timothy J. Baek committed
7
8

	import Modal from '../common/Modal.svelte';
9
	import Account from './Settings/Account.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
10
11
12
	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
13
	import Audio from './Settings/Audio.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
14
	import Chats from './Settings/Chats.svelte';
15
16
	import User from '../icons/User.svelte';
	import Personalization from './Settings/Personalization.svelte';
17
	import { updateUserSettings } from '$lib/apis/users';
Timothy J. Baek's avatar
Timothy J. Baek committed
18
	import { goto } from '$app/navigation';
19

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

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

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

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

35
	let selectedTab = 'general';
Timothy J. Baek's avatar
Timothy J. Baek committed
36
37
38
</script>

<Modal bind:show>
Timothy J. Baek's avatar
Timothy J. Baek committed
39
	<div class="text-gray-700 dark:text-gray-100">
Timothy J. Baek's avatar
Timothy J. Baek committed
40
		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
41
			<div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
			<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
61
62
		<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4">
			<div
Timothy J. Baek's avatar
Timothy J. Baek committed
63
				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
64
65
			>
				<button
66
					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
67
					'general'
Timothy J. Baek's avatar
Timothy J. Baek committed
68
69
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
Timothy J. Baek's avatar
Timothy J. Baek committed
70
					on:click={() => {
71
						selectedTab = 'general';
Timothy J. Baek's avatar
Timothy J. Baek committed
72
					}}
Timothy J. Baek's avatar
Timothy J. Baek committed
73
				>
Timothy J. Baek's avatar
Timothy J. Baek committed
74
75
76
77
78
79
80
81
82
83
84
85
86
87
					<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>
88
					<div class=" self-center">{$i18n.t('General')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
89
90
				</button>

91
				<button
92
					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
93
					'interface'
Timothy J. Baek's avatar
Timothy J. Baek committed
94
95
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
96
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
97
						selectedTab = 'interface';
98
99
100
101
102
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
103
							viewBox="0 0 16 16"
104
105
106
107
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
Timothy J. Baek's avatar
Timothy J. Baek committed
108
								fill-rule="evenodd"
Timothy J. Baek's avatar
Timothy J. Baek committed
109
								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
110
111
112
113
								clip-rule="evenodd"
							/>
						</svg>
					</div>
114
					<div class=" self-center">{$i18n.t('Interface')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
115
116
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
117
118
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
119
					'personalization'
Timothy J. Baek's avatar
Timothy J. Baek committed
120
121
122
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
					on:click={() => {
123
						selectedTab = 'personalization';
Timothy J. Baek's avatar
Timothy J. Baek committed
124
125
126
					}}
				>
					<div class=" self-center mr-2">
127
						<User />
Timothy J. Baek's avatar
Timothy J. Baek committed
128
					</div>
129
					<div class=" self-center">{$i18n.t('Personalization')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
130
131
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
132
133
				<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
134
					'audio'
Timothy J. Baek's avatar
Timothy J. Baek committed
135
136
137
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
138
						selectedTab = 'audio';
Timothy J. Baek's avatar
Timothy J. Baek committed
139
140
141
142
143
144
145
146
147
148
149
150
151
152
					}}
				>
					<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"
153
154
155
							/>
						</svg>
					</div>
156
					<div class=" self-center">{$i18n.t('Audio')}</div>
157
				</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
158

159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'chats'
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
					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>
182
					<div class=" self-center">{$i18n.t('Chats')}</div>
183
184
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'account'
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
					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>
208
					<div class=" self-center">{$i18n.t('Account')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
209
210
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
				{#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'
							? 'bg-gray-200 dark:bg-gray-700'
							: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
						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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'about'
						? 'bg-gray-200 dark:bg-gray-700'
						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
					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>
263
					<div class=" self-center">{$i18n.t('About')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
264
				</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
265
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
266
			<div class="flex-1 md:min-h-[28rem]">
267
				{#if selectedTab === 'general'}
Timothy J. Baek's avatar
Timothy J. Baek committed
268
					<General
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
269
						{getModels}
Timothy J. Baek's avatar
Timothy J. Baek committed
270
271
						{saveSettings}
						on:save={() => {
272
							toast.success($i18n.t('Settings saved successfully!'));
Timothy J. Baek's avatar
Timothy J. Baek committed
273
274
						}}
					/>
275
				{:else if selectedTab === 'interface'}
Timothy J. Baek's avatar
Timothy J. Baek committed
276
					<Interface
Timothy J. Baek's avatar
Timothy J. Baek committed
277
						{saveSettings}
Timothy J. Baek's avatar
Timothy J. Baek committed
278
						on:save={() => {
279
							toast.success($i18n.t('Settings saved successfully!'));
280
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
281
					/>
282
283
284
285
286
287
288
				{:else if selectedTab === 'personalization'}
					<Personalization
						{saveSettings}
						on:save={() => {
							toast.success($i18n.t('Settings saved successfully!'));
						}}
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
289
290
				{:else if selectedTab === 'audio'}
					<Audio
Timothy J. Baek's avatar
Timothy J. Baek committed
291
292
						{saveSettings}
						on:save={() => {
293
							toast.success($i18n.t('Settings saved successfully!'));
Timothy J. Baek's avatar
Timothy J. Baek committed
294
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
295
					/>
296
				{:else if selectedTab === 'chats'}
Timothy J. Baek's avatar
Timothy J. Baek committed
297
					<Chats {saveSettings} />
298
				{:else if selectedTab === 'account'}
299
300
					<Account
						saveHandler={() => {
301
							toast.success($i18n.t('Settings saved successfully!'));
302
						}}
303
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
304
				{:else if selectedTab === 'about'}
Timothy J. Baek's avatar
Timothy J. Baek committed
305
					<About />
Timothy J. Baek's avatar
Timothy J. Baek committed
306
307
				{/if}
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
308
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
309
310
	</div>
</Modal>
311
312
313
314
315
316
317
318
319

<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 */
	}

320
321
322
323
324
325
326
327
328
	.tabs::-webkit-scrollbar {
		display: none; /* for Chrome, Safari and Opera */
	}

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

329
330
331
332
	input[type='number'] {
		-moz-appearance: textfield; /* Firefox */
	}
</style>