ConceptPortal-public/rsconcept/frontend/src/components/RSInput/RSInput.tsx
2023-11-27 12:11:39 +03:00

177 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Extension } from '@codemirror/state';
import { tags } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { EditorView } from 'codemirror';
import { RefObject, useCallback, useMemo, useRef } from 'react';
import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
import { TokenID } from '../../models/rslang';
import Label from '../Common/Label';
import { ccBracketMatching } from './bracketMatching';
import { RSLanguage } from './rslang';
import { getSymbolSubstitute,RSTextWrapper } from './textEditing';
import { rsHoverTooltip } from './tooltip';
const editorSetup: BasicSetupOptions = {
highlightSpecialChars: false,
history: true,
drawSelection: false,
syntaxHighlighting: false,
defaultKeymap: true,
historyKeymap: true,
lineNumbers: false,
highlightActiveLineGutter: false,
foldGutter: false,
dropCursor: true,
allowMultipleSelections: false,
indentOnInput: false,
bracketMatching: false,
closeBrackets: false,
autocompletion: false,
rectangularSelection: false,
crosshairCursor: false,
highlightActiveLine: false,
highlightSelectionMatches: false,
closeBracketsKeymap: false,
searchKeymap: false,
foldKeymap: false,
completionKeymap: false,
lintKeymap: false
};
interface RSInputProps
extends Pick<ReactCodeMirrorProps,
'id' | 'height' | 'minHeight' | 'maxHeight' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
> {
label?: string
dimensions?: string
disabled?: boolean
noTooltip?: boolean
innerref?: RefObject<ReactCodeMirrorRef> | undefined
onChange?: (newValue: string) => void
}
function RSInput({
id, label, innerref, onChange,
disabled, noTooltip,
dimensions = 'w-full',
...props
}: RSInputProps) {
const { darkMode, colors } = useConceptTheme();
const { schema } = useRSForm();
const internalRef = useRef<ReactCodeMirrorRef>(null);
const thisRef = useMemo(
() => {
return innerref ?? internalRef;
}, [internalRef, innerref]);
const cursor = useMemo(() => !disabled ? 'cursor-text': 'cursor-default', [disabled]);
const customTheme: Extension = useMemo(
() => createTheme({
theme: darkMode ? 'dark' : 'light',
settings: {
fontFamily: 'inherit',
background: !disabled ? colors.bgInput : colors.bgDefault,
foreground: colors.fgDefault,
selection: colors.bgHover
},
styles: [
{ tag: tags.name, color: colors.fgPurple, cursor: 'default' }, // GlobalID
{ tag: tags.variableName, color: colors.fgGreen }, // LocalID
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical
{ tag: tags.keyword, color: colors.fgBlue }, // keywords
{ tag: tags.literal, color: colors.fgBlue }, // literals
{ tag: tags.controlKeyword, fontWeight: '500'}, // R | I | D
{ tag: tags.unit, fontSize: '0.75rem' }, // indicies
]
}), [disabled, colors, darkMode]);
const editorExtensions = useMemo(
() => [
EditorView.lineWrapping,
RSLanguage,
ccBracketMatching(darkMode),
... noTooltip ? [] : [rsHoverTooltip(schema?.items || [])],
], [darkMode, schema?.items, noTooltip]);
const handleInput = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (!thisRef.current) {
return;
}
const text = new RSTextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
if (event.shiftKey && !event.altKey) {
if (event.key === '*') {
text.insertToken(TokenID.DECART);
event.preventDefault();
} else if (event.code === 'KeyB') {
text.insertChar('');
event.preventDefault();
} else if (event.code === 'KeyZ') {
text.insertChar('Z');
event.preventDefault();
} else if (event.code === 'KeyR') {
text.insertChar('R');
event.preventDefault();
} else if (event.code === 'KeyF') {
text.insertChar('F');
event.preventDefault();
} else if (event.code === 'KeyP') {
text.insertChar('P');
event.preventDefault();
} else if (event.code === 'KeyX') {
text.insertChar('X');
event.preventDefault();
} else if (event.code === 'KeyS') {
text.insertChar('S');
event.preventDefault();
} else if (event.code === 'KeyD') {
text.insertChar('D');
event.preventDefault();
} else if (event.code === 'KeyC') {
text.insertChar('C');
event.preventDefault();
}
} else if (event.altKey) {
if (text.processAltKey(event.code, event.shiftKey)) {
event.preventDefault();
}
} else if (!event.ctrlKey) {
const newSymbol = getSymbolSubstitute(event.code, event.shiftKey);
if (newSymbol) {
text.replaceWith(newSymbol);
event.preventDefault();
}
}
}, [thisRef]);
return (
<div className={`flex flex-col ${dimensions} ${cursor}`}>
{label ?
<Label
text={label}
htmlFor={id}
className='mb-2'
/> : null}
<CodeMirror id={id}
ref={thisRef}
basicSetup={editorSetup}
theme={customTheme}
extensions={editorExtensions}
indentWithTab={false}
onChange={onChange}
editable={!disabled}
onKeyDown={handleInput}
{...props}
/>
</div>
);
}
export default RSInput;