ConceptPortal-public/rsconcept/frontend/src/components/RSInput/index.tsx

176 lines
5.3 KiB
TypeScript
Raw Normal View History

2023-08-14 23:02:41 +03:00
2023-08-10 18:35:49 +03:00
import { Extension } from '@codemirror/state';
import { tags } from '@lezer/highlight';
2023-08-10 18:35:49 +03:00
import { createTheme } from '@uiw/codemirror-themes';
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
2023-08-10 18:35:49 +03:00
import { EditorView } from 'codemirror';
import { RefObject, useCallback, useMemo, useRef } from 'react';
2023-08-10 18:35:49 +03:00
2023-08-15 00:41:09 +03:00
import { useRSForm } from '../../context/RSFormContext';
2023-08-12 20:52:11 +03:00
import { useConceptTheme } from '../../context/ThemeContext';
2023-09-11 20:31:54 +03:00
import { TokenID } from '../../models/rslang';
import Label from '../Common/Label';
2023-08-14 23:02:41 +03:00
import { ccBracketMatching } from './bracketMatching';
2023-08-13 00:57:31 +03:00
import { RSLanguage } from './rslang';
import { getSymbolSubstitute,RSTextWrapper } from './textEditing';
2023-09-27 23:36:51 +03:00
import { rsHoverTooltip } from './tooltip';
2023-08-10 18:35:49 +03:00
const editorSetup: BasicSetupOptions = {
2023-08-14 23:02:41 +03:00
highlightSpecialChars: false,
2023-08-10 18:35:49 +03:00
history: true,
2023-08-14 23:02:41 +03:00
drawSelection: false,
syntaxHighlighting: false,
2023-08-10 18:35:49 +03:00
defaultKeymap: true,
historyKeymap: true,
lineNumbers: false,
highlightActiveLineGutter: false,
foldGutter: false,
2023-09-29 15:33:32 +03:00
dropCursor: true,
2023-08-10 18:35:49 +03:00
allowMultipleSelections: false,
indentOnInput: false,
bracketMatching: false,
2023-08-10 18:35:49 +03:00
closeBrackets: false,
autocompletion: false,
rectangularSelection: false,
crosshairCursor: false,
highlightActiveLine: false,
highlightSelectionMatches: false,
closeBracketsKeymap: false,
searchKeymap: false,
foldKeymap: false,
completionKeymap: false,
lintKeymap: false
};
interface RSInputProps
2023-09-11 17:56:32 +03:00
extends Pick<ReactCodeMirrorProps,
2023-11-07 18:03:37 +03:00
'id' | 'height' | 'minHeight' | 'maxHeight' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'
2023-09-11 17:56:32 +03:00
> {
label?: string
dimensions?: string
2023-11-06 02:20:16 +03:00
disabled?: boolean
2023-11-06 18:44:14 +03:00
noTooltip?: boolean
innerref?: RefObject<ReactCodeMirrorRef> | undefined
onChange?: (newValue: string) => void
2023-08-11 10:54:53 +03:00
}
function RSInput({
2023-11-06 18:44:14 +03:00
id, label, innerref, onChange, disabled, noTooltip,
dimensions = 'w-full',
2023-08-11 10:54:53 +03:00
...props
}: RSInputProps) {
2023-08-27 15:39:49 +03:00
const { darkMode, colors } = useConceptTheme();
2023-08-15 00:41:09 +03:00
const { schema } = useRSForm();
2023-08-10 18:35:49 +03:00
const internalRef = useRef<ReactCodeMirrorRef>(null);
const thisRef = useMemo(
() => {
return innerref ?? internalRef;
2023-08-23 22:57:25 +03:00
}, [internalRef, innerref]);
2023-11-06 02:20:16 +03:00
const cursor = useMemo(() => !disabled ? 'cursor-text': 'cursor-default', [disabled]);
const customTheme: Extension = useMemo(
() => createTheme({
theme: darkMode ? 'dark' : 'light',
settings: {
fontFamily: 'inherit',
2023-11-06 02:20:16 +03:00
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
]
2023-11-06 02:20:16 +03:00
}), [disabled, colors, darkMode]);
2023-08-14 23:02:41 +03:00
const editorExtensions = useMemo(
() => [
EditorView.lineWrapping,
RSLanguage,
ccBracketMatching(darkMode),
2023-11-06 18:44:14 +03:00
... noTooltip ? [] : [rsHoverTooltip(schema?.items || [])],
], [darkMode, schema?.items, noTooltip]);
2023-08-14 23:02:41 +03:00
const handleInput = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (!thisRef.current) {
return;
}
const text = new RSTextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
2023-10-11 15:19:27 +03:00
if (event.shiftKey && !event.altKey) {
if (event.key === '*') {
text.insertToken(TokenID.DECART);
event.preventDefault();
2023-11-05 18:41:28 +03:00
} else if (event.code === 'KeyB') {
2023-10-11 15:19:27 +03:00
text.insertChar('');
event.preventDefault();
2023-11-05 18:41:28 +03:00
} else if (event.code === 'KeyZ') {
text.insertChar('Z');
event.preventDefault();
2023-11-06 23:13:27 +03:00
} 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();
2023-10-11 15:19:27 +03:00
}
2023-11-05 18:41:28 +03:00
} else if (event.altKey) {
if (text.processAltKey(event.code, event.shiftKey)) {
event.preventDefault();
}
} else if (!event.ctrlKey) {
const newSymbol = getSymbolSubstitute(event.code, event.shiftKey);
2023-11-05 18:41:28 +03:00
if (newSymbol) {
text.replaceWith(newSymbol);
event.preventDefault();
}
}
}, [thisRef]);
2023-08-10 18:35:49 +03:00
return (
<div className={`flex flex-col ${dimensions} ${cursor}`}>
{label &&
<Label
text={label}
htmlFor={id}
className='mb-2'
/>}
<CodeMirror id={id}
ref={thisRef}
2023-08-10 18:35:49 +03:00
basicSetup={editorSetup}
theme={customTheme}
2023-08-11 10:54:53 +03:00
extensions={editorExtensions}
2023-08-10 18:35:49 +03:00
indentWithTab={false}
onChange={onChange}
2023-11-06 02:20:16 +03:00
editable={!disabled}
onKeyDown={handleInput}
2023-08-11 10:54:53 +03:00
{...props}
2023-08-10 18:35:49 +03:00
/>
</div>
);
}
export default RSInput;