mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Reimplement CstCreate and minor UI fixes
This commit is contained in:
parent
1f9e761565
commit
4fbfda4eb4
|
@ -69,11 +69,20 @@ class StandaloneCstSerializer(serializers.ModelSerializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class CstCreateSerializer(serializers.Serializer):
|
||||
alias = serializers.CharField(max_length=8)
|
||||
cst_type = serializers.CharField(max_length=10)
|
||||
class CstCreateSerializer(serializers.ModelSerializer):
|
||||
insert_after = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = Constituenta
|
||||
fields = 'alias', 'cst_type', 'convention', 'term_raw', 'definition_raw', 'definition_formal', 'insert_after'
|
||||
|
||||
def validate(self, attrs):
|
||||
if ('term_raw' in attrs):
|
||||
attrs['term_resolved'] = attrs['term_raw']
|
||||
if ('definition_raw' in attrs):
|
||||
attrs['definition_resolved'] = attrs['definition_raw']
|
||||
return attrs
|
||||
|
||||
|
||||
class CstListSerlializer(serializers.Serializer):
|
||||
items = serializers.ListField(
|
||||
|
|
|
@ -215,6 +215,28 @@ class TestRSFormViewset(APITestCase):
|
|||
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
||||
self.assertEqual(x4.order, 3)
|
||||
|
||||
def test_create_constituenta_data(self):
|
||||
data = json.dumps({
|
||||
'alias': 'X3',
|
||||
'cst_type': 'basic',
|
||||
'convention': '1',
|
||||
'term_raw': '2',
|
||||
'definition_formal': '3',
|
||||
'definition_raw': '4'
|
||||
})
|
||||
schema = self.rsform_owned
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data['new_cst']['alias'], 'X3')
|
||||
self.assertEqual(response.data['new_cst']['cst_type'], 'basic')
|
||||
self.assertEqual(response.data['new_cst']['convention'], '1')
|
||||
self.assertEqual(response.data['new_cst']['term_raw'], '2')
|
||||
self.assertEqual(response.data['new_cst']['term_resolved'], '2')
|
||||
self.assertEqual(response.data['new_cst']['definition_formal'], '3')
|
||||
self.assertEqual(response.data['new_cst']['definition_raw'], '4')
|
||||
self.assertEqual(response.data['new_cst']['definition_resolved'], '4')
|
||||
|
||||
def test_delete_constituenta(self):
|
||||
schema = self.rsform_owned
|
||||
data = json.dumps({'items': [{'id': 1337}]})
|
||||
|
|
|
@ -82,6 +82,14 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
|||
serializer.validated_data['cst_type'])
|
||||
else:
|
||||
constituenta = schema.insert_last(serializer.validated_data['alias'], serializer.validated_data['cst_type'])
|
||||
|
||||
constituenta.convention = serializer.validated_data.get('convention', '')
|
||||
constituenta.term_raw = serializer.validated_data.get('term_raw', '')
|
||||
constituenta.term_resolved = serializer.validated_data.get('term_resolved', '')
|
||||
constituenta.definition_formal = serializer.validated_data.get('definition_formal', '')
|
||||
constituenta.definition_raw = serializer.validated_data.get('definition_raw', '')
|
||||
constituenta.definition_resolved = serializer.validated_data.get('definition_resolved', '')
|
||||
constituenta.save()
|
||||
schema.refresh_from_db()
|
||||
outSerializer = serializers.RSFormDetailsSerlializer(schema)
|
||||
response = Response(status=201, data={
|
||||
|
|
|
@ -8,16 +8,17 @@ extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'> {
|
|||
label: string
|
||||
widthClass?: string
|
||||
colorClass?: string
|
||||
singleRow?: boolean
|
||||
}
|
||||
|
||||
function TextInput({
|
||||
id, required, label,
|
||||
id, required, label, singleRow,
|
||||
widthClass = 'w-full',
|
||||
colorClass = 'clr-input',
|
||||
...props
|
||||
}: TextInputProps) {
|
||||
return (
|
||||
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
||||
<div className={`flex ${singleRow ? 'items-center gap-4' : 'flex-col items-start'} [&:not(:first-child)]:mt-3`}>
|
||||
<Label
|
||||
text={label}
|
||||
required={required}
|
||||
|
|
|
@ -7,7 +7,7 @@ interface TextURLProps {
|
|||
|
||||
function TextURL({ text, href }: TextURLProps) {
|
||||
return (
|
||||
<Link className='text-sm font-bold text-blue-400 dark:text-orange-600 dark:hover:text-orange-400 hover:underline hover:text-blue-600' to={href}>
|
||||
<Link className='font-bold hover:underline clr-url' to={href}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import ConceptTooltip from '../Common/ConceptTooltip';
|
||||
import TextURL from '../Common/TextURL';
|
||||
import { BellIcon, PlusIcon, SquaresIcon } from '../Icons';
|
||||
import { PlusIcon, SquaresIcon } from '../Icons';
|
||||
import NavigationButton from './NavigationButton';
|
||||
|
||||
function UserTools() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
|
||||
const navigateCreateRSForm = () => { navigate('/rsform-create'); };
|
||||
const navigateMyWork = () => { navigate('/library?filter=personal'); };
|
||||
|
||||
const handleNotifications = () => {
|
||||
toast.info('Уведомления в разработке');
|
||||
};
|
||||
const navigateCreateRSForm = () => navigate('/rsform-create');
|
||||
const navigateMyWork = () => navigate('/library?filter=personal');
|
||||
|
||||
return (
|
||||
<div className='flex items-center px-2 border-r-2 border-gray-400 dark:border-gray-300'>
|
||||
|
@ -30,7 +25,6 @@ function UserTools() {
|
|||
/>}
|
||||
{ !user &&
|
||||
<NavigationButton id='items-nav-help'
|
||||
description='Невозможно создать новую схему. Войдите в систему'
|
||||
icon={<PlusIcon />}
|
||||
/>}
|
||||
<ConceptTooltip anchorSelect='#items-nav-help' clickable>
|
||||
|
@ -45,7 +39,6 @@ function UserTools() {
|
|||
</ConceptTooltip>
|
||||
</span>
|
||||
{ user && <NavigationButton icon={<SquaresIcon />} description='Мои схемы' onClick={navigateMyWork} /> }
|
||||
{ user && user.is_staff && <NavigationButton icon={<BellIcon />} description='Уведомления' onClick={handleNotifications} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,12 +4,15 @@ import { tags as t } from '@lezer/highlight';
|
|||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
import { EditorView } from 'codemirror';
|
||||
import { Ref, useMemo } from 'react';
|
||||
import { RefObject, useCallback, useMemo, useRef } from 'react';
|
||||
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { TokenID } from '../../utils/enums';
|
||||
import Label from '../Common/Label';
|
||||
import { ccBracketMatching } from './bracketMatching';
|
||||
import { RSLanguage } from './rslang';
|
||||
import { getSymbolSubstitute,TextWrapper } from './textEditing';
|
||||
import { rshoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
|
@ -41,19 +44,25 @@ const editorSetup: BasicSetupOptions = {
|
|||
};
|
||||
|
||||
interface RSInputProps
|
||||
extends Omit<ReactCodeMirrorProps, 'onChange'> {
|
||||
innerref?: Ref<ReactCodeMirrorRef> | undefined
|
||||
onChange: (newValue: string) => void
|
||||
onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void
|
||||
extends Omit<ReactCodeMirrorProps, 'onChange'| 'onKeyDown'> {
|
||||
label?: string
|
||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||
onChange?: (newValue: string) => void
|
||||
}
|
||||
|
||||
function RSInput({
|
||||
innerref, onChange, editable,
|
||||
id, label, innerref, onChange, editable,
|
||||
...props
|
||||
}: RSInputProps) {
|
||||
const { darkMode } = useConceptTheme();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = useMemo(
|
||||
() => {
|
||||
return innerref ?? internalRef;
|
||||
}, [internalRef, innerref])
|
||||
|
||||
const cursor = useMemo(() => editable ? 'cursor-text': 'cursor-default', [editable]);
|
||||
const lightTheme: Extension = useMemo(
|
||||
() => createTheme({
|
||||
|
@ -105,16 +114,47 @@ function RSInput({
|
|||
rshoverTooltip(schema?.items || []),
|
||||
], [darkMode, schema?.items]);
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!thisRef.current) {
|
||||
return;
|
||||
}
|
||||
const text = new TextWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
if (event.shiftKey && event.key === '*' && !event.altKey) {
|
||||
text.insertToken(TokenID.DECART);
|
||||
} else if (event.altKey) {
|
||||
if (!text.processAltKey(event.key)) {
|
||||
return;
|
||||
}
|
||||
} else if (!event.ctrlKey) {
|
||||
const newSymbol = getSymbolSubstitute(event.key);
|
||||
if (!newSymbol) {
|
||||
return;
|
||||
}
|
||||
text.replaceWith(newSymbol);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
}, [thisRef]);
|
||||
|
||||
return (
|
||||
<div className={`w-full ${cursor} text-lg`}>
|
||||
<CodeMirror
|
||||
ref={innerref}
|
||||
{label &&
|
||||
<Label
|
||||
text={label}
|
||||
required={false}
|
||||
htmlFor={id}
|
||||
/>}
|
||||
<CodeMirror id={id}
|
||||
ref={thisRef}
|
||||
basicSetup={editorSetup}
|
||||
theme={darkMode ? darkTheme : lightTheme}
|
||||
extensions={editorExtensions}
|
||||
indentWithTab={false}
|
||||
onChange={value => onChange(value)}
|
||||
onChange={onChange}
|
||||
editable={editable}
|
||||
onKeyDown={handleInput}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { createContext, useCallback, useContext, useState } from 'react';
|
|||
interface INavSearchContext {
|
||||
query: string
|
||||
setQuery: (value: string) => void
|
||||
cleanQuery: () => void
|
||||
resetQuery: () => void
|
||||
}
|
||||
|
||||
const NavSearchContext = createContext<INavSearchContext | null>(null);
|
||||
|
@ -24,13 +24,13 @@ interface NavSearchStateProps {
|
|||
export const NavSearchState = ({ children }: NavSearchStateProps) => {
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const cleanQuery = useCallback(() => setQuery(''), []);
|
||||
const resetQuery = useCallback(() => setQuery(''), []);
|
||||
|
||||
return (
|
||||
<NavSearchContext.Provider value={{
|
||||
query,
|
||||
setQuery,
|
||||
cleanQuery
|
||||
resetQuery: resetQuery
|
||||
}}>
|
||||
{children}
|
||||
</NavSearchContext.Provider>
|
||||
|
|
|
@ -102,6 +102,10 @@
|
|||
@apply bg-white dark:bg-gray-900 checked:bg-blue-700 dark:checked:bg-orange-500
|
||||
}
|
||||
|
||||
.clr-url {
|
||||
@apply hover:text-blue-600 text-blue-400 dark:text-orange-600 dark:hover:text-orange-400
|
||||
}
|
||||
|
||||
.text-red {
|
||||
@apply text-red-600 dark:text-red-400
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ function CreateRSFormPage() {
|
|||
<RequireAuth>
|
||||
<Form title='Создание концептуальной схемы'
|
||||
onSubmit={handleSubmit}
|
||||
widthClass='max-w-lg mt-4'
|
||||
widthClass='max-w-lg w-full mt-4'
|
||||
>
|
||||
<TextInput id='title' label='Полное название' type='text'
|
||||
required={!file}
|
||||
|
@ -67,10 +67,10 @@ function CreateRSFormPage() {
|
|||
onChange={event => setTitle(event.target.value)}
|
||||
/>
|
||||
<TextInput id='alias' label='Сокращение' type='text'
|
||||
singleRow
|
||||
required={!file}
|
||||
value={alias}
|
||||
placeholder={file && 'Загрузить из файла'}
|
||||
widthClass='max-w-sm'
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
/>
|
||||
<TextArea id='comment' label='Комментарий'
|
||||
|
|
|
@ -3,6 +3,8 @@ import { useIntl } from 'react-intl';
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import ConceptDataTable from '../../components/Common/ConceptDataTable';
|
||||
import TextURL from '../../components/Common/TextURL';
|
||||
import { useNavSearch } from '../../context/NavSearchContext';
|
||||
import { useUsers } from '../../context/UsersContext';
|
||||
import { IRSFormMeta } from '../../utils/models'
|
||||
|
||||
|
@ -11,13 +13,12 @@ interface ViewLibraryProps {
|
|||
}
|
||||
|
||||
function ViewLibrary({ schemas }: ViewLibraryProps) {
|
||||
const { resetQuery: cleanQuery } = useNavSearch();
|
||||
const navigate = useNavigate();
|
||||
const intl = useIntl();
|
||||
const { getUserLabel } = useUsers();
|
||||
|
||||
const openRSForm = (schema: IRSFormMeta) => {
|
||||
navigate(`/rsforms/${schema.id}`);
|
||||
};
|
||||
const openRSForm = (schema: IRSFormMeta) => navigate(`/rsforms/${schema.id}`);
|
||||
|
||||
const columns = useMemo(() =>
|
||||
[
|
||||
|
@ -55,8 +56,7 @@ function ViewLibrary({ schemas }: ViewLibraryProps) {
|
|||
sortable: true,
|
||||
reorder: true
|
||||
}
|
||||
], [intl, getUserLabel]
|
||||
);
|
||||
], [intl, getUserLabel]);
|
||||
|
||||
return (
|
||||
<ConceptDataTable
|
||||
|
@ -70,7 +70,15 @@ function ViewLibrary({ schemas }: ViewLibraryProps) {
|
|||
|
||||
noDataComponent={<span className='flex flex-col justify-center p-2 text-center'>
|
||||
<p>Список схем пуст</p>
|
||||
<p>Измените фильтр или создайте новую концептуальную схему</p>
|
||||
<p>
|
||||
<TextURL text='Создать схему' href='/rsform-create'/>
|
||||
<span> | </span>
|
||||
<TextURL text='Все схемы' href='/library?filter=common'/>
|
||||
<span> | </span>
|
||||
<span className='cursor-pointer hover:underline clr-url' onClick={cleanQuery}>
|
||||
<b>Очистить фильтр</b>
|
||||
</span>
|
||||
</p>
|
||||
</span>}
|
||||
|
||||
pagination
|
||||
|
|
|
@ -11,7 +11,7 @@ import ViewLibrary from './ViewLibrary';
|
|||
|
||||
function LibraryPage() {
|
||||
const search = useLocation().search;
|
||||
const { query, cleanQuery } = useNavSearch();
|
||||
const { query, resetQuery: cleanQuery } = useNavSearch();
|
||||
const { user } = useAuth();
|
||||
const library = useLibrary();
|
||||
|
||||
|
@ -21,10 +21,12 @@ function LibraryPage() {
|
|||
useLayoutEffect(() => {
|
||||
const filterType = new URLSearchParams(search).get('filter');
|
||||
if (filterType === 'common') {
|
||||
cleanQuery();
|
||||
setFilterParams({
|
||||
is_common: true
|
||||
});
|
||||
} else if (filterType === 'personal' && user) {
|
||||
cleanQuery();
|
||||
setFilterParams({
|
||||
ownedBy: user.id!
|
||||
});
|
||||
|
|
|
@ -41,7 +41,7 @@ function LoginPage() {
|
|||
return (
|
||||
<div className='w-full py-2'> { user
|
||||
? <b>{`Вы вошли в систему как ${user.username}`}</b>
|
||||
: <Form title='Ввод данных пользователя' onSubmit={handleSubmit} widthClass='w-[20rem]'>
|
||||
: <Form title='Ввод данных пользователя' onSubmit={handleSubmit} widthClass='w-[21rem]'>
|
||||
<TextInput id='username'
|
||||
label='Имя пользователя'
|
||||
required
|
||||
|
|
|
@ -2,26 +2,51 @@ import { useEffect, useState } from 'react';
|
|||
|
||||
import ConceptSelect from '../../components/Common/ConceptSelect';
|
||||
import Modal from '../../components/Common/Modal';
|
||||
import { type CstType } from '../../utils/models';
|
||||
import TextArea from '../../components/Common/TextArea';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { CstType,ICstCreateData } from '../../utils/models';
|
||||
import { CstTypeSelector, getCstTypeLabel } from '../../utils/staticUI';
|
||||
|
||||
interface DlgCreateCstProps {
|
||||
hideWindow: () => void
|
||||
defaultType?: CstType
|
||||
onCreate: (type: CstType) => void
|
||||
initial?: ICstCreateData
|
||||
onCreate: (data: ICstCreateData) => void
|
||||
}
|
||||
|
||||
function DlgCreateCst({ hideWindow, defaultType, onCreate }: DlgCreateCstProps) {
|
||||
function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
|
||||
const [validated, setValidated] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<CstType | undefined>(undefined);
|
||||
const [selectedType, setSelectedType] = useState<CstType>(CstType.BASE);
|
||||
|
||||
const [term, setTerm] = useState('');
|
||||
const [textDefinition, setTextDefinition] = useState('');
|
||||
const [expression, setExpression] = useState('');
|
||||
const [convention, setConvention] = useState('');
|
||||
|
||||
function getData(): ICstCreateData {
|
||||
return {
|
||||
cst_type: selectedType,
|
||||
insert_after: initial?.insert_after ?? null,
|
||||
alias: '',
|
||||
convention: convention,
|
||||
definition_formal: expression,
|
||||
definition_raw: textDefinition,
|
||||
term_raw: term
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (selectedType) onCreate(selectedType);
|
||||
onCreate(getData());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedType(defaultType);
|
||||
}, [defaultType]);
|
||||
if (initial) {
|
||||
setSelectedType(initial.cst_type);
|
||||
setTerm(initial.term_raw);
|
||||
setTextDefinition(initial.definition_raw);
|
||||
setExpression(initial.definition_formal);
|
||||
setConvention(initial.convention);
|
||||
}
|
||||
}, [initial]);
|
||||
|
||||
useEffect(() => {
|
||||
setValidated(selectedType !== undefined);
|
||||
|
@ -35,16 +60,45 @@ function DlgCreateCst({ hideWindow, defaultType, onCreate }: DlgCreateCstProps)
|
|||
canSubmit={validated}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className='fixed h-fit w-[15rem] px-2'>
|
||||
<div className='h-fit w-[35rem] px-2 mb-2 flex flex-col justify-stretch'>
|
||||
<div className='flex justify-center w-full'>
|
||||
<ConceptSelect
|
||||
className='my-4'
|
||||
className='my-2 min-w-[15rem] self-center'
|
||||
options={CstTypeSelector}
|
||||
placeholder='Выберите тип'
|
||||
values={selectedType ? [{ value: selectedType, label: getCstTypeLabel(selectedType) }] : []}
|
||||
onChange={data => { setSelectedType(data.length > 0 ? data[0].value : undefined); }}
|
||||
onChange={data => { setSelectedType(data.length > 0 ? data[0].value : CstType.BASE); }}
|
||||
/>
|
||||
</div>
|
||||
<TextArea id='term' label='Термин'
|
||||
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
|
||||
rows={2}
|
||||
value={term}
|
||||
spellCheck
|
||||
onChange={event => setTerm(event.target.value)}
|
||||
/>
|
||||
<RSInput id='expression' label='Формальное выражение'
|
||||
editable
|
||||
className='mt-2'
|
||||
height='5.5rem'
|
||||
value={expression}
|
||||
onChange={value => setExpression(value)}
|
||||
/>
|
||||
<TextArea id='definition' label='Текстовое определение'
|
||||
placeholder='Лингвистическая интерпретация формального выражения'
|
||||
rows={2}
|
||||
value={textDefinition}
|
||||
spellCheck
|
||||
onChange={event => { setTextDefinition(event.target.value); }}
|
||||
/>
|
||||
<TextArea id='convention' label='Конвенция / Комментарий'
|
||||
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
||||
rows={2}
|
||||
value={convention}
|
||||
spellCheck
|
||||
onChange={event => { setConvention(event.target.value); }}
|
||||
/>
|
||||
</div>
|
||||
<div className='h-[4rem]'></div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import TextArea from '../../components/Common/TextArea';
|
|||
import CstStatusInfo from '../../components/Help/InfoCstStatus';
|
||||
import { DumpBinIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { type CstType, EditMode, ICstUpdateData, SyntaxTree } from '../../utils/models';
|
||||
import { CstType, EditMode, ICstCreateData, ICstUpdateData, SyntaxTree } from '../../utils/models';
|
||||
import { getCstTypeLabel, getCstTypificationLabel } from '../../utils/staticUI';
|
||||
import EditorRSExpression from './EditorRSExpression';
|
||||
import ViewSideConstituents from './elements/ViewSideConstituents';
|
||||
|
@ -21,7 +21,7 @@ interface EditorConstituentaProps {
|
|||
activeID?: number
|
||||
onOpenEdit: (cstID: number) => void
|
||||
onShowAST: (expression: string, ast: SyntaxTree) => void
|
||||
onCreateCst: (selectedID: number | undefined, type: CstType | undefined) => void
|
||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
||||
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,16 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDe
|
|||
if (!activeID || !schema) {
|
||||
return;
|
||||
}
|
||||
onCreateCst(activeID, activeCst?.cstType);
|
||||
const data: ICstCreateData = {
|
||||
insert_after: activeID,
|
||||
cst_type: activeCst?.cstType ?? CstType.BASE,
|
||||
alias: '',
|
||||
term_raw: '',
|
||||
definition_formal: '',
|
||||
definition_raw: '',
|
||||
convention: '',
|
||||
};
|
||||
onCreateCst(data);
|
||||
}
|
||||
|
||||
function handleRename() {
|
||||
|
|
|
@ -10,12 +10,12 @@ import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, Sm
|
|||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { CstType, IConstituenta, ICstMovetoData } from '../../utils/models'
|
||||
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../utils/models'
|
||||
import { getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI';
|
||||
|
||||
interface EditorItemsProps {
|
||||
onOpenEdit: (cstID: number) => void
|
||||
onCreateCst: (selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => void
|
||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,16 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
return Math.max(position, prev);
|
||||
}, -1);
|
||||
const insert_where = selectedPosition >= 0 ? schema.items[selectedPosition].id : undefined;
|
||||
onCreateCst(insert_where, type, type !== undefined);
|
||||
const data: ICstCreateData = {
|
||||
insert_after: insert_where ?? null,
|
||||
cst_type: type ?? CstType.BASE,
|
||||
alias: '',
|
||||
term_raw: '',
|
||||
definition_formal: '',
|
||||
definition_raw: '',
|
||||
convention: '',
|
||||
};
|
||||
onCreateCst(data, type !== undefined);
|
||||
}
|
||||
|
||||
// Implement hotkeys for working with constituents table
|
||||
|
|
|
@ -2,10 +2,9 @@ import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
|||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import Button from '../../components/Common/Button';
|
||||
import Label from '../../components/Common/Label';
|
||||
import { Loader } from '../../components/Common/Loader';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { getSymbolSubstitute, TextWrapper } from '../../components/RSInput/textEditing';
|
||||
import { TextWrapper } from '../../components/RSInput/textEditing';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||
import { TokenID } from '../../utils/enums';
|
||||
|
@ -32,8 +31,8 @@ interface EditorRSExpressionProps {
|
|||
}
|
||||
|
||||
function EditorRSExpression({
|
||||
id, activeCst, label, disabled, isActive, placeholder, value, onShowAST,
|
||||
toggleEditMode, setTypification, onChange
|
||||
activeCst, disabled, isActive, value, onShowAST,
|
||||
toggleEditMode, setTypification, onChange, ... props
|
||||
}: EditorRSExpressionProps) {
|
||||
const { schema } = useRSForm();
|
||||
|
||||
|
@ -106,31 +105,6 @@ function EditorRSExpression({
|
|||
setIsModified(true);
|
||||
}, []);
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (!rsInput.current) {
|
||||
return;
|
||||
}
|
||||
const text = new TextWrapper(rsInput.current as Required<ReactCodeMirrorRef>);
|
||||
if (event.shiftKey && event.key === '*' && !event.altKey) {
|
||||
text.insertToken(TokenID.DECART);
|
||||
} else if (event.altKey) {
|
||||
if (!text.processAltKey(event.key)) {
|
||||
return;
|
||||
}
|
||||
} else if (!event.ctrlKey) {
|
||||
const newSymbol = getSymbolSubstitute(event.key);
|
||||
if (!newSymbol) {
|
||||
return;
|
||||
}
|
||||
text.replaceWith(newSymbol);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
setIsModified(true);
|
||||
}, []);
|
||||
|
||||
const EditButtons = useMemo(() => {
|
||||
return (<div className='flex items-center justify-between w-full'>
|
||||
<div className='text-sm w-fit'>
|
||||
|
@ -220,20 +194,14 @@ function EditorRSExpression({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Label
|
||||
text={label}
|
||||
required={false}
|
||||
htmlFor={id}
|
||||
/>
|
||||
<RSInput innerref={rsInput}
|
||||
className='mt-2'
|
||||
height='10.1rem'
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
editable={!disabled}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleInput}
|
||||
onFocus={handleFocusIn}
|
||||
{...props}
|
||||
/>
|
||||
<div className='flex w-full gap-4 py-1 mt-1 justify-stretch'>
|
||||
<div className='flex flex-col gap-2'>
|
||||
|
|
|
@ -18,7 +18,7 @@ import { useConceptTheme } from '../../context/ThemeContext';
|
|||
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||
import { prefixes, resources } from '../../utils/constants';
|
||||
import { Graph } from '../../utils/Graph';
|
||||
import { CstType, IConstituenta } from '../../utils/models';
|
||||
import { CstType, IConstituenta, ICstCreateData } from '../../utils/models';
|
||||
import { getCstClassColor, getCstStatusColor,
|
||||
GraphColoringSelector, GraphLayoutSelector,
|
||||
mapColoringLabels, mapLayoutLabels
|
||||
|
@ -57,7 +57,7 @@ export interface GraphEditorParams {
|
|||
|
||||
interface EditorTermGraphProps {
|
||||
onOpenEdit: (cstID: number) => void
|
||||
onCreateCst: (selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => void
|
||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
||||
}
|
||||
|
||||
|
@ -270,12 +270,16 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
|||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
const selectedPosition = allSelected.reduce((prev, cstID) => {
|
||||
const position = schema.items.findIndex(cst => cst.id === Number(cstID));
|
||||
return Math.max(position, prev);
|
||||
}, -1);
|
||||
const insert_where = selectedPosition >= 0 ? schema.items[selectedPosition].id : undefined;
|
||||
onCreateCst(insert_where, undefined);
|
||||
const data: ICstCreateData = {
|
||||
insert_after: null,
|
||||
cst_type: allSelected.length == 0 ? CstType.BASE: CstType.TERM,
|
||||
alias: '',
|
||||
term_raw: '',
|
||||
definition_formal: allSelected.map(id => schema.items.find(cst => cst.id === Number(id))!.alias).join(' '),
|
||||
definition_raw: '',
|
||||
convention: '',
|
||||
};
|
||||
onCreateCst(data);
|
||||
}
|
||||
|
||||
function handleDeleteCst() {
|
||||
|
@ -352,7 +356,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
|||
initial={getOptions()}
|
||||
onConfirm={handleChangeOptions}
|
||||
/>}
|
||||
<div className='flex flex-col py-2 border-t border-r max-w-[12.44rem] pr-2 text-sm select-none' style={{height: canvasHeight}}>
|
||||
<div className='flex flex-col border-t border-r max-w-[12.44rem] pr-2 pb-2 text-sm select-none' style={{height: canvasHeight}}>
|
||||
{hoverCst &&
|
||||
<div className='relative'>
|
||||
<InfoConstituenta
|
||||
|
@ -361,7 +365,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
|||
/>
|
||||
</div>}
|
||||
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center justify-between py-1'>
|
||||
<div className='mr-3 whitespace-nowrap'>
|
||||
Выбраны
|
||||
<span className='ml-1'>
|
||||
|
@ -459,7 +463,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
|||
className='relative border-t border-r'
|
||||
style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}}
|
||||
>
|
||||
<div className='relative top-0 right-0 z-10 flex m-2 flex-start'>
|
||||
<div className='relative top-0 right-0 z-10 flex mt-1 ml-2 flex-start'>
|
||||
<div className='px-1 py-1' id='items-graph-help' >
|
||||
<HelpIcon color='text-primary' size={5} />
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Loader } from '../../components/Common/Loader';
|
|||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
|
||||
import { CstType, ICstCreateData, SyntaxTree } from '../../utils/models';
|
||||
import { ICstCreateData, SyntaxTree } from '../../utils/models';
|
||||
import { createAliasFor } from '../../utils/staticUI';
|
||||
import DlgCloneRSForm from './DlgCloneRSForm';
|
||||
import DlgCreateCst from './DlgCreateCst';
|
||||
|
@ -53,8 +53,7 @@ function RSTabs() {
|
|||
const [toBeDeleted, setToBeDeleted] = useState<number[]>([]);
|
||||
const [showDeleteCst, setShowDeleteCst] = useState(false);
|
||||
|
||||
const [defaultType, setDefaultType] = useState<CstType | undefined>(undefined);
|
||||
const [insertWhere, setInsertWhere] = useState<number | undefined>(undefined);
|
||||
const [createInitialData, setCreateInitialData] = useState<ICstCreateData>();
|
||||
const [showCreateCst, setShowCreateCst] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
|
@ -90,15 +89,11 @@ function RSTabs() {
|
|||
}, [navigate, schema, activeTab]);
|
||||
|
||||
const handleCreateCst = useCallback(
|
||||
(type: CstType, selectedCst?: number) => {
|
||||
(data: ICstCreateData) => {
|
||||
if (!schema?.items) {
|
||||
return;
|
||||
}
|
||||
const data: ICstCreateData = {
|
||||
cst_type: type,
|
||||
alias: createAliasFor(type, schema),
|
||||
insert_after: selectedCst ?? insertWhere ?? null
|
||||
}
|
||||
data.alias = createAliasFor(data.cst_type, schema);
|
||||
cstCreate(data, newCst => {
|
||||
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
||||
navigateTo(activeTab, newCst.id);
|
||||
|
@ -115,15 +110,14 @@ function RSTabs() {
|
|||
}, TIMEOUT_UI_REFRESH);
|
||||
}
|
||||
});
|
||||
}, [schema, cstCreate, insertWhere, navigateTo, activeTab]);
|
||||
}, [schema, cstCreate, navigateTo, activeTab]);
|
||||
|
||||
const promptCreateCst = useCallback(
|
||||
(selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => {
|
||||
if (skipDialog && type) {
|
||||
handleCreateCst(type, selectedID);
|
||||
(initialData: ICstCreateData, skipDialog?: boolean) => {
|
||||
if (skipDialog) {
|
||||
handleCreateCst(initialData);
|
||||
} else {
|
||||
setDefaultType(type);
|
||||
setInsertWhere(selectedID);
|
||||
setCreateInitialData(initialData);
|
||||
setShowCreateCst(true);
|
||||
}
|
||||
}, [handleCreateCst]);
|
||||
|
@ -209,7 +203,7 @@ function RSTabs() {
|
|||
<DlgCreateCst
|
||||
hideWindow={() => setShowCreateCst(false)}
|
||||
onCreate={handleCreateCst}
|
||||
defaultType={defaultType}
|
||||
initial={createInitialData}
|
||||
/>}
|
||||
{showDeleteCst &&
|
||||
<DlgDeleteCst
|
||||
|
|
|
@ -156,7 +156,8 @@ export interface IConstituentaList {
|
|||
items: IConstituentaID[]
|
||||
}
|
||||
|
||||
export interface ICstCreateData extends Pick<IConstituentaMeta, 'alias' | 'cst_type'> {
|
||||
export interface ICstCreateData
|
||||
extends Pick<IConstituentaMeta, 'alias' | 'cst_type' | 'definition_raw' | 'term_raw' | 'convention' | 'definition_formal' > {
|
||||
insert_after: number | null
|
||||
}
|
||||
|
||||
|
|
|
@ -485,7 +485,7 @@ export function getRSErrorPrefix(error: IRSErrorDescription): string {
|
|||
export function getRSErrorMessage(error: IRSErrorDescription): string {
|
||||
switch (error.errorType) {
|
||||
case RSErrorType.syntax:
|
||||
return 'UNKNOWN SYNTAX ERROR';
|
||||
return 'Неопределенная синтаксическая ошибка';
|
||||
case RSErrorType.missingParanthesis:
|
||||
return 'Некорректная конструкция языка родов структур, проверьте структуру выражения';
|
||||
case RSErrorType.missingCurlyBrace:
|
||||
|
|
Loading…
Reference in New Issue
Block a user