Reimplement CstCreate and minor UI fixes

This commit is contained in:
IRBorisov 2023-08-16 18:32:37 +03:00
parent 1f9e761565
commit 4fbfda4eb4
21 changed files with 282 additions and 156 deletions

View File

@ -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(

View File

@ -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}]})

View File

@ -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={

View File

@ -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}

View File

@ -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>
);

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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='Комментарий'

View File

@ -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

View File

@ -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!
});

View File

@ -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

View File

@ -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='Договоренность об интерпретации неопределяемого понятия&#x000D;&#x000A;Комментарий к производному понятию'
rows={2}
value={convention}
spellCheck
onChange={event => { setConvention(event.target.value); }}
/>
</div>
<div className='h-[4rem]'></div>
</Modal>
);
}

View File

@ -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() {

View File

@ -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

View File

@ -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'>

View File

@ -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>

View File

@ -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

View File

@ -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
}

View File

@ -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: