SettingsModal.svelte 13.6 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';
Timothy J. Baek's avatar
Timothy J. Baek committed
18
	import Valves from './Settings/Valves.svelte';
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';
36
37

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

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

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

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

<Modal bind:show>
Timothy J. Baek's avatar
Timothy J. Baek committed
70
	<div class="text-gray-700 dark:text-gray-100">
Timothy J. Baek's avatar
Timothy J. Baek committed
71
		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
72
			<div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
			<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
92
93
		<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4">
			<div
94
				id="settings-tabs-container"
Timothy J. Baek's avatar
Timothy J. Baek committed
95
				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
96
97
			>
				<button
98
					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
99
					'general'
Timothy J. Baek's avatar
Timothy J. Baek committed
100
101
						? '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
102
					on:click={() => {
103
						selectedTab = 'general';
Timothy J. Baek's avatar
Timothy J. Baek committed
104
					}}
Timothy J. Baek's avatar
Timothy J. Baek committed
105
				>
Timothy J. Baek's avatar
Timothy J. Baek committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
					<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>
120
					<div class=" self-center">{$i18n.t('General')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
121
122
				</button>

123
				<button
124
					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
125
					'interface'
Timothy J. Baek's avatar
Timothy J. Baek committed
126
127
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
128
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
129
						selectedTab = 'interface';
130
131
132
133
134
					}}
				>
					<div class=" self-center mr-2">
						<svg
							xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
135
							viewBox="0 0 16 16"
136
137
138
139
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
Timothy J. Baek's avatar
Timothy J. Baek committed
140
								fill-rule="evenodd"
Timothy J. Baek's avatar
Timothy J. Baek committed
141
								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
142
143
144
145
								clip-rule="evenodd"
							/>
						</svg>
					</div>
146
					<div class=" self-center">{$i18n.t('Interface')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
147
148
				</button>

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

Timothy J. Baek's avatar
Timothy J. Baek committed
164
165
				<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
166
					'audio'
Timothy J. Baek's avatar
Timothy J. Baek committed
167
168
						? '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
169
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
170
						selectedTab = 'audio';
Timothy J. Baek's avatar
Timothy J. Baek committed
171
172
173
174
175
176
177
178
179
180
181
182
183
184
					}}
				>
					<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"
185
186
187
							/>
						</svg>
					</div>
188
					<div class=" self-center">{$i18n.t('Audio')}</div>
189
				</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
190

Timothy J. Baek's avatar
Timothy J. Baek committed
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
				<button
					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
					'valves'
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
					on:click={() => {
						selectedTab = 'valves';
					}}
				>
					<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
								d="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z"
							/>
						</svg>
					</div>
					<div class=" self-center">{$i18n.t('Valves')}</div>
				</button>

215
216
217
				<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
218
219
						? 'bg-gray-200 dark:bg-gray-800'
						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
					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>
238
					<div class=" self-center">{$i18n.t('Chats')}</div>
239
240
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
241
242
243
				<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
244
245
						? '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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
					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>
264
					<div class=" self-center">{$i18n.t('Account')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
265
266
				</button>

Timothy J. Baek's avatar
Timothy J. Baek committed
267
268
269
270
				{#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
271
272
							? '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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
						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
296
297
298
				<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
299
300
						? '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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
					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>
319
					<div class=" self-center">{$i18n.t('About')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
320
				</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
321
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
322
			<div class="flex-1 md:min-h-[28rem]">
323
				{#if selectedTab === 'general'}
Timothy J. Baek's avatar
Timothy J. Baek committed
324
					<General
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
325
						{getModels}
Timothy J. Baek's avatar
Timothy J. Baek committed
326
327
						{saveSettings}
						on:save={() => {
328
							toast.success($i18n.t('Settings saved successfully!'));
Timothy J. Baek's avatar
Timothy J. Baek committed
329
330
						}}
					/>
331
				{:else if selectedTab === 'interface'}
Timothy J. Baek's avatar
Timothy J. Baek committed
332
					<Interface
Timothy J. Baek's avatar
Timothy J. Baek committed
333
						{saveSettings}
Timothy J. Baek's avatar
Timothy J. Baek committed
334
						on:save={() => {
335
							toast.success($i18n.t('Settings saved successfully!'));
336
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
337
					/>
338
339
340
341
342
343
344
				{:else if selectedTab === 'personalization'}
					<Personalization
						{saveSettings}
						on:save={() => {
							toast.success($i18n.t('Settings saved successfully!'));
						}}
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
345
346
				{:else if selectedTab === 'audio'}
					<Audio
Timothy J. Baek's avatar
Timothy J. Baek committed
347
348
						{saveSettings}
						on:save={() => {
349
							toast.success($i18n.t('Settings saved successfully!'));
Timothy J. Baek's avatar
Timothy J. Baek committed
350
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
351
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
352
353
354
355
356
357
358
				{:else if selectedTab === 'valves'}
					<Valves
						{saveSettings}
						on:save={() => {
							toast.success($i18n.t('Settings saved successfully!'));
						}}
					/>
359
				{:else if selectedTab === 'chats'}
Timothy J. Baek's avatar
Timothy J. Baek committed
360
					<Chats {saveSettings} />
361
				{:else if selectedTab === 'account'}
362
363
					<Account
						saveHandler={() => {
364
							toast.success($i18n.t('Settings saved successfully!'));
365
						}}
366
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
367
				{:else if selectedTab === 'about'}
Timothy J. Baek's avatar
Timothy J. Baek committed
368
					<About />
Timothy J. Baek's avatar
Timothy J. Baek committed
369
370
				{/if}
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
371
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
372
373
	</div>
</Modal>
374
375
376
377
378
379
380
381
382

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

383
384
385
386
387
388
389
390
391
	.tabs::-webkit-scrollbar {
		display: none; /* for Chrome, Safari and Opera */
	}

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

392
393
394
395
	input[type='number'] {
		-moz-appearance: textfield; /* Firefox */
	}
</style>