open-webui/src/lib/components/common/CodeEditor.svelte

132 lines
3.3 KiB
Svelte
Raw Normal View History

2024-06-10 22:24:26 +00:00
<script lang="ts">
import { basicSetup, EditorView } from 'codemirror';
import { keymap, placeholder } from '@codemirror/view';
2024-06-10 23:02:23 +00:00
import { Compartment, EditorState } from '@codemirror/state';
2024-06-10 22:24:26 +00:00
import { acceptCompletion } from '@codemirror/autocomplete';
import { indentWithTab } from '@codemirror/commands';
2024-06-11 00:12:48 +00:00
import { indentUnit } from '@codemirror/language';
2024-06-10 22:24:26 +00:00
import { python } from '@codemirror/lang-python';
import { oneDark } from '@codemirror/theme-one-dark';
2024-06-11 00:12:48 +00:00
import { onMount, createEventDispatcher } from 'svelte';
import { formatPythonCode } from '$lib/apis/utils';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
2024-06-10 22:24:26 +00:00
2024-06-10 23:35:42 +00:00
export let boilerplate = '';
2024-06-10 22:24:26 +00:00
export let value = '';
2024-06-10 23:02:23 +00:00
let codeEditor;
let isDarkMode = false;
let editorTheme = new Compartment();
2024-06-11 00:30:07 +00:00
export const formatPythonCodeHandler = async () => {
2024-06-11 00:12:48 +00:00
if (codeEditor) {
console.log('formatPythonCodeHandler');
const res = await formatPythonCode(value).catch((error) => {
toast.error(error);
return null;
});
if (res && res.code) {
const formattedCode = res.code;
codeEditor.dispatch({
changes: [{ from: 0, to: codeEditor.state.doc.length, insert: formattedCode }]
});
2024-06-11 00:24:42 +00:00
toast.success('Code formatted successfully');
2024-06-11 00:12:48 +00:00
return true;
}
return false;
}
};
2024-06-10 23:02:23 +00:00
let extensions = [
basicSetup,
keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
python(),
2024-06-11 00:12:48 +00:00
indentUnit.of(' '),
2024-06-10 23:02:23 +00:00
placeholder('Enter your code here...'),
EditorView.updateListener.of((e) => {
if (e.docChanged) {
value = e.state.doc.toString();
}
}),
editorTheme.of([])
];
2024-06-10 22:24:26 +00:00
onMount(() => {
2024-06-11 00:24:42 +00:00
value = boilerplate;
2024-06-10 23:02:23 +00:00
// Check if html class has dark mode
isDarkMode = document.documentElement.classList.contains('dark');
2024-06-10 22:24:26 +00:00
// python code editor, highlight python code
2024-06-10 23:02:23 +00:00
codeEditor = new EditorView({
2024-06-10 22:24:26 +00:00
state: EditorState.create({
2024-06-11 00:24:42 +00:00
doc: value,
2024-06-10 23:02:23 +00:00
extensions: extensions
2024-06-10 22:24:26 +00:00
}),
parent: document.getElementById('code-textarea')
});
2024-06-10 23:02:23 +00:00
if (isDarkMode) {
codeEditor.dispatch({
effects: editorTheme.reconfigure(oneDark)
});
}
// listen to html class changes this should fire only when dark mode is toggled
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const _isDarkMode = document.documentElement.classList.contains('dark');
if (_isDarkMode !== isDarkMode) {
isDarkMode = _isDarkMode;
if (_isDarkMode) {
codeEditor.dispatch({
effects: editorTheme.reconfigure(oneDark)
});
} else {
codeEditor.dispatch({
effects: editorTheme.reconfigure()
});
}
}
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
2024-06-11 00:12:48 +00:00
// Add a keyboard shortcut to format the code when Ctrl/Cmd + S is pressed
// Override the default browser save functionality
const handleSave = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
formatPythonCodeHandler();
dispatch('save');
}
};
document.addEventListener('keydown', handleSave);
2024-06-10 23:02:23 +00:00
return () => {
observer.disconnect();
2024-06-11 00:12:48 +00:00
document.removeEventListener('keydown', handleSave);
2024-06-10 23:02:23 +00:00
};
2024-06-10 22:24:26 +00:00
});
</script>
2024-06-10 23:02:23 +00:00
<div id="code-textarea" class="h-full w-full" />