-
Notifications
You must be signed in to change notification settings - Fork 92
feat(webui): Add disabled state and theme to SqlEditor; Focus SqlEditor on page load and after query termination; Remove autocomplete.
#1244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1af4000
0dc4a24
a711b05
7394a9d
c0241b1
58b160d
205f29f
066e389
a95af1f
e15cb5e
60479d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,145 +1,118 @@ | ||||||||||||
| import { | ||||||||||||
| useCallback, | ||||||||||||
| useEffect, | ||||||||||||
| useState, | ||||||||||||
| useImperativeHandle, | ||||||||||||
| useRef, | ||||||||||||
| } from "react"; | ||||||||||||
|
|
||||||||||||
| import { | ||||||||||||
| Editor, | ||||||||||||
| EditorProps, | ||||||||||||
| useMonaco, | ||||||||||||
| } from "@monaco-editor/react"; | ||||||||||||
| import {language as sqlLanguage} from "monaco-editor/esm/vs/basic-languages/sql/sql.js"; | ||||||||||||
| import {theme} from "antd"; | ||||||||||||
| import color from "color"; | ||||||||||||
| import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js"; | ||||||||||||
|
|
||||||||||||
| import "./monaco-loader"; | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainSanity-check: ensure SQL language contributions remain loaded. Using our retrieved learnings: we previously broke autocomplete when 🏁 Script executed: #!/bin/bash
# Look for language contributions and editor.all.js usage
rg -nP 'editor\.all\.js|sql\.contribution|monaco-editor/esm/vs/editor/editor\.all\.js|basic-languages/sql' -S
# Confirm we still use @monaco-editor/react loader
rg -n 'monaco-loader' components/webui/client/src/components/SqlEditorLength of output: 409 Add missing The loader currently only brings in the SQL contribution, but our previous findings show that without • File: components/webui/client/src/components/SqlEditor/monaco-loader.ts // ensure the full editor API is loaded first
+import "monaco-editor/esm/vs/editor/editor.all.js";
// then register SQL language support
import "monaco-editor/esm/vs/basic-languages/sql/sql.contribution.js";This will restore the required SQL autocomplete capabilities. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| const MAX_VISIBLE_LINES: number = 5; | ||||||||||||
| type SqlEditorRef = { | ||||||||||||
| focus: () => void; | ||||||||||||
| }; | ||||||||||||
|
|
||||||||||||
| type SqlEditorProps = Omit<EditorProps, "language"> & React.RefAttributes<SqlEditorRef> & { | ||||||||||||
| disabled: boolean; | ||||||||||||
|
|
||||||||||||
| type SqlEditorProps = Omit<EditorProps, "language">; | ||||||||||||
| /** Callback when the editor is mounted and ref is ready to use. */ | ||||||||||||
| onEditorReady?: () => void; | ||||||||||||
| }; | ||||||||||||
|
hoophalab marked this conversation as resolved.
|
||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Monaco editor with highlighting and autocomplete for SQL syntax. | ||||||||||||
| * Monaco editor with highlighting for SQL syntax. | ||||||||||||
| * | ||||||||||||
| * @param props | ||||||||||||
| * @return | ||||||||||||
| */ | ||||||||||||
| const SqlEditor = (props: SqlEditorProps) => { | ||||||||||||
| const {ref, disabled, onEditorReady, ...editorProps} = props; | ||||||||||||
| const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>(null); | ||||||||||||
| const monacoEditor = useMonaco(); | ||||||||||||
|
|
||||||||||||
| const {token} = theme.useToken(); | ||||||||||||
|
|
||||||||||||
| useImperativeHandle(ref, () => ({ | ||||||||||||
| focus: () => { | ||||||||||||
| editorRef.current?.focus(); | ||||||||||||
| }, | ||||||||||||
| }), []); | ||||||||||||
|
|
||||||||||||
| const handleEditorDidMount = useCallback(( | ||||||||||||
| editor: monaco.editor.IStandaloneCodeEditor, | ||||||||||||
| ) => { | ||||||||||||
| editorRef.current = editor; | ||||||||||||
| onEditorReady?.(); | ||||||||||||
| }, [onEditorReady]); | ||||||||||||
|
|
||||||||||||
| // Define disabled theme for monaco editor | ||||||||||||
| useEffect(() => { | ||||||||||||
| if (null === monacoEditor) { | ||||||||||||
| return () => { | ||||||||||||
| }; | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // Adds autocomplete suggestions for SQL keywords on editor load | ||||||||||||
| const provider = monacoEditor.languages.registerCompletionItemProvider("sql", { | ||||||||||||
| provideCompletionItems: (model, position) => { | ||||||||||||
| const word = model.getWordUntilPosition(position); | ||||||||||||
| const range = { | ||||||||||||
| startLineNumber: position.lineNumber, | ||||||||||||
| endLineNumber: position.lineNumber, | ||||||||||||
| startColumn: word.startColumn, | ||||||||||||
| endColumn: word.endColumn, | ||||||||||||
| }; | ||||||||||||
| const suggestions = sqlLanguage.keywords.map((keyword: string) => ({ | ||||||||||||
| detail: "Presto SQL (CLP)", | ||||||||||||
| insertText: `${keyword} `, | ||||||||||||
| kind: monacoEditor.languages.CompletionItemKind.Keyword, | ||||||||||||
| label: keyword, | ||||||||||||
| range: range, | ||||||||||||
| })); | ||||||||||||
|
|
||||||||||||
| // When SQL keyword suggestions appear (e.g., after "SELECT a"), hitting Enter | ||||||||||||
| // accepts the first suggestion. To prevent accidental auto-completion | ||||||||||||
| // in multi-line queries and to allow users to dismiss suggestions more easily, | ||||||||||||
| // we make the current input the first suggestion. | ||||||||||||
| // Users can then use arrow keys to select a keyword if needed. | ||||||||||||
| const typedWord = model.getValueInRange(range); | ||||||||||||
| if (0 < typedWord.length) { | ||||||||||||
| suggestions.push({ | ||||||||||||
| detail: "Current", | ||||||||||||
| insertText: `${typedWord}\n`, | ||||||||||||
| kind: monaco.languages.CompletionItemKind.Text, | ||||||||||||
| label: typedWord, | ||||||||||||
| range: range, | ||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return { | ||||||||||||
| suggestions: suggestions, | ||||||||||||
| incomplete: true, | ||||||||||||
| }; | ||||||||||||
| monacoEditor.editor.defineTheme("disabled-theme", { | ||||||||||||
| base: "vs", | ||||||||||||
| inherit: true, | ||||||||||||
| rules: [], | ||||||||||||
| colors: { | ||||||||||||
| "editor.background": color(token.colorBgContainerDisabled).hexa(), | ||||||||||||
| "editor.foreground": color(token.colorTextDisabled).hexa(), | ||||||||||||
|
|
||||||||||||
| // transparent | ||||||||||||
| "focusBorder": "#00000000", | ||||||||||||
| }, | ||||||||||||
| triggerCharacters: [ | ||||||||||||
| " ", | ||||||||||||
| "\n", | ||||||||||||
| ], | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| return () => { | ||||||||||||
| provider.dispose(); | ||||||||||||
| }; | ||||||||||||
| }, [monacoEditor]); | ||||||||||||
|
|
||||||||||||
| const [isContentMultiline, setIsContentMultiline] = useState<boolean>(false); | ||||||||||||
|
|
||||||||||||
| const handleMonacoMount = useCallback((editor: monaco.editor.IStandaloneCodeEditor) => { | ||||||||||||
| editor.onDidContentSizeChange((ev) => { | ||||||||||||
| if (false === ev.contentHeightChanged) { | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| if (null === monacoEditor) { | ||||||||||||
| throw new Error("Unexpected null Monaco instance"); | ||||||||||||
| } | ||||||||||||
| const domNode = editor.getDomNode(); | ||||||||||||
| if (null === domNode) { | ||||||||||||
| throw new Error("Unexpected null editor DOM node"); | ||||||||||||
| } | ||||||||||||
| const model = editor.getModel(); | ||||||||||||
| if (null === model) { | ||||||||||||
| throw new Error("Unexpected null editor model"); | ||||||||||||
| } | ||||||||||||
| const lineHeight = editor.getOption(monacoEditor.editor.EditorOption.lineHeight); | ||||||||||||
| const contentHeight = editor.getContentHeight(); | ||||||||||||
| const approxWrappedLines = Math.round(contentHeight / lineHeight); | ||||||||||||
| setIsContentMultiline(1 < approxWrappedLines); | ||||||||||||
| if (MAX_VISIBLE_LINES >= approxWrappedLines) { | ||||||||||||
| domNode.style.height = `${contentHeight}px`; | ||||||||||||
| } else { | ||||||||||||
| domNode.style.height = `${lineHeight * MAX_VISIBLE_LINES}px`; | ||||||||||||
| } | ||||||||||||
| }); | ||||||||||||
| }, [monacoEditor]); | ||||||||||||
| }, [ | ||||||||||||
| monacoEditor, | ||||||||||||
| token, | ||||||||||||
| ]); | ||||||||||||
|
|
||||||||||||
| return ( | ||||||||||||
| <Editor | ||||||||||||
| language={"sql"} | ||||||||||||
|
|
||||||||||||
| // Use white background while loading (default is grey) so transition to editor with | ||||||||||||
| // white background is less jarring. | ||||||||||||
| loading={<div style={{backgroundColor: "white", height: "100%", width: "100%"}}/>} | ||||||||||||
| options={{ | ||||||||||||
| automaticLayout: true, | ||||||||||||
| folding: isContentMultiline, | ||||||||||||
| fontSize: 20, | ||||||||||||
| lineHeight: 30, | ||||||||||||
| lineNumbers: isContentMultiline ? | ||||||||||||
| "on" : | ||||||||||||
| "off", | ||||||||||||
| lineNumbersMinChars: 2, | ||||||||||||
| minimap: {enabled: false}, | ||||||||||||
| overviewRulerBorder: false, | ||||||||||||
| placeholder: "Enter your SQL query", | ||||||||||||
| renderLineHighlightOnlyWhenFocus: true, | ||||||||||||
| scrollBeyondLastLine: false, | ||||||||||||
| wordWrap: "on", | ||||||||||||
| }} | ||||||||||||
| onMount={handleMonacoMount} | ||||||||||||
| {...props}/> | ||||||||||||
| <div | ||||||||||||
| style={ | ||||||||||||
| disabled ? | ||||||||||||
| {pointerEvents: "none"} : | ||||||||||||
| {} | ||||||||||||
| } | ||||||||||||
| > | ||||||||||||
|
hoophalab marked this conversation as resolved.
|
||||||||||||
| <Editor | ||||||||||||
|
Comment on lines
78
to
+86
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) UX consideration: pointer-events disables scrolling; verify that’s desired. While querying, users cannot scroll or select text with the mouse. If read-only is sufficient, consider dropping 🤖 Prompt for AI Agents |
||||||||||||
| language={"sql"} | ||||||||||||
| loading={ | ||||||||||||
| <div | ||||||||||||
| style={{ | ||||||||||||
| backgroundColor: "white", | ||||||||||||
| height: "100%", | ||||||||||||
| width: "100%", | ||||||||||||
| }}/> | ||||||||||||
| } | ||||||||||||
| options={{ | ||||||||||||
| automaticLayout: true, | ||||||||||||
| folding: false, | ||||||||||||
| fontSize: 16, | ||||||||||||
| lineNumbers: "off", | ||||||||||||
| minimap: {enabled: false}, | ||||||||||||
| overviewRulerBorder: false, | ||||||||||||
| placeholder: "Enter your SQL query", | ||||||||||||
| renderLineHighlightOnlyWhenFocus: true, | ||||||||||||
| scrollBeyondLastLine: false, | ||||||||||||
| wordWrap: "on", | ||||||||||||
| }} | ||||||||||||
|
davemarco marked this conversation as resolved.
|
||||||||||||
| theme={disabled ? | ||||||||||||
| "disabled-theme" : | ||||||||||||
| "light"} | ||||||||||||
| onMount={handleEditorDidMount} | ||||||||||||
|
hoophalab marked this conversation as resolved.
|
||||||||||||
| {...editorProps}/> | ||||||||||||
| </div> | ||||||||||||
| ); | ||||||||||||
| }; | ||||||||||||
|
|
||||||||||||
| export default SqlEditor; | ||||||||||||
| export type {SqlEditorRef}; | ||||||||||||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.