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

185 lines
4.4 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-10-05 19:38:09 +00:00
import { languages } from '@codemirror/language-data';
// import { python } from '@codemirror/lang-python';
// import { javascript } from '@codemirror/lang-javascript';
2024-10-05 19:04:36 +00:00
2024-06-10 22:24:26 +00:00
import { oneDark } from '@codemirror/theme-one-dark';
2024-10-06 19:07:45 +00:00
import { onMount, createEventDispatcher, getContext, tick } from 'svelte';
2024-10-05 19:38:09 +00:00
2024-06-11 00:12:48 +00:00
import { formatPythonCode } from '$lib/apis/utils';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
2024-06-24 19:26:07 +00:00
const i18n = getContext('i18n');
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-10-05 19:04:36 +00:00
let _value = '';
$: if (value) {
updateValue();
}
const updateValue = () => {
_value = value;
if (codeEditor) {
codeEditor.dispatch({
changes: [{ from: 0, to: codeEditor.state.doc.length, insert: _value }]
});
}
};
export let id = '';
export let lang = '';
2024-06-10 22:24:26 +00:00
2024-06-10 23:02:23 +00:00
let codeEditor;
let isDarkMode = false;
let editorTheme = new Compartment();
2024-10-05 19:38:09 +00:00
let editorLanguage = new Compartment();
2024-06-10 23:02:23 +00:00
2024-10-05 19:38:09 +00:00
const getLang = async () => {
const language = languages.find((l) => l.alias.includes(lang));
return await language?.load();
2024-10-05 19:04:36 +00:00
};
2024-06-11 00:30:07 +00:00
export const formatPythonCodeHandler = async () => {
2024-06-11 00:12:48 +00:00
if (codeEditor) {
2024-10-05 19:04:36 +00:00
const res = await formatPythonCode(_value).catch((error) => {
2024-06-11 00:12:48 +00:00
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
2024-10-06 19:07:45 +00:00
_value = formattedCode;
dispatch('change', { value: _value });
await tick();
2024-06-24 16:09:45 +00:00
toast.success($i18n.t('Code formatted successfully'));
2024-06-11 00:12:48 +00:00
return true;
}
return false;
}
2024-06-11 02:19:53 +00:00
return false;
2024-06-11 00:12:48 +00:00
};
2024-06-10 23:02:23 +00:00
let extensions = [
basicSetup,
keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
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) {
2024-10-05 19:04:36 +00:00
_value = e.state.doc.toString();
dispatch('change', { value: _value });
2024-06-10 23:02:23 +00:00
}
}),
2024-10-05 19:38:09 +00:00
editorTheme.of([]),
editorLanguage.of([])
2024-06-10 23:02:23 +00:00
];
2024-10-05 19:38:09 +00:00
$: if (lang) {
setLanguage();
}
const setLanguage = async () => {
const language = await getLang();
if (language) {
codeEditor.dispatch({
effects: editorLanguage.reconfigure(language)
});
}
};
2024-06-10 22:24:26 +00:00
onMount(() => {
2024-06-11 04:53:51 +00:00
console.log(value);
if (value === '') {
value = boilerplate;
}
2024-06-11 00:24:42 +00:00
2024-10-05 19:04:36 +00:00
_value = value;
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-10-05 19:04:36 +00:00
doc: _value,
2024-06-10 23:02:23 +00:00
extensions: extensions
2024-06-10 22:24:26 +00:00
}),
2024-10-05 19:04:36 +00:00
parent: document.getElementById(`code-textarea-${id}`)
2024-06-10 22:24:26 +00:00
});
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 05:33:25 +00:00
const keydownHandler = async (e) => {
2024-06-11 00:12:48 +00:00
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
2024-06-11 03:58:47 +00:00
dispatch('save');
2024-06-11 00:12:48 +00:00
}
2024-06-11 05:33:25 +00:00
// Format code when Ctrl + Shift + F is pressed
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'f') {
e.preventDefault();
await formatPythonCodeHandler();
}
2024-06-11 00:12:48 +00:00
};
2024-06-11 05:33:25 +00:00
document.addEventListener('keydown', keydownHandler);
2024-06-11 00:12:48 +00:00
2024-06-10 23:02:23 +00:00
return () => {
observer.disconnect();
2024-06-11 05:33:25 +00:00
document.removeEventListener('keydown', keydownHandler);
2024-06-10 23:02:23 +00:00
};
2024-06-10 22:24:26 +00:00
});
</script>
2024-10-05 19:04:36 +00:00
<div id="code-textarea-{id}" class="h-full w-full" />