Skip to content

Commit

Permalink
Add user settings tab
Browse files Browse the repository at this point in the history
  • Loading branch information
thecodingwizard committed Jan 17, 2025
1 parent 6fa0466 commit b01a463
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 74 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@sveltejs/adapter-vercel": "^5.5.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/vscode": "^1.96.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
Expand Down
120 changes: 74 additions & 46 deletions src/lib/components/SettingsDialog/SettingsDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import Server from 'lucide-svelte/icons/server';
import X from 'lucide-svelte/icons/x';
import { LANGUAGES, type FileSettings, type Language, type UserData } from '$lib/types';
import type { User as FirebaseUser } from 'firebase/auth';
import TextField from './TextField.svelte';
const {
elements: { trigger, overlay, content, title, description, close, portalled },
Expand All @@ -18,27 +20,37 @@
forceVisible: true
});
export const open = () => {
meltUiOpen.set(true);
};
const {
firebaseUser,
userPermission,
userData,
fileSettings,
onSave
}: {
firebaseUser: FirebaseUser;
userPermission: 'OWNER' | 'READ' | 'READ_WRITE' | 'PRIVATE';
userData: UserData;
fileSettings: FileSettings;
onSave: (newUserData: UserData, newFileSettings: FileSettings) => void;
onSave: (newUserData: UserData, newFileSettings: FileSettings, newUsername: string) => void;
} = $props();
const onSubmit = (event: SubmitEvent) => {
event.preventDefault();
const formData = new FormData(event.target as HTMLFormElement);
let tabSize: 2 | 4 | 8;
try {
tabSize = parseInt(formData.get('tabSize') as string) as 2 | 4 | 8;
} catch (e) {
alert('Invalid tab size value of ' + formData.get('tabSize'));
return;
}
const newUserData = {
editorMode: formData.get('editorMode') as 'normal' | 'vim'
editorMode: formData.get('editorMode') as 'normal' | 'vim',
tabSize,
theme: formData.get('theme') as 'light' | 'dark',
// deprecated: for compatibility with old IDE only
lightMode: formData.get('theme') === 'light'
};
let newFileSettings = {
...fileSettings,
Expand All @@ -49,12 +61,17 @@
newFileSettings.compilerOptions[newFileSettings.language] = formData.get(
'compiler_options'
) as string;
onSave(newUserData, newFileSettings);
onSave(newUserData, newFileSettings, formData.get('username') as string);
meltUiOpen.set(false);
};
let activeTab: 'workspace' | 'user' | 'judge' = $state('workspace');
let selectedLanguage: Language = $state(fileSettings.language);
export const open = () => {
meltUiOpen.set(true);
activeTab = 'workspace';
};
</script>

{#if $meltUiOpen}
Expand Down Expand Up @@ -95,24 +112,17 @@
</div>

<form class="space-y-6 p-4 sm:p-6" onsubmit={onSubmit}>
<p class="text-sm text-gray-500" class:hidden={activeTab === 'workspace'}>
<p class="text-sm text-gray-500" class:hidden={activeTab !== 'judge'}>
Work In Progress: Much of the functionality is still being ported over to the new site.
</p>

<div class:hidden={activeTab !== 'workspace'}>
<label for="workspaceName" class="block font-medium text-gray-700">
Workspace Name
</label>
<div class="mt-1">
<input
type="text"
name="workspaceName"
id="workspaceName"
class="mt-0 block w-full border-0 border-b-2 border-gray-200 px-0 pt-0 pb-1 text-sm text-black focus:border-black focus:ring-0"
defaultValue={fileSettings.workspaceName || ''}
readonly={!(userPermission === 'OWNER' || userPermission === 'READ_WRITE')}
/>
</div>
<TextField
label="Workspace Name"
name="workspaceName"
defaultValue={fileSettings.workspaceName || ''}
readonly={!(userPermission === 'OWNER' || userPermission === 'READ_WRITE')}
/>
</div>
<div class:hidden={activeTab !== 'workspace'}>
<div class="mb-2 font-medium text-gray-800">Language</div>
Expand All @@ -127,23 +137,14 @@
</div>

<div class:hidden={activeTab !== 'workspace'}>
<label for="compilerOptions" class="block font-medium text-gray-700">
{LANGUAGES[selectedLanguage]} Compiler Options
</label>
<div class="mt-1">
{#key selectedLanguage}
<!-- Rerender when selectedLanguage changes to reset the value -->
<input
type="text"
name="compiler_options"
id="compiler_options"
class="mt-0 block w-full border-0 border-b-2 border-gray-200 px-0 pt-0 pb-1 font-mono text-sm text-black focus:border-black focus:ring-0"
defaultValue={fileSettings.compilerOptions[selectedLanguage]}
placeholder="None"
readonly={!(userPermission === 'OWNER' || userPermission === 'READ_WRITE')}
/>
{/key}
</div>
<TextField
label={`${LANGUAGES[selectedLanguage]} Compiler Options`}
name="compiler_options"
defaultValue={fileSettings.compilerOptions[selectedLanguage]}
readonly={!(userPermission === 'OWNER' || userPermission === 'READ_WRITE')}
placeholder="None"
className="font-mono"
/>
</div>

<div class:hidden={activeTab !== 'workspace'}>
Expand All @@ -161,6 +162,14 @@
/>
</div>

<div class:hidden={activeTab !== 'user'}>
<TextField
label="User Name"
name="username"
defaultValue={firebaseUser.displayName || ''}
/>
</div>

<div class:hidden={activeTab !== 'user'}>
<div class="mb-2 font-medium text-gray-800">Editor Mode</div>
<RadioGroup
Expand All @@ -171,6 +180,27 @@
/>
</div>

<div class:hidden={activeTab !== 'user'}>
<div class="mb-2 font-medium text-gray-800">Tab Size</div>
<RadioGroup
name="tabSize"
defaultValue={userData.tabSize.toString()}
options={{ 2: '2', 4: '4', 8: '8' }}
theme="dark"
/>
</div>

<div class:hidden={activeTab !== 'user'}>
<div class="mb-2 font-medium text-gray-800">Theme</div>
<RadioGroup
name="theme"
defaultValue={userData.theme}
options={{ light: 'Light', dark: 'Dark' }}
theme="dark"
/>
</div>


<div class="mt-6 flex items-center space-x-4">
<button
type="button"
Expand All @@ -179,14 +209,12 @@
>
Cancel
</button>
{#if userPermission === 'OWNER' || userPermission === 'READ_WRITE'}
<button
type="submit"
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none"
>
Save
</button>
{/if}
<button
type="submit"
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none"
>
Save
</button>
</div>
</form>

Expand Down
18 changes: 18 additions & 0 deletions src/lib/components/SettingsDialog/TextField.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
const { label, name, ...props } = $props();
</script>

<div>
<label for={name} class="block font-medium text-gray-700">
{label}
</label>
<div class="mt-1">
<input
type="text"
{name}
id={name}
class="mt-0 block w-full border-0 border-b-2 border-gray-200 px-0 pt-0 pb-1 text-sm text-black focus:border-black focus:ring-0"
{...props}
/>
</div>
</div>
1 change: 1 addition & 0 deletions src/lib/components/editor/RealtimeEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
export type EditorProps = {
theme?: 'dark' | 'light';
language?: 'cpp' | 'java' | 'py' | 'plaintext';
tabSize?: number;
// Optional compiler options for the LSP
compilerOptions?: string;
readOnly?: boolean;
Expand Down
50 changes: 43 additions & 7 deletions src/lib/components/editor/monaco/MainMonacoEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ attached to the promise, so when the promise is cancelled, it logs an error.

<script lang="ts" module>
import * as monaco from 'monaco-editor';
import * as vscode from 'vscode';
// PRIVATE: do not access this outside this <script module> block!
// We must keep _monacoWrapper.isStarting() and _monacoWrapper.isStopping() in sync with mainMonacoState.
Expand All @@ -46,6 +47,7 @@ attached to the promise, so when the promise is cancelled, it logs an error.
const createMainMonacoWrapper = async (
language: Language,
compilerOptions: string,
theme: 'light' | 'dark',
readOnly: boolean,
editorElement: HTMLElement,
statusbarElement: HTMLElement
Expand All @@ -55,7 +57,7 @@ attached to the promise, so when the promise is cancelled, it logs an error.
}
_monacoWrapper = new MonacoEditorLanguageClientWrapper();
const monacoWrapperConfig = getMonacoWrapperConfig(language, compilerOptions, {
const monacoWrapperConfig = getMonacoWrapperConfig(language, theme, compilerOptions, {
...baseEditorOptions,
language,
readOnly
Expand Down Expand Up @@ -119,6 +121,8 @@ attached to the promise, so when the promise is cancelled, it logs an error.
readOnly = false,
compilerOptions,
editorMode,
tabSize,
theme = 'dark',
yjsInfo
}: EditorProps = $props();
Expand Down Expand Up @@ -157,6 +161,12 @@ attached to the promise, so when the promise is cancelled, it logs an error.
throw new Error('Main monaco editor language cannot be plaintext');
}
// Rerun when compilerOptions changes.
// Do not rerun when readOnly or theme change, since they are handled separately.
// Do not rerun when editorElement or statusbarElement changes, since they are dom elements.
// Dependencies are not tracked inside callbacks.
compilerOptions;
// Note: It's possible this will crash if this effect reruns many times
// in a row, faster than we can create / dispose the monaco wrapper.
Expand All @@ -172,9 +182,10 @@ attached to the promise, so when the promise is cancelled, it logs an error.
createMainMonacoWrapper(
language,
compilerOptions ?? '',
untrack(() => readOnly), // handled separately
untrack(() => editorElement), // ignore dom changes
untrack(() => statusbarElement) // ignore dom changes
theme,
readOnly,
editorElement,
statusbarElement
).then((_editor) => {
if (!isAlive) {
throw new Error('Monaco editor changed before it finished initializing');
Expand All @@ -193,6 +204,30 @@ attached to the promise, so when the promise is cancelled, it logs an error.
});
});
$effect(() => {
if (!editor) return;
vscode.workspace
.getConfiguration()
.update(
'workbench.colorTheme',
theme === 'dark' ? 'Default Dark Modern' : 'Default Light Modern',
vscode.ConfigurationTarget.Global
);
});
$effect(() => {
if (!editor) return;
vscode.workspace
.getConfiguration()
.update(
'editor.tabSize',
tabSize,
vscode.ConfigurationTarget.Global
);
});
$effect(() => {
// For monaco, vim mode has the following restrictions:
// - We cannot unload the extension once it is loaded.
Expand Down Expand Up @@ -240,7 +275,7 @@ attached to the promise, so when the promise is cancelled, it logs an error.
const interval = setInterval(() => {
const value = editor?.getValue();
if (!value) return;
// Only save files < 15kb in size
if (value.length < 15000) {
let data = localStorage.getItem('editorHistory') ?? '[]';
Expand All @@ -251,11 +286,12 @@ attached to the promise, so when the promise is cancelled, it logs an error.
}
parsedData.push(value);
if (data.length > 4 * 1024 * 1024) { // save at most 4MB of data
if (data.length > 4 * 1024 * 1024) {
// save at most 4MB of data
// Remove oldest 1/3 of entries to stay under storage limit
parsedData = parsedData.slice(Math.floor(parsedData.length / 3));
}
localStorage.setItem('editorHistory', JSON.stringify(parsedData));
}
}, 10000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
untrack(() => editorElement),
{
...baseEditorOptions,
language
language,
}
);
Expand Down
Loading

0 comments on commit b01a463

Please sign in to comment.