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