import { useColorModeValue } from '@kobalte/core' import { editor as mEditor, Uri } from 'monaco-editor' import { createEffect, on, onMount } from 'solid-js' import { $activeTab, $tabs, type EditorTab } from '../../store/tabs.ts' import { useStore } from '../../store/use-store.ts' import { setupMonaco } from './utils/setup.ts' import './Editor.css' export interface EditorProps { class?: string } const DEFAULT_CODE = ` /** * This playground comes pre-loaded with all @mtcute/* libraries, * as well as a pre-configured Telegram client (available as \`tg\` global variable). * * Exports from this file will become available in the REPL. */ export const self = await tg.getMe() console.log(self) `.trimStart() function findChangedTab(a: EditorTab[], b: EditorTab[]) { const set = new Set(a.map(tab => tab.id)) for (const tab of b) { if (!set.has(tab.id)) return tab } return null } export default function Editor(props: EditorProps) { const tabs = useStore($tabs) const activeTab = useStore($activeTab) let ref!: HTMLDivElement let editor: mEditor.IStandaloneCodeEditor | undefined const monacoTheme = useColorModeValue('latte', 'mocha') // const monacoTheme = () => scheme() === 'dark' ? 'ayu-dark' : 'ayu-light' const modelsByTab = new Map() onMount(async () => { editor = mEditor.create(ref, { model: null, automaticLayout: true, minimap: { enabled: false, }, scrollbar: { verticalScrollbarSize: 8, }, lightbulb: { enabled: 'onCode' as any, }, quickSuggestions: { other: true, comments: true, strings: true, }, padding: { top: 8 }, acceptSuggestionOnCommitCharacter: true, acceptSuggestionOnEnter: 'on', accessibilitySupport: 'on', inlayHints: { enabled: 'on', }, lineNumbersMinChars: 3, theme: monacoTheme(), scrollBeyondLastLine: false, }) await setupMonaco() for (const tab of tabs()) { const model = mEditor.createModel(tab.main ? DEFAULT_CODE : '', 'typescript', Uri.parse(`file:///${tab.id}.ts`)) modelsByTab.set(tab.id, model) } editor.setModel(modelsByTab.get(activeTab())!) // editor.onDidChangeModelContent(() => { // props.onCodeChange(editor?.getValue() ?? '') // }) return () => editor?.dispose() }) createEffect(on(() => monacoTheme(), (theme) => { if (!editor) return editor.updateOptions({ theme }) })) createEffect(on(activeTab, (tabId) => { if (!editor) return const model = modelsByTab.get(tabId) if (!model) return editor.setModel(model) })) createEffect(on(tabs, (tabs, prevTabs) => { if (!editor || !prevTabs) return if (tabs.length === prevTabs.length) { // verify filenames for (const tab of tabs) { const oldName = prevTabs.find(prevTab => prevTab.id === tab.id)?.fileName if (!oldName) continue // weird flex but ok if (oldName === tab.fileName) continue // renamed const oldModel = modelsByTab.get(tab.id) if (!oldModel) continue const newModel = mEditor.createModel(oldModel.getValue(), 'typescript', Uri.parse(`file:///${tab.fileName}`)) modelsByTab.set(tab.id, newModel) if (editor.getModel() === oldModel) { editor.setModel(newModel) } oldModel.dispose() } return } if (tabs.length > prevTabs.length) { // a tab was created const changed = findChangedTab(prevTabs, tabs) if (!changed) return const model = mEditor.createModel('', 'typescript', Uri.parse(`file:///${changed.fileName}`)) modelsByTab.set(changed.id, model) editor.setModel(model) } else { // a tab was deleted const changed = findChangedTab(tabs, prevTabs) if (!changed) return modelsByTab.delete(changed.id) } })) return (
) }