mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Move to Vite. Refactor type system for data transport
This commit is contained in:
parent
a669f70efc
commit
20bea9c067
|
@ -10,7 +10,8 @@ function RunServer() {
|
||||||
RunBackend
|
RunBackend
|
||||||
RunFrontend
|
RunFrontend
|
||||||
Start-Sleep -Seconds 1
|
Start-Sleep -Seconds 1
|
||||||
Start-Process "http://127.0.0.1:8000/"
|
Start-Process "http://localhost:8000/"
|
||||||
|
Start-Process "http://localhost:3000/"
|
||||||
}
|
}
|
||||||
|
|
||||||
function RunBackend() {
|
function RunBackend() {
|
||||||
|
@ -29,7 +30,8 @@ function RunBackend() {
|
||||||
|
|
||||||
function RunFrontend() {
|
function RunFrontend() {
|
||||||
Set-Location $PSScriptRoot\frontend
|
Set-Location $PSScriptRoot\frontend
|
||||||
Invoke-Expression "cmd /c start powershell -Command { `$Host.UI.RawUI.WindowTitle = 'react'; & npm run start }"
|
& npm install
|
||||||
|
Invoke-Expression "cmd /c start powershell -Command { `$Host.UI.RawUI.WindowTitle = 'react'; & npm run dev }"
|
||||||
}
|
}
|
||||||
|
|
||||||
function FlushData {
|
function FlushData {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# Application settings
|
# Application settings
|
||||||
SECRET_KEY=django-insecure-)rq@!&v7l2r%2%q#n!uq+zk@=&yc0^&ql^7%2!%9u)vt1x&j=d
|
SECRET_KEY=django-insecure-)rq@!&v7l2r%2%q#n!uq+zk@=&yc0^&ql^7%2!%9u)vt1x&j=d
|
||||||
ALLOWED_HOSTS=rs.acconcept.ru;localhost
|
ALLOWED_HOSTS=rs.acconcept.ru;localhost
|
||||||
|
CSRF_TRUSTED_ORIGINS=http://rs.acconcept.ru:3000;localhost:3000
|
||||||
|
CORS_ALLOWED_ORIGINS=http://rs.acconcept.ru:3000;localhost:3000
|
||||||
|
|
||||||
|
|
||||||
# File locations
|
# File locations
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 4.2.3 on 2023-07-24 19:06
|
# Generated by Django 4.2.3 on 2023-07-26 15:19
|
||||||
|
|
||||||
import apps.rsform.models
|
import apps.rsform.models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -39,7 +39,7 @@ class Migration(migrations.Migration):
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('order', models.PositiveIntegerField(default=-1, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Позиция')),
|
('order', models.PositiveIntegerField(default=-1, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Позиция')),
|
||||||
('alias', models.CharField(default='undefined', max_length=8, verbose_name='Имя')),
|
('alias', models.CharField(default='undefined', max_length=8, verbose_name='Имя')),
|
||||||
('csttype', models.CharField(choices=[('basic', 'Base'), ('constant', 'Constant'), ('structure', 'Structured'), ('axiom', 'Axiom'), ('term', 'Term'), ('function', 'Function'), ('predicate', 'Predicate'), ('theorem', 'Theorem')], default='basic', max_length=10, verbose_name='Тип')),
|
('cst_type', models.CharField(choices=[('basic', 'Base'), ('constant', 'Constant'), ('structure', 'Structured'), ('axiom', 'Axiom'), ('term', 'Term'), ('function', 'Function'), ('predicate', 'Predicate'), ('theorem', 'Theorem')], default='basic', max_length=10, verbose_name='Тип')),
|
||||||
('convention', models.TextField(blank=True, default='', verbose_name='Комментарий/Конвенция')),
|
('convention', models.TextField(blank=True, default='', verbose_name='Комментарий/Конвенция')),
|
||||||
('term_raw', models.TextField(blank=True, default='', verbose_name='Термин (с отсылками)')),
|
('term_raw', models.TextField(blank=True, default='', verbose_name='Термин (с отсылками)')),
|
||||||
('term_resolved', models.TextField(blank=True, default='', verbose_name='Термин')),
|
('term_resolved', models.TextField(blank=True, default='', verbose_name='Термин')),
|
||||||
|
|
|
@ -86,11 +86,12 @@ class RSForm(models.Model):
|
||||||
schema=self,
|
schema=self,
|
||||||
order=position,
|
order=position,
|
||||||
alias=alias,
|
alias=alias,
|
||||||
csttype=type
|
cst_type=type
|
||||||
)
|
)
|
||||||
self._update_from_core()
|
self._update_from_core()
|
||||||
self.save()
|
self.save()
|
||||||
return Constituenta.objects.get(pk=result.pk)
|
result.refresh_from_db()
|
||||||
|
return result
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def insert_last(self, alias: str, type: CstType) -> 'Constituenta':
|
def insert_last(self, alias: str, type: CstType) -> 'Constituenta':
|
||||||
|
@ -102,7 +103,7 @@ class RSForm(models.Model):
|
||||||
schema=self,
|
schema=self,
|
||||||
order=position,
|
order=position,
|
||||||
alias=alias,
|
alias=alias,
|
||||||
csttype=type
|
cst_type=type
|
||||||
)
|
)
|
||||||
self._update_from_core()
|
self._update_from_core()
|
||||||
self.save()
|
self.save()
|
||||||
|
@ -217,7 +218,7 @@ class Constituenta(models.Model):
|
||||||
max_length=8,
|
max_length=8,
|
||||||
default='undefined'
|
default='undefined'
|
||||||
)
|
)
|
||||||
csttype = models.CharField(
|
cst_type = models.CharField(
|
||||||
verbose_name='Тип',
|
verbose_name='Тип',
|
||||||
max_length=10,
|
max_length=10,
|
||||||
choices=CstType.choices,
|
choices=CstType.choices,
|
||||||
|
@ -274,7 +275,7 @@ class Constituenta(models.Model):
|
||||||
alias=data['alias'],
|
alias=data['alias'],
|
||||||
schema=schema,
|
schema=schema,
|
||||||
order=order,
|
order=order,
|
||||||
csttype=data['cstType'],
|
cst_type=data['cstType'],
|
||||||
convention=data.get('convention', 'Без названия')
|
convention=data.get('convention', 'Без названия')
|
||||||
)
|
)
|
||||||
if 'definition' in data:
|
if 'definition' in data:
|
||||||
|
@ -284,9 +285,9 @@ class Constituenta(models.Model):
|
||||||
cst.definition_raw = data['definition']['text'].get('raw', '')
|
cst.definition_raw = data['definition']['text'].get('raw', '')
|
||||||
cst.definition_resolved = data['definition']['text'].get('resolved', '')
|
cst.definition_resolved = data['definition']['text'].get('resolved', '')
|
||||||
if 'term' in data:
|
if 'term' in data:
|
||||||
cst.term_raw = data['definition']['text'].get('raw', '')
|
cst.term_raw = data['term'].get('raw', '')
|
||||||
cst.term_resolved = data['definition']['text'].get('resolved', '')
|
cst.term_resolved = data['term'].get('resolved', '')
|
||||||
cst.term_forms = data['definition']['text'].get('forms', [])
|
cst.term_forms = data['term'].get('forms', [])
|
||||||
cst.save()
|
cst.save()
|
||||||
return cst
|
return cst
|
||||||
|
|
||||||
|
@ -294,7 +295,7 @@ class Constituenta(models.Model):
|
||||||
return {
|
return {
|
||||||
'entityUID': self.id,
|
'entityUID': self.id,
|
||||||
'type': 'constituenta',
|
'type': 'constituenta',
|
||||||
'cstType': self.csttype,
|
'cstType': self.cst_type,
|
||||||
'alias': self.alias,
|
'alias': self.alias,
|
||||||
'convention': self.convention,
|
'convention': self.convention,
|
||||||
'term': {
|
'term': {
|
||||||
|
|
|
@ -24,7 +24,7 @@ class ConstituentaSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Constituenta
|
model = Constituenta
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = ('id', 'order', 'alias', 'csttype')
|
read_only_fields = ('id', 'order', 'alias', 'cst_type')
|
||||||
|
|
||||||
def update(self, instance: Constituenta, validated_data):
|
def update(self, instance: Constituenta, validated_data):
|
||||||
instance.schema.save()
|
instance.schema.save()
|
||||||
|
@ -48,8 +48,8 @@ class StandaloneCstSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class CstCreateSerializer(serializers.Serializer):
|
class CstCreateSerializer(serializers.Serializer):
|
||||||
alias = serializers.CharField(max_length=8)
|
alias = serializers.CharField(max_length=8)
|
||||||
csttype = serializers.CharField(max_length=10)
|
cst_type = serializers.CharField(max_length=10)
|
||||||
insert_after = serializers.IntegerField(required=False)
|
insert_after = serializers.IntegerField(required=False, allow_null=True)
|
||||||
|
|
||||||
|
|
||||||
class CstListSerlializer(serializers.Serializer):
|
class CstListSerlializer(serializers.Serializer):
|
||||||
|
|
|
@ -53,7 +53,7 @@ class TestConstituenta(TestCase):
|
||||||
self.assertEqual(cst.schema, self.schema1)
|
self.assertEqual(cst.schema, self.schema1)
|
||||||
self.assertEqual(cst.order, 1)
|
self.assertEqual(cst.order, 1)
|
||||||
self.assertEqual(cst.alias, 'X1')
|
self.assertEqual(cst.alias, 'X1')
|
||||||
self.assertEqual(cst.csttype, CstType.BASE)
|
self.assertEqual(cst.cst_type, CstType.BASE)
|
||||||
self.assertEqual(cst.convention, '')
|
self.assertEqual(cst.convention, '')
|
||||||
self.assertEqual(cst.definition_formal, '')
|
self.assertEqual(cst.definition_formal, '')
|
||||||
self.assertEqual(cst.term_raw, '')
|
self.assertEqual(cst.term_raw, '')
|
||||||
|
|
|
@ -174,14 +174,14 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
def test_create_constituenta(self):
|
def test_create_constituenta(self):
|
||||||
data = json.dumps({'alias': 'X3', 'csttype': 'basic'})
|
data = json.dumps({'alias': 'X3', 'cst_type': 'basic'})
|
||||||
response = self.client.post(f'/api/rsforms/{self.rsform_unowned.id}/cst-create/',
|
response = self.client.post(f'/api/rsforms/{self.rsform_unowned.id}/cst-create/',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
schema = self.rsform_owned
|
schema = self.rsform_owned
|
||||||
Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
|
Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
|
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=2)
|
||||||
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
|
@ -189,7 +189,7 @@ class TestRSFormViewset(APITestCase):
|
||||||
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
||||||
self.assertEqual(x3.order, 3)
|
self.assertEqual(x3.order, 3)
|
||||||
|
|
||||||
data = json.dumps({'alias': 'X4', 'csttype': 'basic', 'insert_after': x2.id})
|
data = json.dumps({'alias': 'X4', 'cst_type': 'basic', 'insert_after': x2.id})
|
||||||
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
|
@ -204,8 +204,8 @@ class TestRSFormViewset(APITestCase):
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
|
x1 = Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
|
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=2)
|
||||||
data = json.dumps({'items': [{'id': x1.id}]})
|
data = json.dumps({'items': [{'id': x1.id}]})
|
||||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
|
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
|
@ -217,7 +217,7 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertEqual(x2.alias, 'X2')
|
self.assertEqual(x2.alias, 'X2')
|
||||||
self.assertEqual(x2.order, 1)
|
self.assertEqual(x2.order, 1)
|
||||||
|
|
||||||
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', csttype='basic', order=1)
|
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', cst_type='basic', order=1)
|
||||||
data = json.dumps({'items': [{'id': x3.id}]})
|
data = json.dumps({'items': [{'id': x3.id}]})
|
||||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
|
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
|
@ -230,8 +230,8 @@ class TestRSFormViewset(APITestCase):
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
x1 = Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
|
x1 = Constituenta.objects.create(schema=schema, alias='X1', cst_type='basic', order=1)
|
||||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
|
x2 = Constituenta.objects.create(schema=schema, alias='X2', cst_type='basic', order=2)
|
||||||
data = json.dumps({'items': [{'id': x2.id}], 'move_to': 1})
|
data = json.dumps({'items': [{'id': x2.id}], 'move_to': 1})
|
||||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
|
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
|
@ -242,7 +242,7 @@ class TestRSFormViewset(APITestCase):
|
||||||
self.assertEqual(x1.order, 2)
|
self.assertEqual(x1.order, 2)
|
||||||
self.assertEqual(x2.order, 1)
|
self.assertEqual(x2.order, 1)
|
||||||
|
|
||||||
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', csttype='basic', order=1)
|
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', cst_type='basic', order=1)
|
||||||
data = json.dumps({'items': [{'id': x3.id}], 'move_to': 1})
|
data = json.dumps({'items': [{'id': x3.id}], 'move_to': 1})
|
||||||
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
|
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
|
||||||
data=data, content_type='application/json')
|
data=data, content_type='application/json')
|
||||||
|
|
|
@ -56,16 +56,18 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
||||||
schema: models.RSForm = self.get_object()
|
schema: models.RSForm = self.get_object()
|
||||||
serializer = serializers.CstCreateSerializer(data=request.data)
|
serializer = serializers.CstCreateSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
if ('insert_after' in serializer.validated_data):
|
if ('insert_after' in serializer.validated_data and serializer.validated_data['insert_after'] is not None):
|
||||||
cstafter = models.Constituenta.objects.get(pk=serializer.validated_data['insert_after'])
|
cstafter = models.Constituenta.objects.get(pk=serializer.validated_data['insert_after'])
|
||||||
constituenta = schema.insert_at(cstafter.order + 1,
|
constituenta = schema.insert_at(cstafter.order + 1,
|
||||||
serializer.validated_data['alias'],
|
serializer.validated_data['alias'],
|
||||||
serializer.validated_data['csttype'])
|
serializer.validated_data['cst_type'])
|
||||||
else:
|
else:
|
||||||
constituenta = schema.insert_last(serializer.validated_data['alias'], serializer.validated_data['csttype'])
|
constituenta = schema.insert_last(serializer.validated_data['alias'], serializer.validated_data['cst_type'])
|
||||||
schema.refresh_from_db()
|
schema.refresh_from_db()
|
||||||
outSerializer = serializers.RSFormDetailsSerlializer(schema)
|
outSerializer = serializers.RSFormDetailsSerlializer(schema)
|
||||||
response = Response(status=201, data={'new_cst': constituenta.to_json(), 'schema': outSerializer.data})
|
response = Response(status=201, data={
|
||||||
|
'new_cst': serializers.ConstituentaSerializer(constituenta).data,
|
||||||
|
'schema': outSerializer.data})
|
||||||
response['Location'] = constituenta.get_absolute_url()
|
response['Location'] = constituenta.get_absolute_url()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -175,13 +177,15 @@ def create_rsform(request):
|
||||||
data = utils.read_trs(request.FILES['file'].file)
|
data = utils.read_trs(request.FILES['file'].file)
|
||||||
if ('title' in request.data and request.data['title'] != ''):
|
if ('title' in request.data and request.data['title'] != ''):
|
||||||
data['title'] = request.data['title']
|
data['title'] = request.data['title']
|
||||||
|
if data['title'] == '':
|
||||||
|
data['title'] = 'Без названия ' + request.FILES['file'].fileName
|
||||||
if ('alias' in request.data and request.data['alias'] != ''):
|
if ('alias' in request.data and request.data['alias'] != ''):
|
||||||
data['alias'] = request.data['alias']
|
data['alias'] = request.data['alias']
|
||||||
if ('comment' in request.data and request.data['comment'] != ''):
|
if ('comment' in request.data and request.data['comment'] != ''):
|
||||||
data['comment'] = request.data['comment']
|
data['comment'] = request.data['comment']
|
||||||
is_common = True
|
is_common = True
|
||||||
if ('is_common' in request.data):
|
if ('is_common' in request.data):
|
||||||
is_common = request.data['is_common']
|
is_common = request.data['is_common'] == 'true'
|
||||||
schema = models.RSForm.import_json(owner, data, is_common)
|
schema = models.RSForm.import_json(owner, data, is_common)
|
||||||
result = serializers.RSFormSerializer(schema)
|
result = serializers.RSFormSerializer(schema)
|
||||||
return Response(status=201, data=result.data)
|
return Response(status=201, data=result.data)
|
||||||
|
|
|
@ -64,12 +64,10 @@ REST_FRAMEWORK = {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# CORS_ORIGIN_ALLOW_ALL = True
|
||||||
CORS_ORIGIN_ALLOW_ALL = True
|
|
||||||
CORS_ALLOW_CREDENTIALS = True
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
# TODO: use env setup to populate allowed_origins in production
|
|
||||||
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000').split(';')
|
|
||||||
CORS_ALLOWED_ORIGINS = os.environ.get('CORS_ALLOWED_ORIGINS', 'http://localhost:3000').split(';')
|
CORS_ALLOWED_ORIGINS = os.environ.get('CORS_ALLOWED_ORIGINS', 'http://localhost:3000').split(';')
|
||||||
|
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000').split(';')
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|
|
@ -4,28 +4,27 @@
|
||||||
"es2021": true
|
"es2021": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"standard-with-typescript",
|
"eslint:recommended",
|
||||||
"plugin:react/recommended",
|
"plugin:@typescript-eslint/recommended-type-checked",
|
||||||
"plugin:react/jsx-runtime"
|
"plugin:react-hooks/recommended"
|
||||||
],
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": "latest",
|
"ecmaVersion": "latest",
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"project": ["tsconfig.json"]
|
"project": ["tsconfig.json", "tsconfig.node.json"]
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react", "simple-import-sort"
|
"react-refresh", "simple-import-sort"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"react-refresh/only-export-components": [
|
||||||
|
"off",
|
||||||
|
{ "allowConstantExport": true }
|
||||||
|
],
|
||||||
"simple-import-sort/imports": "warn",
|
"simple-import-sort/imports": "warn",
|
||||||
"@typescript-eslint/no-unused-vars": "warn",
|
|
||||||
"no-trailing-spaces": "warn",
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
"no-multiple-empty-lines": "warn",
|
"@typescript-eslint/no-unsafe-argument": "off"
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
|
||||||
"@typescript-eslint/semi": "off",
|
|
||||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
|
||||||
"@typescript-eslint/space-before-function-paren": "off",
|
|
||||||
"@typescript-eslint/indent": "off",
|
|
||||||
"object-shorthand": "off"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
41
rsconcept/frontend/.gitignore
vendored
41
rsconcept/frontend/.gitignore
vendored
|
@ -1,23 +1,24 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# Logs
|
||||||
|
logs
|
||||||
# dependencies
|
*.log
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
|
@ -7,16 +7,10 @@ RUN apt-get update -qq && \
|
||||||
# ======= Build =======
|
# ======= Build =======
|
||||||
FROM node-base as builder
|
FROM node-base as builder
|
||||||
|
|
||||||
ENV NODE_ENV production
|
|
||||||
WORKDIR /result
|
WORKDIR /result
|
||||||
|
|
||||||
# Install dependencies
|
COPY ./ ./
|
||||||
COPY *.json *.js ./
|
RUN npm install
|
||||||
RUN npm ci --only=production
|
|
||||||
|
|
||||||
# Build deployment files
|
|
||||||
COPY ./public ./public
|
|
||||||
COPY ./src ./src
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ========= Server =======
|
# ========= Server =======
|
||||||
|
@ -33,7 +27,7 @@ USER node
|
||||||
|
|
||||||
# Bring up deployment files
|
# Bring up deployment files
|
||||||
WORKDIR /home/node
|
WORKDIR /home/node
|
||||||
COPY --chown=node:node --from=builder /result/build ./
|
COPY --chown=node:node --from=builder /result/dist ./
|
||||||
|
|
||||||
# Start server through docker-compose
|
# Start server through docker-compose
|
||||||
# serve -s /home/node -l 3000
|
# serve -s /home/node -l 3000
|
|
@ -1,14 +1,12 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ru">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
<link rel="icon" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="Веб-приложение для работы с концептуальными схемами" />
|
<meta name="description" content="Веб-приложение для работы с концептуальными схемами" />
|
||||||
|
|
||||||
<link rel="manifest" id="manifest-placeholder" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
|
|
||||||
<title>Концепт Портал</title>
|
<title>Концепт Портал</title>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -25,7 +23,7 @@
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>Включите использование JavaScript для работы с данным веб-приложением.</noscript>
|
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
15822
rsconcept/frontend/package-lock.json
generated
15822
rsconcept/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
|
||||||
"@testing-library/react": "^13.4.0",
|
|
||||||
"@testing-library/user-event": "^13.5.0",
|
|
||||||
"@types/jest": "^27.5.2",
|
|
||||||
"@types/node": "^16.18.34",
|
|
||||||
"@types/react": "^18.2.7",
|
|
||||||
"@types/react-dom": "^18.2.4",
|
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
@ -18,42 +18,26 @@
|
||||||
"react-error-boundary": "^4.0.10",
|
"react-error-boundary": "^4.0.10",
|
||||||
"react-intl": "^6.4.4",
|
"react-intl": "^6.4.4",
|
||||||
"react-loader-spinner": "^5.3.4",
|
"react-loader-spinner": "^5.3.4",
|
||||||
"react-router-dom": "^6.12.1",
|
"react-router-dom": "^6.14.2",
|
||||||
"react-scripts": "^5.0.1",
|
|
||||||
"react-select": "^5.7.4",
|
"react-select": "^5.7.4",
|
||||||
"react-tabs": "^6.0.1",
|
"react-tabs": "^6.0.2",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3"
|
||||||
"styled-components": "^6.0.4",
|
|
||||||
"web-vitals": "^2.1.4"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build",
|
|
||||||
"test": "react-scripts test",
|
|
||||||
"eject": "react-scripts eject"
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.0",
|
"@types/node": "^20.4.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@types/react": "^18.2.15",
|
||||||
|
"@types/react-dom": "^18.2.7",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.0.3",
|
||||||
|
"autoprefixer": "^10.4.14",
|
||||||
"eslint": "^8.45.0",
|
"eslint": "^8.45.0",
|
||||||
"eslint-config-standard-with-typescript": "^37.0.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-react-refresh": "^0.4.3",
|
||||||
"eslint-plugin-n": "^16.0.1",
|
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
|
||||||
"eslint-plugin-react": "^7.33.0",
|
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
"tailwindcss": "^3.3.2"
|
"postcss": "^8.4.27",
|
||||||
|
"tailwindcss": "^3.3.3",
|
||||||
|
"typescript": "^5.0.2",
|
||||||
|
"vite": "^4.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
rsconcept/frontend/postcss.config.js
Normal file
6
rsconcept/frontend/postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"short_name": "КонцептПортал",
|
|
||||||
"name": "Портал для работы с концептуальными схемами",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.svg",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo.svg",
|
|
||||||
"sizes": "512x512 256x256 64x64 32x32 24x24 16x16"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
|
||||||
Disallow:
|
|
|
@ -1,4 +1,4 @@
|
||||||
import axios, { type AxiosError } from 'axios';
|
import axios, { type AxiosError,AxiosHeaderValue } from 'axios';
|
||||||
|
|
||||||
import PrettyJson from './Common/PrettyJSON';
|
import PrettyJson from './Common/PrettyJSON';
|
||||||
|
|
||||||
|
@ -29,15 +29,29 @@ function DescribeError(error: ErrorInfo) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isHtml = error.response.headers['content-type'].includes('text/html');
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
const isHtml = (() => {
|
||||||
|
if (!error.response) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const header = error.response.headers['content-type'] as AxiosHeaderValue;
|
||||||
|
if (!header) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof header === 'number' || typeof header === 'boolean') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
|
||||||
|
return header.includes('text/html');
|
||||||
|
})();
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col justify-start'>
|
<div className='flex flex-col justify-start'>
|
||||||
<p className='underline'>Ошибка</p>
|
<p className='underline'>Ошибка</p>
|
||||||
<p>{error.message}</p>
|
<p>{error.message}</p>
|
||||||
{error.response.data && (<>
|
{error.response.data && (<>
|
||||||
<p className='mt-2 underline'>Описание</p>
|
<p className='mt-2 underline'>Описание</p>
|
||||||
{ isHtml && <div dangerouslySetInnerHTML={{ __html: error.response.data }} /> }
|
{ isHtml && <div dangerouslySetInnerHTML={{ __html: error.response.data as TrustedHTML }} /> }
|
||||||
{ !isHtml && <PrettyJson data={error.response.data} />}
|
{ !isHtml && <PrettyJson data={error.response.data as object} />}
|
||||||
</>)}
|
</>)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { Tab } from 'react-tabs';
|
||||||
function ConceptTab({ children, className, ...otherProps }: TabProps) {
|
function ConceptTab({ children, className, ...otherProps }: TabProps) {
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Tab
|
||||||
className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab ${className?.toString() ?? ''} whitespace-nowrap`}
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab ${className} whitespace-nowrap`}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
interface LabeledTextProps {
|
interface LabeledTextProps {
|
||||||
id?: string
|
id?: string
|
||||||
label: string
|
label: string
|
||||||
text: any
|
text: string | number
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
interface PrettyJsonProps {
|
interface PrettyJsonProps {
|
||||||
data: any
|
data: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
function PrettyJson({ data }: PrettyJsonProps) {
|
function PrettyJson({ data }: PrettyJsonProps) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ interface TextAreaProps {
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
widthClass?: string
|
widthClass?: string
|
||||||
rows?: number
|
rows?: number
|
||||||
value?: any
|
value?: string | ReadonlyArray<string> | number | undefined;
|
||||||
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
onFocus?: () => void
|
onFocus?: () => void
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
|
||||||
import { ToastContainer, type ToastContainerProps } from 'react-toastify';
|
import { ToastContainer, type ToastContainerProps } from 'react-toastify';
|
||||||
|
|
||||||
import { useConceptTheme } from '../context/ThemeContext';
|
import { useConceptTheme } from '../context/ThemeContext';
|
||||||
|
|
||||||
function ToasterThemed({ theme, ...props }: ToastContainerProps) {
|
interface ToasterThemedProps extends Omit<ToastContainerProps, 'theme'>{}
|
||||||
|
|
||||||
|
function ToasterThemed({ ...props }: ToasterThemedProps) {
|
||||||
const { darkMode } = useConceptTheme();
|
const { darkMode } = useConceptTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -12,4 +16,5 @@ function ToasterThemed({ theme, ...props }: ToastContainerProps) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ToasterThemed;
|
export default ToasterThemed;
|
||||||
|
|
|
@ -2,14 +2,14 @@ import { createContext, useCallback, useContext, useLayoutEffect, useState } fro
|
||||||
|
|
||||||
import { type ErrorInfo } from '../components/BackendError';
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import useLocalStorage from '../hooks/useLocalStorage';
|
import useLocalStorage from '../hooks/useLocalStorage';
|
||||||
import { type BackendCallback, getAuth, postLogin, postLogout, postSignup } from '../utils/backendAPI';
|
import { type DataCallback, getAuth, postLogin, postLogout, postSignup } from '../utils/backendAPI';
|
||||||
import { type ICurrentUser, type IUserSignupData } from '../utils/models';
|
import { ICurrentUser, IUserLoginData, IUserProfile, IUserSignupData } from '../utils/models';
|
||||||
|
|
||||||
interface IAuthContext {
|
interface IAuthContext {
|
||||||
user: ICurrentUser | undefined
|
user: ICurrentUser | undefined
|
||||||
login: (username: string, password: string, callback?: BackendCallback) => void
|
login: (data: IUserLoginData, callback?: DataCallback) => void
|
||||||
logout: (callback?: BackendCallback) => void
|
logout: (callback?: DataCallback) => void
|
||||||
signup: (data: IUserSignupData, callback?: BackendCallback) => void
|
signup: (data: IUserSignupData, callback?: DataCallback<IUserProfile>) => void
|
||||||
loading: boolean
|
loading: boolean
|
||||||
error: ErrorInfo
|
error: ErrorInfo
|
||||||
setError: (error: ErrorInfo) => void
|
setError: (error: ErrorInfo) => void
|
||||||
|
@ -35,69 +35,55 @@ export const AuthState = ({ children }: AuthStateProps) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
const loadCurrentUser = useCallback(
|
const reload = useCallback(
|
||||||
async () => {
|
(callback?: () => void) => {
|
||||||
await getAuth({
|
getAuth({
|
||||||
onError: () => { setUser(undefined); },
|
onError: () => { setUser(undefined); },
|
||||||
onSuccess: response => {
|
onSuccess: currentUser => {
|
||||||
if (response.data.id) {
|
if (currentUser.id) {
|
||||||
setUser(response.data);
|
setUser(currentUser);
|
||||||
} else {
|
} else {
|
||||||
setUser(undefined)
|
setUser(undefined);
|
||||||
}
|
}
|
||||||
|
if (callback) callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [setUser]
|
}, [setUser]
|
||||||
);
|
);
|
||||||
|
|
||||||
function login(uname: string, pw: string, callback?: BackendCallback) {
|
function login(data: IUserLoginData, callback?: DataCallback) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
postLogin({
|
postLogin({
|
||||||
data: { username: uname, password: pw },
|
data: data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading,
|
setLoading: setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => { setError(error); },
|
||||||
onSuccess:
|
onSuccess: newData => reload(() => { if (callback) callback(newData); })
|
||||||
(response) => {
|
});
|
||||||
loadCurrentUser()
|
|
||||||
.then(() => { if (callback) callback(response); })
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
}).catch(console.error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout(callback?: BackendCallback) {
|
function logout(callback?: DataCallback) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
postLogout({
|
postLogout({
|
||||||
showError: true,
|
showError: true,
|
||||||
onSuccess:
|
onSuccess: newData => reload(() => { if (callback) callback(newData); })
|
||||||
(response) => {
|
});
|
||||||
loadCurrentUser()
|
|
||||||
.then(() => { if (callback) callback(response); })
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
}).catch(console.error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function signup(data: IUserSignupData, callback?: BackendCallback) {
|
function signup(data: IUserSignupData, callback?: DataCallback<IUserProfile>) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
postSignup({
|
postSignup({
|
||||||
data,
|
data: data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading,
|
setLoading: setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => { setError(error); },
|
||||||
onSuccess:
|
onSuccess: newData => reload(() => { if (callback) callback(newData); })
|
||||||
(response) => {
|
});
|
||||||
loadCurrentUser()
|
|
||||||
.then(() => { if (callback) callback(response); })
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
}).catch(console.error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
loadCurrentUser().catch(console.error);
|
reload();
|
||||||
}, [loadCurrentUser])
|
}, [reload])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider
|
<AuthContext.Provider
|
||||||
|
|
|
@ -4,11 +4,14 @@ import { toast } from 'react-toastify'
|
||||||
import { type ErrorInfo } from '../components/BackendError'
|
import { type ErrorInfo } from '../components/BackendError'
|
||||||
import { useRSFormDetails } from '../hooks/useRSFormDetails'
|
import { useRSFormDetails } from '../hooks/useRSFormDetails'
|
||||||
import {
|
import {
|
||||||
type BackendCallback, deleteRSForm, getTRSFile,
|
type DataCallback, deleteRSForm, getTRSFile,
|
||||||
patchConstituenta, patchDeleteConstituenta, patchMoveConstituenta, patchRSForm,
|
patchConstituenta, patchDeleteConstituenta, patchMoveConstituenta, patchRSForm,
|
||||||
postClaimRSForm, postNewConstituenta
|
postClaimRSForm, postNewConstituenta
|
||||||
} from '../utils/backendAPI'
|
} from '../utils/backendAPI'
|
||||||
import { type IConstituenta, type IRSForm } from '../utils/models'
|
import {
|
||||||
|
IConstituenta, IConstituentaList, IConstituentaMeta, ICstCreateData,
|
||||||
|
ICstMovetoData, ICstUpdateData, IRSForm, IRSFormMeta, IRSFormUpdateData
|
||||||
|
} from '../utils/models'
|
||||||
import { useAuth } from './AuthContext'
|
import { useAuth } from './AuthContext'
|
||||||
|
|
||||||
interface IRSFormContext {
|
interface IRSFormContext {
|
||||||
|
@ -32,15 +35,15 @@ interface IRSFormContext {
|
||||||
toggleReadonly: () => void
|
toggleReadonly: () => void
|
||||||
toggleTracking: () => void
|
toggleTracking: () => void
|
||||||
|
|
||||||
update: (data: any, callback?: BackendCallback) => void
|
update: (data: IRSFormUpdateData, callback?: DataCallback<IRSFormMeta>) => void
|
||||||
destroy: (callback?: BackendCallback) => void
|
destroy: (callback?: DataCallback) => void
|
||||||
claim: (callback?: BackendCallback) => void
|
claim: (callback?: DataCallback<IRSFormMeta>) => void
|
||||||
download: (callback: BackendCallback) => void
|
download: (callback: DataCallback<Blob>) => void
|
||||||
|
|
||||||
cstUpdate: (cstdID: string, data: any, callback?: BackendCallback) => void
|
cstCreate: (data: ICstCreateData, callback?: DataCallback<IConstituentaMeta>) => void
|
||||||
cstCreate: (data: any, callback?: BackendCallback) => void
|
cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void
|
||||||
cstDelete: (data: any, callback?: BackendCallback) => void
|
cstDelete: (data: IConstituentaList, callback?: () => void) => void
|
||||||
cstMoveTo: (data: any, callback?: BackendCallback) => void
|
cstMoveTo: (data: ICstMovetoData, callback?: () => void) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const RSFormContext = createContext<IRSFormContext | null>(null)
|
const RSFormContext = createContext<IRSFormContext | null>(null)
|
||||||
|
@ -76,7 +79,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
!loading && !isReadonly &&
|
!loading && !isReadonly &&
|
||||||
((isOwned || (isForceAdmin && user?.is_staff)) ?? false)
|
((isOwned || (isForceAdmin && user?.is_staff)) ?? false)
|
||||||
)
|
)
|
||||||
}, [user, isReadonly, isForceAdmin, isOwned, loading])
|
}, [user?.is_staff, isReadonly, isForceAdmin, isOwned, loading])
|
||||||
|
|
||||||
const activeCst = useMemo(
|
const activeCst = useMemo(
|
||||||
() => {
|
() => {
|
||||||
|
@ -94,34 +97,39 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
(data: any, callback?: BackendCallback) => {
|
(data: IRSFormUpdateData, callback?: DataCallback<IRSFormMeta>) => {
|
||||||
|
if (!schema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
patchRSForm(schemaID, {
|
patchRSForm(schemaID, {
|
||||||
data,
|
data: data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSuccess: (response) => {
|
onSuccess: newData => {
|
||||||
reload(setProcessing)
|
setSchema(Object.assign(schema, newData));
|
||||||
.then(() => { if (callback != null) callback(response); })
|
if (callback) callback(newData);
|
||||||
.catch(console.error);
|
|
||||||
}
|
}
|
||||||
}).catch(console.error);
|
});
|
||||||
}, [schemaID, setError, reload])
|
}, [schemaID, setError, setSchema, schema])
|
||||||
|
|
||||||
const destroy = useCallback(
|
const destroy = useCallback(
|
||||||
(callback?: BackendCallback) => {
|
(callback?: DataCallback) => {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
deleteRSForm(schemaID, {
|
deleteRSForm(schemaID, {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSuccess: callback
|
onSuccess: newData => {
|
||||||
}).catch(console.error);
|
setSchema(undefined);
|
||||||
}, [schemaID, setError])
|
if (callback) callback(newData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [schemaID, setError, setSchema])
|
||||||
|
|
||||||
const claim = useCallback(
|
const claim = useCallback(
|
||||||
(callback?: BackendCallback) => {
|
(callback?: DataCallback<IRSFormMeta>) => {
|
||||||
if (!schema || !user) {
|
if (!schema || !user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -130,85 +138,81 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSuccess: (response) => {
|
onSuccess: newData => {
|
||||||
schema.owner = user.id;
|
setSchema(Object.assign(schema, newData));
|
||||||
schema.time_update = response.data.time_update;
|
if (callback) callback(newData);
|
||||||
setSchema(schema);
|
|
||||||
if (callback != null) callback(response);
|
|
||||||
}
|
}
|
||||||
}).catch(console.error);
|
});
|
||||||
}, [schemaID, setError, schema, user, setSchema])
|
}, [schemaID, setError, schema, user, setSchema])
|
||||||
|
|
||||||
const download = useCallback(
|
const download = useCallback(
|
||||||
(callback: BackendCallback) => {
|
(callback: DataCallback<Blob>) => {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
getTRSFile(schemaID, {
|
getTRSFile(schemaID, {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSuccess: callback
|
onSuccess: callback
|
||||||
}).catch(console.error);
|
});
|
||||||
}, [schemaID, setError])
|
}, [schemaID, setError])
|
||||||
|
|
||||||
const cstUpdate = useCallback(
|
|
||||||
(cstID: string, data: any, callback?: BackendCallback) => {
|
|
||||||
setError(undefined)
|
|
||||||
patchConstituenta(cstID, {
|
|
||||||
data,
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: error => { setError(error) },
|
|
||||||
onSuccess: (response) => {
|
|
||||||
reload(setProcessing)
|
|
||||||
.then(() => { if (callback != null) callback(response); })
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
}).catch(console.error);
|
|
||||||
}, [setError])
|
|
||||||
|
|
||||||
const cstCreate = useCallback(
|
const cstCreate = useCallback(
|
||||||
(data: any, callback?: BackendCallback) => {
|
(data: ICstCreateData, callback?: DataCallback<IConstituentaMeta>) => {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
postNewConstituenta(schemaID, {
|
postNewConstituenta(schemaID, {
|
||||||
data,
|
data: data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSuccess: (response) => {
|
onSuccess: newData => {
|
||||||
setSchema(response.data.schema);
|
setSchema(newData.schema);
|
||||||
if (callback != null) callback(response);
|
if (callback) callback(newData.new_cst);
|
||||||
}
|
}
|
||||||
}).catch(console.error);
|
});
|
||||||
}, [schemaID, setError, setSchema]);
|
}, [schemaID, setError, setSchema]);
|
||||||
|
|
||||||
const cstDelete = useCallback(
|
const cstDelete = useCallback(
|
||||||
(data: any, callback?: BackendCallback) => {
|
(data: IConstituentaList, callback?: () => void) => {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
patchDeleteConstituenta(schemaID, {
|
patchDeleteConstituenta(schemaID, {
|
||||||
data,
|
data: data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSuccess: (response) => {
|
onSuccess: newData => {
|
||||||
setSchema(response.data)
|
setSchema(newData);
|
||||||
if (callback != null) callback(response)
|
if (callback) callback();
|
||||||
}
|
}
|
||||||
}).catch(console.error);
|
});
|
||||||
}, [schemaID, setError, setSchema]);
|
}, [schemaID, setError, setSchema]);
|
||||||
|
|
||||||
const cstMoveTo = useCallback(
|
const cstUpdate = useCallback(
|
||||||
(data: any, callback?: BackendCallback) => {
|
(data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
patchMoveConstituenta(schemaID, {
|
patchConstituenta(String(data.id), {
|
||||||
data,
|
data: data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSuccess: (response) => {
|
onSuccess: newData => {
|
||||||
setSchema(response.data);
|
reload(setProcessing, () => { if (callback != null) callback(newData); })
|
||||||
if (callback != null) callback(response);
|
|
||||||
}
|
}
|
||||||
}).catch(console.error);
|
});
|
||||||
|
}, [setError, reload])
|
||||||
|
|
||||||
|
const cstMoveTo = useCallback(
|
||||||
|
(data: ICstMovetoData, callback?: () => void) => {
|
||||||
|
setError(undefined)
|
||||||
|
patchMoveConstituenta(schemaID, {
|
||||||
|
data: data,
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: error => { setError(error) },
|
||||||
|
onSuccess: newData => {
|
||||||
|
setSchema(newData);
|
||||||
|
if (callback) callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
}, [schemaID, setError, setSchema]);
|
}, [schemaID, setError, setSchema]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,8 +5,8 @@ import { type IUserInfo } from '../utils/models';
|
||||||
|
|
||||||
interface IUsersContext {
|
interface IUsersContext {
|
||||||
users: IUserInfo[]
|
users: IUserInfo[]
|
||||||
reload: () => Promise<void>
|
reload: () => void
|
||||||
getUserLabel: (userID?: number) => string
|
getUserLabel: (userID: number | null) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
const UsersContext = createContext<IUsersContext | null>(null)
|
const UsersContext = createContext<IUsersContext | null>(null)
|
||||||
|
@ -27,13 +27,13 @@ interface UsersStateProps {
|
||||||
export const UsersState = ({ children }: UsersStateProps) => {
|
export const UsersState = ({ children }: UsersStateProps) => {
|
||||||
const [users, setUsers] = useState<IUserInfo[]>([])
|
const [users, setUsers] = useState<IUserInfo[]>([])
|
||||||
|
|
||||||
const getUserLabel = (userID?: number) => {
|
const getUserLabel = (userID: number | null) => {
|
||||||
const user = users.find(({ id }) => id === userID)
|
const user = users.find(({ id }) => id === userID)
|
||||||
if (user == null) {
|
if (!user) {
|
||||||
return (userID ? userID.toString() : 'Отсутствует');
|
return (userID ? userID.toString() : 'Отсутствует');
|
||||||
}
|
}
|
||||||
const hasFirstName = user.first_name != null && user.first_name !== '';
|
const hasFirstName = user.first_name !== '';
|
||||||
const hasLastName = user.last_name != null && user.last_name !== '';
|
const hasLastName = user.last_name !== '';
|
||||||
if (hasFirstName || hasLastName) {
|
if (hasFirstName || hasLastName) {
|
||||||
if (!hasLastName) {
|
if (!hasLastName) {
|
||||||
return user.first_name;
|
return user.first_name;
|
||||||
|
@ -47,17 +47,17 @@ export const UsersState = ({ children }: UsersStateProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const reload = useCallback(
|
const reload = useCallback(
|
||||||
async () => {
|
() => {
|
||||||
await getActiveUsers({
|
getActiveUsers({
|
||||||
showError: true,
|
showError: true,
|
||||||
onError: () => { setUsers([]); },
|
onError: () => { setUsers([]); },
|
||||||
onSuccess: response => { setUsers(response.data); }
|
onSuccess: newData => { setUsers(newData); }
|
||||||
});
|
});
|
||||||
}, [setUsers]
|
}, [setUsers]
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload().catch(console.error);
|
reload();
|
||||||
}, [reload])
|
}, [reload])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
import { type AxiosResponse } from 'axios';
|
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
import { type ErrorInfo } from '../components/BackendError';
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import { postCheckExpression } from '../utils/backendAPI';
|
import { DataCallback, postCheckExpression } from '../utils/backendAPI';
|
||||||
import { type IRSForm } from '../utils/models';
|
import { ExpressionParse, type IRSForm } from '../utils/models';
|
||||||
|
|
||||||
function useCheckExpression({ schema }: { schema?: IRSForm }) {
|
function useCheckExpression({ schema }: { schema?: IRSForm }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
const [parseData, setParseData] = useState<any | undefined>(undefined);
|
const [parseData, setParseData] = useState<ExpressionParse | undefined>(undefined);
|
||||||
|
|
||||||
const resetParse = useCallback(() => { setParseData(undefined); }, []);
|
const resetParse = useCallback(() => { setParseData(undefined); }, []);
|
||||||
|
|
||||||
async function checkExpression(expression: string, onSuccess?: (response: AxiosResponse) => void) {
|
function checkExpression(expression: string, onSuccess?: DataCallback<ExpressionParse>) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setParseData(undefined);
|
setParseData(undefined);
|
||||||
await postCheckExpression(String(schema?.id), {
|
postCheckExpression(String(schema?.id), {
|
||||||
data: { expression },
|
data: { expression: expression },
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading,
|
setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => { setError(error); },
|
||||||
onSuccess: (response) => {
|
onSuccess: newData => {
|
||||||
setParseData(response.data);
|
setParseData(newData);
|
||||||
if (onSuccess) onSuccess(response);
|
if (onSuccess) onSuccess(newData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,6 @@ function useClickedOutside({ ref, callback }: { ref: React.RefObject<HTMLElement
|
||||||
document.removeEventListener('mouseup', handleClickOutside);
|
document.removeEventListener('mouseup', handleClickOutside);
|
||||||
};
|
};
|
||||||
}, [ref, callback]);
|
}, [ref, callback]);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default useClickedOutside;
|
export default useClickedOutside;
|
||||||
|
|
|
@ -15,6 +15,6 @@ function useDropdown() {
|
||||||
toggle: () => { setIsActive(!isActive); },
|
toggle: () => { setIsActive(!isActive); },
|
||||||
hide: () => { setIsActive(false); }
|
hide: () => { setIsActive(false); }
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export default useDropdown;
|
export default useDropdown;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
function getStorageValue<ValueType>(key: string, defaultValue: ValueType) {
|
function getStorageValue<ValueType>(key: string, defaultValue: ValueType) {
|
||||||
const saved = localStorage.getItem(key);
|
const saved = localStorage.getItem(key);
|
||||||
const initial = saved ? JSON.parse(saved) : undefined;
|
const initial = saved ? JSON.parse(saved) as ValueType : undefined;
|
||||||
return initial || defaultValue;
|
return initial || defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,21 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { type ErrorInfo } from '../components/BackendError';
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import { postNewRSForm } from '../utils/backendAPI';
|
import { DataCallback, postNewRSForm } from '../utils/backendAPI';
|
||||||
|
import { IRSFormCreateData, IRSFormMeta } from '../utils/models';
|
||||||
|
|
||||||
function useNewRSForm() {
|
function useNewRSForm() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
async function createSchema({ data, file, onSuccess }: {
|
function createSchema(data: IRSFormCreateData, onSuccess: DataCallback<IRSFormMeta>) {
|
||||||
data: any
|
|
||||||
file?: File
|
|
||||||
onSuccess: (newID: string) => void
|
|
||||||
}) {
|
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
if (file) {
|
postNewRSForm({
|
||||||
data.file = file;
|
data: data,
|
||||||
data.fileName = file.name;
|
|
||||||
}
|
|
||||||
await postNewRSForm({
|
|
||||||
data,
|
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading,
|
setLoading: setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => { setError(error); },
|
||||||
onSuccess: response => { onSuccess(response.data.id); }
|
onSuccess: onSuccess
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,40 +2,43 @@ import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { type ErrorInfo } from '../components/BackendError';
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import { getRSFormDetails } from '../utils/backendAPI';
|
import { getRSFormDetails } from '../utils/backendAPI';
|
||||||
import { CalculateStats, type IRSForm } from '../utils/models'
|
import { IRSForm, IRSFormData,LoadRSFormData } from '../utils/models'
|
||||||
|
|
||||||
export function useRSFormDetails({ target }: { target?: string }) {
|
export function useRSFormDetails({ target }: { target?: string }) {
|
||||||
const [schema, setInnerSchema] = useState<IRSForm | undefined>(undefined);
|
const [schema, setInnerSchema] = useState<IRSForm | undefined>(undefined);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
function setSchema(schema?: IRSForm) {
|
function setSchema(data?: IRSFormData) {
|
||||||
if (schema) CalculateStats(schema);
|
if (!data) {
|
||||||
|
setInnerSchema(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const schema = LoadRSFormData(data);
|
||||||
setInnerSchema(schema);
|
setInnerSchema(schema);
|
||||||
console.log('Loaded schema: ', schema);
|
console.log('Loaded schema: ', schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = useCallback(
|
const reload = useCallback(
|
||||||
async (setCustomLoading?: typeof setLoading) => {
|
(setCustomLoading?: typeof setLoading, callback?: () => void) => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await getRSFormDetails(target, {
|
getRSFormDetails(target, {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setCustomLoading ?? setLoading,
|
setLoading: setCustomLoading ?? setLoading,
|
||||||
onError: error => { setInnerSchema(undefined); setError(error); },
|
onError: error => { setInnerSchema(undefined); setError(error); },
|
||||||
onSuccess: (response) => { setSchema(response.data); }
|
onSuccess: schema => {
|
||||||
|
setSchema(schema);
|
||||||
|
if (callback) callback();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [target]);
|
}, [target]);
|
||||||
|
|
||||||
async function reload(setCustomLoading?: typeof setLoading) {
|
|
||||||
await fetchData(setCustomLoading);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData().catch((error) => { setError(error); });
|
reload();
|
||||||
}, [fetchData])
|
}, [reload])
|
||||||
|
|
||||||
return { schema, setSchema, reload, error, setError, loading };
|
return { schema, setSchema, reload, error, setError, loading };
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
import { type ErrorInfo } from '../components/BackendError';
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import { getRSForms } from '../utils/backendAPI';
|
import { getRSForms } from '../utils/backendAPI';
|
||||||
import { type IRSForm } from '../utils/models'
|
import { IRSFormMeta } from '../utils/models'
|
||||||
|
|
||||||
export enum FilterType {
|
export enum FilterType {
|
||||||
PERSONAL = 'personal',
|
PERSONAL = 'personal',
|
||||||
|
@ -11,20 +11,20 @@ export enum FilterType {
|
||||||
|
|
||||||
export interface RSFormsFilter {
|
export interface RSFormsFilter {
|
||||||
type: FilterType
|
type: FilterType
|
||||||
data?: any
|
data?: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRSForms() {
|
export function useRSForms() {
|
||||||
const [rsforms, setRSForms] = useState<IRSForm[]>([]);
|
const [rsforms, setRSForms] = useState<IRSFormMeta[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
const loadList = useCallback(async (filter: RSFormsFilter) => {
|
const loadList = useCallback((filter: RSFormsFilter) => {
|
||||||
await getRSForms(filter, {
|
getRSForms(filter, {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading,
|
setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => { setError(error); },
|
||||||
onSuccess: response => { setRSForms(response.data); }
|
onSuccess: newData => { setRSForms(newData); }
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
@ -10,21 +10,20 @@ export function useUserProfile() {
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
const fetchUser = useCallback(
|
const fetchUser = useCallback(
|
||||||
async () => {
|
() => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setUser(undefined);
|
setUser(undefined);
|
||||||
|
getProfile({
|
||||||
await getProfile({
|
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading,
|
setLoading: setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => { setError(error); },
|
||||||
onSuccess: response => { setUser(response.data); }
|
onSuccess: newData => { setUser(newData); }
|
||||||
});
|
});
|
||||||
}, [setUser]
|
}, [setUser]
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUser().catch((error) => { setError(error); });
|
fetchUser();
|
||||||
}, [fetchUser])
|
}, [fetchUser])
|
||||||
|
|
||||||
return { user, fetchUser, error, loading };
|
return { user, fetchUser, error, loading };
|
||||||
|
|
|
@ -1,28 +1,21 @@
|
||||||
'use client';
|
import './index.css'
|
||||||
|
|
||||||
import './index.css';
|
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import React from 'react';
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client'
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import App from './App';
|
import App from './App.tsx'
|
||||||
import ErrorFallback from './components/ErrorFallback';
|
import ErrorFallback from './components/ErrorFallback.tsx';
|
||||||
import { AuthState } from './context/AuthContext';
|
import { AuthState } from './context/AuthContext.tsx';
|
||||||
import { ThemeState } from './context/ThemeContext';
|
import { ThemeState } from './context/ThemeContext.tsx';
|
||||||
import { UsersState } from './context/UsersContext';
|
import { UsersState } from './context/UsersContext.tsx';
|
||||||
|
|
||||||
axios.defaults.withCredentials = true
|
axios.defaults.withCredentials = true;
|
||||||
axios.defaults.xsrfCookieName = 'csrftoken'
|
axios.defaults.xsrfCookieName = 'csrftoken';
|
||||||
axios.defaults.xsrfHeaderName = 'x-csrftoken'
|
axios.defaults.xsrfHeaderName = 'x-csrftoken';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
|
||||||
document.getElementById('root') as HTMLElement
|
|
||||||
);
|
|
||||||
|
|
||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
console.log('Resetting state after error fallback')
|
console.log('Resetting state after error fallback')
|
||||||
|
@ -33,7 +26,7 @@ const logError = (error: Error, info: { componentStack: string }) => {
|
||||||
console.log('Component stack: ' + info.componentStack)
|
console.log('Component stack: ' + info.componentStack)
|
||||||
};
|
};
|
||||||
|
|
||||||
root.render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
|
@ -52,5 +45,5 @@ root.render(
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</React.StrictMode>
|
</React.StrictMode>,
|
||||||
);
|
)
|
|
@ -8,6 +8,7 @@ import TextInput from '../components/Common/TextInput';
|
||||||
import TextURL from '../components/Common/TextURL';
|
import TextURL from '../components/Common/TextURL';
|
||||||
import InfoMessage from '../components/InfoMessage';
|
import InfoMessage from '../components/InfoMessage';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import { IUserLoginData } from '../utils/models';
|
||||||
|
|
||||||
function LoginPage() {
|
function LoginPage() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
|
@ -30,7 +31,11 @@ function LoginPage() {
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!loading) {
|
if (!loading) {
|
||||||
login(username, password, () => { navigate('/rsforms?filter=personal'); });
|
const data: IUserLoginData = {
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
login(data, () => { navigate('/rsforms?filter=personal'); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import TextArea from '../components/Common/TextArea';
|
||||||
import TextInput from '../components/Common/TextInput';
|
import TextInput from '../components/Common/TextInput';
|
||||||
import RequireAuth from '../components/RequireAuth';
|
import RequireAuth from '../components/RequireAuth';
|
||||||
import useNewRSForm from '../hooks/useNewRSForm';
|
import useNewRSForm from '../hooks/useNewRSForm';
|
||||||
import { type IRSFormCreateData } from '../utils/models';
|
import { IRSFormCreateData, IRSFormMeta } from '../utils/models';
|
||||||
|
|
||||||
function RSFormCreatePage() {
|
function RSFormCreatePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -30,9 +30,9 @@ function RSFormCreatePage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSuccess = (newID: string) => {
|
const onSuccess = (newSchema: IRSFormMeta) => {
|
||||||
toast.success('Схема успешно создана');
|
toast.success('Схема успешно создана');
|
||||||
navigate(`/rsforms/${newID}`);
|
navigate(`/rsforms/${newSchema.id}`);
|
||||||
}
|
}
|
||||||
const { createSchema, error, setError, loading } = useNewRSForm()
|
const { createSchema, error, setError, loading } = useNewRSForm()
|
||||||
|
|
||||||
|
@ -46,16 +46,14 @@ function RSFormCreatePage() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data: IRSFormCreateData = {
|
const data: IRSFormCreateData = {
|
||||||
title,
|
title: title,
|
||||||
alias,
|
alias: alias,
|
||||||
comment,
|
comment: comment,
|
||||||
is_common: common
|
is_common: common,
|
||||||
|
file: file,
|
||||||
|
fileName: file?.name
|
||||||
};
|
};
|
||||||
void createSchema({
|
void createSchema(data, onSuccess);
|
||||||
data,
|
|
||||||
file,
|
|
||||||
onSuccess
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import { type AxiosResponse } from 'axios';
|
|
||||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../components/Common/SubmitButton';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../components/Common/TextArea';
|
||||||
import { DumpBinIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
import { DumpBinIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { type CstType, EditMode, type INewCstData } from '../../utils/models';
|
import { type CstType, EditMode, type ICstCreateData, ICstUpdateData } from '../../utils/models';
|
||||||
import { createAliasFor, getCstTypeLabel } from '../../utils/staticUI';
|
import { createAliasFor, getCstTypeLabel } from '../../utils/staticUI';
|
||||||
import ConstituentsSideList from './ConstituentsSideList';
|
import ConstituentsSideList from './ConstituentsSideList';
|
||||||
import CreateCstModal from './CreateCstModal';
|
import CreateCstModal from './CreateCstModal';
|
||||||
import ExpressionEditor from './ExpressionEditor';
|
import ExpressionEditor from './ExpressionEditor';
|
||||||
|
import { RSFormTabsList } from './RSFormTabs';
|
||||||
|
|
||||||
function ConstituentEditor() {
|
function ConstituentEditor() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
activeCst, activeID, schema, setActiveID, processing, isEditable,
|
activeCst, activeID, schema, setActiveID, processing, isEditable,
|
||||||
cstDelete, cstUpdate, cstCreate
|
cstDelete, cstUpdate, cstCreate
|
||||||
|
@ -33,10 +35,8 @@ function ConstituentEditor() {
|
||||||
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
|
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (schema?.items && schema?.items.length > 0) {
|
if (schema && schema?.items.length > 0) {
|
||||||
// TODO: figure out why schema.items could be undef?
|
setActiveID((prev) => (prev ?? schema.items[0].id ?? undefined));
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
setActiveID((prev) => (prev ?? schema?.items![0].id ?? undefined));
|
|
||||||
}
|
}
|
||||||
}, [schema, setActiveID]);
|
}, [schema, setActiveID]);
|
||||||
|
|
||||||
|
@ -46,12 +46,14 @@ function ConstituentEditor() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsModified(
|
setIsModified(
|
||||||
activeCst.term?.raw !== term ||
|
activeCst.term.raw !== term ||
|
||||||
activeCst.definition?.text?.raw !== textDefinition ||
|
activeCst.definition.text.raw !== textDefinition ||
|
||||||
activeCst.convention !== convention ||
|
activeCst.convention !== convention ||
|
||||||
activeCst.definition?.formal !== expression
|
activeCst.definition.formal !== expression
|
||||||
);
|
);
|
||||||
}, [activeCst, term, textDefinition, expression, convention]);
|
}, [activeCst, activeCst?.term, activeCst?.definition.formal,
|
||||||
|
activeCst?.definition.text.raw, activeCst?.convention,
|
||||||
|
term, textDefinition, expression, convention]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (activeCst) {
|
if (activeCst) {
|
||||||
|
@ -61,25 +63,25 @@ function ConstituentEditor() {
|
||||||
setTerm(activeCst.term?.raw ?? '');
|
setTerm(activeCst.term?.raw ?? '');
|
||||||
setTextDefinition(activeCst.definition?.text?.raw ?? '');
|
setTextDefinition(activeCst.definition?.text?.raw ?? '');
|
||||||
setExpression(activeCst.definition?.formal ?? '');
|
setExpression(activeCst.definition?.formal ?? '');
|
||||||
setTypification(activeCst?.parse?.typification ?? 'N/A');
|
setTypification(activeCst?.parse?.typification || 'N/A');
|
||||||
}
|
}
|
||||||
}, [activeCst]);
|
}, [activeCst]);
|
||||||
|
|
||||||
const handleSubmit =
|
const handleSubmit =
|
||||||
(event: React.FormEvent<HTMLFormElement>) => {
|
(event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!processing) {
|
if (!activeID || processing) {
|
||||||
const data = {
|
return;
|
||||||
|
}
|
||||||
|
const data: ICstUpdateData = {
|
||||||
|
id: activeID,
|
||||||
alias: alias,
|
alias: alias,
|
||||||
convention: convention,
|
convention: convention,
|
||||||
definition_formal: expression,
|
definition_formal: expression,
|
||||||
definition_raw: textDefinition,
|
definition_raw: textDefinition,
|
||||||
term_raw: term
|
term_raw: term
|
||||||
};
|
};
|
||||||
cstUpdate(String(activeID), data, () => {
|
cstUpdate(data, () => { toast.success('Изменения сохранены'); });
|
||||||
toast.success('Изменения сохранены');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
|
@ -98,25 +100,24 @@ function ConstituentEditor() {
|
||||||
}, [activeID, schema, setActiveID, cstDelete]);
|
}, [activeID, schema, setActiveID, cstDelete]);
|
||||||
|
|
||||||
const handleAddNew = useCallback(
|
const handleAddNew = useCallback(
|
||||||
(csttype?: CstType) => {
|
(type?: CstType) => {
|
||||||
if (!activeID || !schema?.items) {
|
if (!activeID || !schema?.items) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!csttype) {
|
if (!type) {
|
||||||
setShowCstModal(true);
|
setShowCstModal(true);
|
||||||
} else {
|
} else {
|
||||||
const data: INewCstData = {
|
const data: ICstCreateData = {
|
||||||
csttype: csttype,
|
cst_type: type,
|
||||||
alias: createAliasFor(csttype, schema),
|
alias: createAliasFor(type, schema),
|
||||||
insert_after: activeID
|
insert_after: activeID
|
||||||
}
|
}
|
||||||
cstCreate(data,
|
cstCreate(data, newCst => {
|
||||||
(response: AxiosResponse) => {
|
navigate(`/rsforms/${schema.id}?tab=${RSFormTabsList.CST_EDIT}&active=${newCst.id}`);
|
||||||
setActiveID(response.data.new_cst.id);
|
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
||||||
toast.success(`Конституента добавлена: ${response.data.new_cst.alias as string}`);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [activeID, schema, cstCreate, setActiveID]);
|
}, [activeID, schema, cstCreate, navigate]);
|
||||||
|
|
||||||
const handleRename = useCallback(() => {
|
const handleRename = useCallback(() => {
|
||||||
toast.info('Переименование в разработке');
|
toast.info('Переименование в разработке');
|
||||||
|
@ -202,7 +203,7 @@ function ConstituentEditor() {
|
||||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||||
value={expression}
|
value={expression}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
isActive={editMode === 'rslang'}
|
isActive={editMode === EditMode.RSLANG}
|
||||||
toggleEditMode={() => { setEditMode(EditMode.RSLANG); }}
|
toggleEditMode={() => { setEditMode(EditMode.RSLANG); }}
|
||||||
onChange={event => { setExpression(event.target.value); }}
|
onChange={event => { setExpression(event.target.value); }}
|
||||||
setValue={setExpression}
|
setValue={setExpression}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import DataTableThemed from '../../components/Common/DataTableThemed';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
import { CstType, type IConstituenta, matchConstituenta } from '../../utils/models';
|
import { CstType, type IConstituenta, matchConstituenta } from '../../utils/models';
|
||||||
import { extractGlobals } from '../../utils/staticUI';
|
import { extractGlobals, getMockConstituenta } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface ConstituentsSideListProps {
|
interface ConstituentsSideListProps {
|
||||||
expression: string
|
expression: string
|
||||||
|
@ -20,19 +20,16 @@ function ConstituentsSideList({ expression }: ConstituentsSideListProps) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!schema?.items) {
|
if (!schema?.items) {
|
||||||
setFilteredData([]);
|
setFilteredData([]);
|
||||||
} else if (onlyExpression) {
|
return;
|
||||||
|
}
|
||||||
|
if (onlyExpression) {
|
||||||
const aliases = extractGlobals(expression);
|
const aliases = extractGlobals(expression);
|
||||||
const filtered = schema?.items.filter((cst) => aliases.has(cst.alias));
|
const filtered = schema?.items.filter((cst) => aliases.has(cst.alias));
|
||||||
const names = filtered.map(cst => cst.alias)
|
const names = filtered.map(cst => cst.alias)
|
||||||
const diff = Array.from(aliases).filter(name => !names.includes(name));
|
const diff = Array.from(aliases).filter(name => !names.includes(name));
|
||||||
if (diff.length > 0) {
|
if (diff.length > 0) {
|
||||||
diff.forEach(
|
diff.forEach(
|
||||||
(alias, i) => filtered.push({
|
(alias, index) => filtered.push(getMockConstituenta(-index, alias, CstType.BASE, 'Конституента отсутствует')));
|
||||||
id: -i,
|
|
||||||
alias: alias,
|
|
||||||
convention: 'Конституента отсутствует',
|
|
||||||
cstType: CstType.BASE
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
setFilteredData(filtered);
|
setFilteredData(filtered);
|
||||||
} else if (!filterText) {
|
} else if (!filterText) {
|
||||||
|
@ -50,7 +47,7 @@ function ConstituentsSideList({ expression }: ConstituentsSideListProps) {
|
||||||
}, [setActiveID]);
|
}, [setActiveID]);
|
||||||
|
|
||||||
const handleDoubleClick = useCallback(
|
const handleDoubleClick = useCallback(
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
(cst: IConstituenta) => {
|
||||||
if (cst.id > 0) setActiveID(cst.id);
|
if (cst.id > 0) setActiveID(cst.id);
|
||||||
}, [setActiveID]);
|
}, [setActiveID]);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { type AxiosResponse } from 'axios';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
@ -8,8 +7,8 @@ import Divider from '../../components/Common/Divider';
|
||||||
import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, SmallPlusIcon } from '../../components/Icons';
|
import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import { CstType, type IConstituenta, type INewCstData, inferStatus, ParsingStatus, ValueClass } from '../../utils/models'
|
import { CstType, type IConstituenta, type ICstCreateData, ICstMovetoData,inferStatus, ParsingStatus, ValueClass } from '../../utils/models'
|
||||||
import { createAliasFor, getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
|
import { createAliasFor, getCstTypePrefix, getCstTypeShortcut, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
|
||||||
import CreateCstModal from './CreateCstModal';
|
import CreateCstModal from './CreateCstModal';
|
||||||
|
|
||||||
interface ConstituentsTableProps {
|
interface ConstituentsTableProps {
|
||||||
|
@ -51,8 +50,8 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
||||||
const data = {
|
const data = {
|
||||||
items: selected.map(id => { return { id }; })
|
items: selected.map(id => { return { id }; })
|
||||||
}
|
}
|
||||||
const deletedNames = selected.map(id => schema.items?.find((cst) => cst.id === id)?.alias);
|
const deletedNames = selected.map(id => schema.items?.find((cst) => cst.id === id)?.alias).join(', ');
|
||||||
cstDelete(data, () => toast.success(`Конституенты удалены: ${deletedNames.toString()}`));
|
cstDelete(data, () => toast.success(`Конституенты удалены: ${deletedNames}`));
|
||||||
}, [selected, schema?.items, cstDelete]);
|
}, [selected, schema?.items, cstDelete]);
|
||||||
|
|
||||||
// Move selected cst up
|
// Move selected cst up
|
||||||
|
@ -69,10 +68,10 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
||||||
}
|
}
|
||||||
return Math.min(prev, index);
|
return Math.min(prev, index);
|
||||||
}, -1);
|
}, -1);
|
||||||
const insertIndex = Math.max(0, currentIndex - 1) + 1
|
const target = Math.max(0, currentIndex - 1) + 1
|
||||||
const data = {
|
const data = {
|
||||||
items: selected.map(id => { return { id }; }),
|
items: selected.map(id => { return { id }; }),
|
||||||
move_to: insertIndex
|
move_to: target
|
||||||
}
|
}
|
||||||
cstMoveTo(data);
|
cstMoveTo(data);
|
||||||
}, [selected, schema?.items, cstMoveTo]);
|
}, [selected, schema?.items, cstMoveTo]);
|
||||||
|
@ -95,10 +94,10 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
||||||
return Math.max(prev, index);
|
return Math.max(prev, index);
|
||||||
}
|
}
|
||||||
}, -1);
|
}, -1);
|
||||||
const insertIndex = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
||||||
const data = {
|
const data: ICstMovetoData = {
|
||||||
items: selected.map(id => { return { id }; }),
|
items: selected.map(id => { return { id }; }),
|
||||||
move_to: insertIndex
|
move_to: target
|
||||||
}
|
}
|
||||||
cstMoveTo(data);
|
cstMoveTo(data);
|
||||||
}, [selected, schema?.items, cstMoveTo]);
|
}, [selected, schema?.items, cstMoveTo]);
|
||||||
|
@ -109,38 +108,56 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Add new constituent
|
// Add new constituent
|
||||||
const handleAddNew = useCallback((csttype?: CstType) => {
|
const handleAddNew = useCallback((type?: CstType) => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!csttype) {
|
if (!type) {
|
||||||
setShowCstModal(true);
|
setShowCstModal(true);
|
||||||
} else {
|
} else {
|
||||||
const data: INewCstData = {
|
const selectedPosition = selected.reduce((prev, cstID) => {
|
||||||
csttype,
|
const position = schema.items.findIndex(cst => cst.id === cstID);
|
||||||
alias: createAliasFor(csttype, schema)
|
return Math.max(position, prev);
|
||||||
|
}, -1) + 1;
|
||||||
|
const data: ICstCreateData = {
|
||||||
|
cst_type: type,
|
||||||
|
alias: createAliasFor(type, schema),
|
||||||
|
insert_after: selectedPosition > 0 ? selectedPosition : null
|
||||||
}
|
}
|
||||||
if (selected.length > 0) {
|
cstCreate(data, new_cst => toast.success(`Добавлена конституента ${new_cst.alias}`));
|
||||||
data.insert_after = selected[selected.length - 1]
|
|
||||||
}
|
|
||||||
cstCreate(data, (response: AxiosResponse) =>
|
|
||||||
toast.success(`Добавлена конституента ${response.data.new_cst.alias as string}`));
|
|
||||||
}
|
}
|
||||||
}, [schema, selected, cstCreate]);
|
}, [schema, selected, cstCreate]);
|
||||||
|
|
||||||
// Implement hotkeys for working with constituents table
|
// Implement hotkeys for working with constituents table
|
||||||
const handleTableKey = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {
|
function handleTableKey(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||||
if (!event.altKey) {
|
if (!event.altKey || !isEditable || event.shiftKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isEditable || selected.length === 0) {
|
if (processAltKey(event.key)) {
|
||||||
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (event.key) {
|
|
||||||
case 'ArrowUp': handleMoveUp(); return;
|
|
||||||
case 'ArrowDown': handleMoveDown();
|
|
||||||
}
|
}
|
||||||
}, [isEditable, selected, handleMoveUp, handleMoveDown]);
|
|
||||||
|
function processAltKey(key: string): boolean {
|
||||||
|
if (selected.length > 0) {
|
||||||
|
switch (key) {
|
||||||
|
case 'ArrowUp': handleMoveUp(); return true;
|
||||||
|
case 'ArrowDown': handleMoveDown(); return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (key) {
|
||||||
|
case '1': handleAddNew(CstType.BASE); return true;
|
||||||
|
case '2': handleAddNew(CstType.STRUCTURED); return true;
|
||||||
|
case '3': handleAddNew(CstType.TERM); return true;
|
||||||
|
case '4': handleAddNew(CstType.AXIOM); return true;
|
||||||
|
case 'q': handleAddNew(CstType.FUNCTION); return true;
|
||||||
|
case 'w': handleAddNew(CstType.PREDICATE); return true;
|
||||||
|
case '5': handleAddNew(CstType.CONSTANT); return true;
|
||||||
|
case '6': handleAddNew(CstType.THEOREM); return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const columns = useMemo(() =>
|
const columns = useMemo(() =>
|
||||||
[
|
[
|
||||||
|
@ -311,7 +328,7 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
||||||
const type = typeStr as CstType;
|
const type = typeStr as CstType;
|
||||||
return <Button key={type}
|
return <Button key={type}
|
||||||
text={`${getCstTypePrefix(type)}`}
|
text={`${getCstTypePrefix(type)}`}
|
||||||
tooltip={getCstTypeLabel(type)}
|
tooltip={getCstTypeShortcut(type)}
|
||||||
dense
|
dense
|
||||||
onClick={() => { handleAddNew(type); }}
|
onClick={() => { handleAddNew(type); }}
|
||||||
/>;
|
/>;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { type AxiosResponse } from 'axios';
|
|
||||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
@ -7,7 +6,8 @@ import Label from '../../components/Common/Label';
|
||||||
import { Loader } from '../../components/Common/Loader';
|
import { Loader } from '../../components/Common/Loader';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||||
import { CstType, TokenID } from '../../utils/models';
|
import { TokenID } from '../../utils/enums';
|
||||||
|
import { CstType } from '../../utils/models';
|
||||||
import ParsingResult from './ParsingResult';
|
import ParsingResult from './ParsingResult';
|
||||||
import RSLocalButton from './RSLocalButton';
|
import RSLocalButton from './RSLocalButton';
|
||||||
import RSTokenButton from './RSTokenButton';
|
import RSTokenButton from './RSTokenButton';
|
||||||
|
@ -47,12 +47,16 @@ function ExpressionEditor({
|
||||||
}
|
}
|
||||||
const prefix = activeCst?.alias + (activeCst?.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
const prefix = activeCst?.alias + (activeCst?.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
||||||
const expression = prefix + value;
|
const expression = prefix + value;
|
||||||
checkExpression(expression, (response: AxiosResponse) => {
|
checkExpression(expression, parse => {
|
||||||
// TODO: update cursor position
|
// TODO: update cursor position
|
||||||
|
if (!parse.parseResult && parse.errors.length > 0) {
|
||||||
|
const errorPosition = parse.errors[0].position - prefix.length
|
||||||
|
expressionCtrl.current!.selectionStart = errorPosition;
|
||||||
|
expressionCtrl.current!.selectionEnd = errorPosition;
|
||||||
|
}
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
setTypification(response.data.typification);
|
setTypification(parse.typification);
|
||||||
toast.success('проверка завершена');
|
});
|
||||||
}).catch(console.error);
|
|
||||||
}, [value, checkExpression, activeCst, setTypification]);
|
}, [value, checkExpression, activeCst, setTypification]);
|
||||||
|
|
||||||
const handleEdit = useCallback((id: TokenID, key?: string) => {
|
const handleEdit = useCallback((id: TokenID, key?: string) => {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import PrettyJson from '../../components/Common/PrettyJSON';
|
import PrettyJson from '../../components/Common/PrettyJSON';
|
||||||
|
import { ExpressionParse } from '../../utils/models';
|
||||||
|
|
||||||
interface ParsingResultProps {
|
interface ParsingResultProps {
|
||||||
data?: any
|
data?: ExpressionParse
|
||||||
}
|
}
|
||||||
|
|
||||||
function ParsingResult({ data }: ParsingResultProps) {
|
function ParsingResult({ data }: ParsingResultProps) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { CrownIcon, DownloadIcon, DumpBinIcon, SaveIcon, ShareIcon } from '../..
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useUsers } from '../../context/UsersContext';
|
import { useUsers } from '../../context/UsersContext';
|
||||||
|
import { IRSFormCreateData } from '../../utils/models';
|
||||||
import { claimOwnershipProc, deleteRSFormProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
import { claimOwnershipProc, deleteRSFormProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
||||||
|
|
||||||
function RSFormCard() {
|
function RSFormCard() {
|
||||||
|
@ -42,7 +43,8 @@ function RSFormCard() {
|
||||||
schema.comment !== comment ||
|
schema.comment !== comment ||
|
||||||
schema.is_common !== common
|
schema.is_common !== common
|
||||||
);
|
);
|
||||||
}, [schema, title, alias, comment, common]);
|
}, [schema, schema?.title, schema?.alias, schema?.comment, schema?.is_common,
|
||||||
|
title, alias, comment, common]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (schema) {
|
if (schema) {
|
||||||
|
@ -55,13 +57,12 @@ function RSFormCard() {
|
||||||
|
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const data = {
|
const data: IRSFormCreateData = {
|
||||||
title,
|
title: title,
|
||||||
alias,
|
alias: alias,
|
||||||
comment,
|
comment: comment,
|
||||||
is_common: common
|
is_common: common
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
update(data, () => toast.success('Изменения сохранены'));
|
update(data, () => toast.success('Изменения сохранены'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -70,7 +71,6 @@ function RSFormCard() {
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
const fileName = (schema?.alias ?? 'Schema') + '.trs';
|
const fileName = (schema?.alias ?? 'Schema') + '.trs';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
downloadRSFormProc(download, fileName);
|
downloadRSFormProc(download, fileName);
|
||||||
}, [download, schema?.alias]);
|
}, [download, schema?.alias]);
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ function RSFormCard() {
|
||||||
<div className='flex justify-start mt-2'>
|
<div className='flex justify-start mt-2'>
|
||||||
<label className='font-semibold'>Владелец:</label>
|
<label className='font-semibold'>Владелец:</label>
|
||||||
<span className='min-w-[200px] ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap'>
|
<span className='min-w-[200px] ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap'>
|
||||||
{getUserLabel(schema?.owner)}
|
{getUserLabel(schema?.owner ?? null)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-start mt-2'>
|
<div className='flex justify-start mt-2'>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { TokenID } from '../../utils/models'
|
import { TokenID } from '../../utils/enums';
|
||||||
|
|
||||||
interface RSLocalButtonProps {
|
interface RSLocalButtonProps {
|
||||||
text: string
|
text: string
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { type TokenID } from '../../utils/models'
|
import { TokenID } from '../../utils/enums';
|
||||||
import { getRSButtonData } from '../../utils/staticUI'
|
import { getRSButtonData } from '../../utils/staticUI'
|
||||||
|
|
||||||
interface RSTokenButtonProps {
|
interface RSTokenButtonProps {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { ExpressionStatus, type IConstituenta, inferStatus, ParsingStatus } from '../../utils/models';
|
import { ExpressionParse,ExpressionStatus, type IConstituenta, inferStatus, ParsingStatus } from '../../utils/models';
|
||||||
import { getStatusInfo } from '../../utils/staticUI';
|
import { getStatusInfo } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface StatusBarProps {
|
interface StatusBarProps {
|
||||||
isModified?: boolean
|
isModified?: boolean
|
||||||
parseData?: any
|
parseData?: ExpressionParse
|
||||||
constituenta?: IConstituenta
|
constituenta?: IConstituenta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Formatted text editing helpers
|
// Formatted text editing helpers
|
||||||
|
|
||||||
import { TokenID } from '../../utils/models'
|
import { TokenID } from '../../utils/enums';
|
||||||
|
|
||||||
export function getSymbolSubstitute(input: string): string | undefined {
|
export function getSymbolSubstitute(input: string): string | undefined {
|
||||||
switch (input) {
|
switch (input) {
|
||||||
|
@ -125,7 +125,7 @@ export class TextWrapper implements IManagedText {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case TokenID.BOOLEAN: {
|
case TokenID.BOOLEAN: {
|
||||||
if (this.selEnd !== this.selStart && this.value.at(this.selStart) === 'ℬ') {
|
if (this.selEnd !== this.selStart && this.value[this.selStart] === 'ℬ') {
|
||||||
this.envelopeWith('ℬ', '');
|
this.envelopeWith('ℬ', '');
|
||||||
} else {
|
} else {
|
||||||
this.envelopeWith('ℬ(', ')');
|
this.envelopeWith('ℬ(', ')');
|
||||||
|
@ -209,4 +209,4 @@ export class TextWrapper implements IManagedText {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import DataTableThemed from '../../components/Common/DataTableThemed';
|
import DataTableThemed from '../../components/Common/DataTableThemed';
|
||||||
import { useUsers } from '../../context/UsersContext';
|
import { useUsers } from '../../context/UsersContext';
|
||||||
import { type IRSForm } from '../../utils/models'
|
import { IRSFormMeta } from '../../utils/models'
|
||||||
|
|
||||||
interface RSFormsTableProps {
|
interface RSFormsTableProps {
|
||||||
schemas: IRSForm[]
|
schemas: IRSFormMeta[]
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSFormsTable({ schemas }: RSFormsTableProps) {
|
function RSFormsTable({ schemas }: RSFormsTableProps) {
|
||||||
|
@ -15,7 +15,7 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { getUserLabel } = useUsers();
|
const { getUserLabel } = useUsers();
|
||||||
|
|
||||||
const openRSForm = (schema: IRSForm, event: React.MouseEvent<Element, MouseEvent>) => {
|
const openRSForm = (schema: IRSFormMeta) => {
|
||||||
navigate(`/rsforms/${schema.id}`);
|
navigate(`/rsforms/${schema.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
||||||
name: 'Шифр',
|
name: 'Шифр',
|
||||||
id: 'alias',
|
id: 'alias',
|
||||||
maxWidth: '140px',
|
maxWidth: '140px',
|
||||||
selector: (schema: IRSForm) => schema.alias,
|
selector: (schema: IRSFormMeta) => schema.alias,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
reorder: true
|
reorder: true
|
||||||
},
|
},
|
||||||
|
@ -33,15 +33,15 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
||||||
name: 'Название',
|
name: 'Название',
|
||||||
id: 'title',
|
id: 'title',
|
||||||
minWidth: '50%',
|
minWidth: '50%',
|
||||||
selector: (schema: IRSForm) => schema.title,
|
selector: (schema: IRSFormMeta) => schema.title,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
reorder: true
|
reorder: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Владелец',
|
name: 'Владелец',
|
||||||
id: 'owner',
|
id: 'owner',
|
||||||
selector: (schema: IRSForm) => schema.owner ?? 0,
|
selector: (schema: IRSFormMeta) => schema.owner ?? 0,
|
||||||
format: (schema: IRSForm) => {
|
format: (schema: IRSFormMeta) => {
|
||||||
return getUserLabel(schema.owner);
|
return getUserLabel(schema.owner);
|
||||||
},
|
},
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
@ -50,8 +50,8 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
||||||
{
|
{
|
||||||
name: 'Обновлена',
|
name: 'Обновлена',
|
||||||
id: 'time_update',
|
id: 'time_update',
|
||||||
selector: (row: IRSForm) => row.time_update,
|
selector: (schema: IRSFormMeta) => schema.time_update,
|
||||||
format: (row: IRSForm) => new Date(row.time_update).toLocaleString(intl.locale),
|
format: (schema: IRSFormMeta) => new Date(schema.time_update).toLocaleString(intl.locale),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
reorder: true
|
reorder: true
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ function RSFormsTable({ schemas }: RSFormsTableProps) {
|
||||||
|
|
||||||
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>Измените фильтр или создайти новую концептуальную схему</p>
|
||||||
</span>}
|
</span>}
|
||||||
|
|
||||||
pagination
|
pagination
|
||||||
|
|
|
@ -19,7 +19,7 @@ function RSFormsPage() {
|
||||||
if (type === FilterType.PERSONAL) {
|
if (type === FilterType.PERSONAL) {
|
||||||
filter.data = user?.id;
|
filter.data = user?.id;
|
||||||
}
|
}
|
||||||
loadList(filter).catch(console.error);
|
loadList(filter);
|
||||||
}, [search, user, loadList]);
|
}, [search, user, loadList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import BackendError from '../components/BackendError';
|
import BackendError from '../components/BackendError';
|
||||||
import Form from '../components/Common/Form';
|
import Form from '../components/Common/Form';
|
||||||
import SubmitButton from '../components/Common/SubmitButton';
|
import SubmitButton from '../components/Common/SubmitButton';
|
||||||
import TextInput from '../components/Common/TextInput';
|
import TextInput from '../components/Common/TextInput';
|
||||||
import TextURL from '../components/Common/TextURL';
|
|
||||||
import InfoMessage from '../components/InfoMessage';
|
import InfoMessage from '../components/InfoMessage';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import { type IUserSignupData } from '../utils/models';
|
import { type IUserSignupData } from '../utils/models';
|
||||||
|
|
||||||
function RegisterPage() {
|
function RegisterPage() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
|
@ -17,7 +19,6 @@ function RegisterPage() {
|
||||||
const [firstName, setFirstName] = useState('');
|
const [firstName, setFirstName] = useState('');
|
||||||
const [lastName, setLastName] = useState('');
|
const [lastName, setLastName] = useState('');
|
||||||
|
|
||||||
const [success, setSuccess] = useState(false);
|
|
||||||
const { user, signup, loading, error, setError } = useAuth()
|
const { user, signup, loading, error, setError } = useAuth()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -35,20 +36,18 @@ function RegisterPage() {
|
||||||
first_name: firstName,
|
first_name: firstName,
|
||||||
last_name: lastName
|
last_name: lastName
|
||||||
};
|
};
|
||||||
signup(data, () => { setSuccess(true); });
|
signup(data, createdUser => {
|
||||||
|
navigate(`/login?username=${createdUser.username}`);
|
||||||
|
toast.success(`Пользователь успешно создан: ${createdUser.username}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full py-2'>
|
<div className='w-full py-2'>
|
||||||
{ success &&
|
{ user &&
|
||||||
<div className='flex flex-col items-center'>
|
|
||||||
<InfoMessage message={`Вы успешно зарегистрировали пользователя ${username}`}/>
|
|
||||||
<TextURL text='Войти в аккаунт' href={`/login?username=${username}`}/>
|
|
||||||
</div>}
|
|
||||||
{ !success && user &&
|
|
||||||
<InfoMessage message={`Вы вошли в систему как ${user.username}. Если хотите зарегистрировать нового пользователя, выйдите из системы (меню в правом верхнем углу экрана)`} /> }
|
<InfoMessage message={`Вы вошли в систему как ${user.username}. Если хотите зарегистрировать нового пользователя, выйдите из системы (меню в правом верхнем углу экрана)`} /> }
|
||||||
{ !success && !user &&
|
{ !user &&
|
||||||
<Form title='Регистрация пользователя' onSubmit={handleSubmit}>
|
<Form title='Регистрация пользователя' onSubmit={handleSubmit}>
|
||||||
<TextInput id='username' label='Имя пользователя' type='text'
|
<TextInput id='username' label='Имя пользователя' type='text'
|
||||||
required
|
required
|
||||||
|
|
1
rsconcept/frontend/src/react-app-env.d.ts
vendored
1
rsconcept/frontend/src/react-app-env.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
/// <reference types='react-scripts' />
|
|
|
@ -1,5 +0,0 @@
|
||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
|
||||||
import '@testing-library/jest-dom';
|
|
|
@ -1,253 +1,272 @@
|
||||||
import axios, { type AxiosResponse } from 'axios'
|
import axios, { AxiosRequestConfig } from 'axios'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
import { type ErrorInfo } from '../components/BackendError'
|
import { type ErrorInfo } from '../components/BackendError'
|
||||||
import { FilterType, type RSFormsFilter } from '../hooks/useRSForms'
|
import { FilterType, RSFormsFilter } from '../hooks/useRSForms'
|
||||||
import { config } from './constants'
|
import { config } from './constants'
|
||||||
import { type ICurrentUser, type IRSForm, type IUserInfo, type IUserProfile } from './models'
|
import {
|
||||||
|
ExpressionParse,
|
||||||
|
IConstituentaList,
|
||||||
|
IConstituentaMeta,
|
||||||
|
ICstCreateData,
|
||||||
|
ICstCreatedResponse,
|
||||||
|
ICstMovetoData,
|
||||||
|
ICstUpdateData,
|
||||||
|
ICurrentUser, IRSFormCreateData, IRSFormData,
|
||||||
|
IRSFormMeta, IRSFormUpdateData, IUserInfo, IUserLoginData, IUserProfile, IUserSignupData, RSExpression
|
||||||
|
} from './models'
|
||||||
|
|
||||||
export type BackendCallback = (response: AxiosResponse) => void;
|
// ================ Data transfer types ================
|
||||||
|
export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void;
|
||||||
|
|
||||||
export interface IFrontRequest {
|
interface IFrontRequest<RequestData, ResponseData> {
|
||||||
onSuccess?: BackendCallback
|
data?: RequestData
|
||||||
|
onSuccess?: DataCallback<ResponseData>
|
||||||
onError?: (error: ErrorInfo) => void
|
onError?: (error: ErrorInfo) => void
|
||||||
setLoading?: (loading: boolean) => void
|
setLoading?: (loading: boolean) => void
|
||||||
showError?: boolean
|
showError?: boolean
|
||||||
data?: any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAxiosRequest {
|
export interface FrontPush<DataType> extends IFrontRequest<DataType, undefined> {
|
||||||
|
data: DataType
|
||||||
|
}
|
||||||
|
export interface FrontPull<DataType> extends IFrontRequest<undefined, DataType>{
|
||||||
|
onSuccess: DataCallback<DataType>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrontExchange<RequestData, ResponseData> extends IFrontRequest<RequestData, ResponseData>{
|
||||||
|
data: RequestData
|
||||||
|
onSuccess: DataCallback<ResponseData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrontAction extends IFrontRequest<undefined, undefined>{}
|
||||||
|
|
||||||
|
interface IAxiosRequest<RequestData, ResponseData> {
|
||||||
endpoint: string
|
endpoint: string
|
||||||
request?: IFrontRequest
|
request: IFrontRequest<RequestData, ResponseData>
|
||||||
title?: string
|
title: string
|
||||||
|
options?: AxiosRequestConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= Export API ==============
|
// ==================== API ====================
|
||||||
export async function postLogin(request?: IFrontRequest) {
|
export function getAuth(request: FrontPull<ICurrentUser>) {
|
||||||
await AxiosPost({
|
AxiosGet({
|
||||||
title: 'Login',
|
|
||||||
endpoint: `${config.url.AUTH}login`,
|
|
||||||
request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAuth(request?: IFrontRequest) {
|
|
||||||
await AxiosGet<ICurrentUser>({
|
|
||||||
title: 'Current user',
|
title: 'Current user',
|
||||||
endpoint: `${config.url.AUTH}auth`,
|
endpoint: `${config.url.AUTH}auth`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProfile(request?: IFrontRequest) {
|
export function postLogin(request: FrontPush<IUserLoginData>) {
|
||||||
await AxiosGet<IUserProfile>({
|
AxiosPost({
|
||||||
title: 'Current user profile',
|
title: 'Login',
|
||||||
endpoint: `${config.url.AUTH}profile`,
|
endpoint: `${config.url.AUTH}login`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postLogout(request?: IFrontRequest) {
|
export function postLogout(request: FrontAction) {
|
||||||
await AxiosPost({
|
AxiosPost({
|
||||||
title: 'Logout',
|
title: 'Logout',
|
||||||
endpoint: `${config.url.AUTH}logout`,
|
endpoint: `${config.url.AUTH}logout`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
export async function postSignup(request?: IFrontRequest) {
|
export function postSignup(request: IFrontRequest<IUserSignupData, IUserProfile>) {
|
||||||
await AxiosPost({
|
AxiosPost({
|
||||||
title: 'Register user',
|
title: 'Register user',
|
||||||
endpoint: `${config.url.AUTH}signup`,
|
endpoint: `${config.url.AUTH}signup`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getActiveUsers(request?: IFrontRequest) {
|
export function getProfile(request: FrontPull<IUserProfile>) {
|
||||||
await AxiosGet<IUserInfo>({
|
AxiosGet({
|
||||||
|
title: 'Current user profile',
|
||||||
|
endpoint: `${config.url.AUTH}profile`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActiveUsers(request: FrontPull<IUserInfo[]>) {
|
||||||
|
AxiosGet({
|
||||||
title: 'Active users list',
|
title: 'Active users list',
|
||||||
endpoint: `${config.url.AUTH}active-users`,
|
endpoint: `${config.url.AUTH}active-users`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRSForms(filter: RSFormsFilter, request?: IFrontRequest) {
|
export function getRSForms(filter: RSFormsFilter, request: FrontPull<IRSFormMeta[]>) {
|
||||||
let endpoint: string = ''
|
const endpoint =
|
||||||
if (filter.type === FilterType.PERSONAL) {
|
filter.type === FilterType.PERSONAL
|
||||||
endpoint = `${config.url.BASE}rsforms?owner=${filter.data as number}`
|
? `${config.url.BASE}rsforms?owner=${filter.data as number}`
|
||||||
} else {
|
: `${config.url.BASE}rsforms?is_common=true`;
|
||||||
endpoint = `${config.url.BASE}rsforms?is_common=true`
|
AxiosGet({
|
||||||
}
|
|
||||||
|
|
||||||
await AxiosGet<IRSForm[]>({
|
|
||||||
title: 'RSForms list',
|
title: 'RSForms list',
|
||||||
endpoint,
|
endpoint: endpoint,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postNewRSForm(request?: IFrontRequest) {
|
export function postNewRSForm(request: FrontExchange<IRSFormCreateData, IRSFormMeta>) {
|
||||||
await AxiosPost({
|
AxiosPost({
|
||||||
title: 'New RSForm',
|
title: 'New RSForm',
|
||||||
endpoint: `${config.url.BASE}rsforms/create-detailed/`,
|
endpoint: `${config.url.BASE}rsforms/create-detailed/`,
|
||||||
request
|
request: request,
|
||||||
|
options: {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRSFormDetails(target: string, request?: IFrontRequest) {
|
export function getRSFormDetails(target: string, request: FrontPull<IRSFormData>) {
|
||||||
await AxiosGet<IRSForm>({
|
AxiosGet({
|
||||||
title: `RSForm details for id=${target}`,
|
title: `RSForm details for id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/details/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/details/`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patchRSForm(target: string, request?: IFrontRequest) {
|
export function patchRSForm(target: string, request: FrontExchange<IRSFormUpdateData, IRSFormMeta>) {
|
||||||
await AxiosPatch({
|
AxiosPatch({
|
||||||
title: `RSForm id=${target}`,
|
title: `RSForm id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patchConstituenta(target: string, request?: IFrontRequest) {
|
export function deleteRSForm(target: string, request: FrontAction) {
|
||||||
await AxiosPatch({
|
AxiosDelete({
|
||||||
title: `Constituenta id=${target}`,
|
|
||||||
endpoint: `${config.url.BASE}constituents/${target}/`,
|
|
||||||
request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteRSForm(target: string, request?: IFrontRequest) {
|
|
||||||
await AxiosDelete({
|
|
||||||
title: `RSForm id=${target}`,
|
title: `RSForm id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTRSFile(target: string, request?: IFrontRequest) {
|
export function postClaimRSForm(target: string, request: FrontPull<IRSFormMeta>) {
|
||||||
await AxiosGetBlob({
|
AxiosPost({
|
||||||
title: `RSForm TRS file for id=${target}`,
|
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/export-trs/`,
|
|
||||||
request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function postClaimRSForm(target: string, request?: IFrontRequest) {
|
|
||||||
await AxiosPost({
|
|
||||||
title: `Claim on RSForm id=${target}`,
|
title: `Claim on RSForm id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${target}/claim/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/claim/`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postCheckExpression(schema: string, request?: IFrontRequest) {
|
export function getTRSFile(target: string, request: FrontPull<Blob>) {
|
||||||
await AxiosPost({
|
AxiosGet({
|
||||||
title: `Check expression for RSForm id=${schema}: ${request?.data.expression as string}`,
|
title: `RSForm TRS file for id=${target}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${schema}/check/`,
|
endpoint: `${config.url.BASE}rsforms/${target}/export-trs/`,
|
||||||
request
|
request: request,
|
||||||
|
options: { responseType: 'blob' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postNewConstituenta(schema: string, request?: IFrontRequest) {
|
export function postNewConstituenta(schema: string, request: FrontExchange<ICstCreateData, ICstCreatedResponse>) {
|
||||||
await AxiosPost({
|
AxiosPost({
|
||||||
title: `New Constituenta for RSForm id=${schema}: ${request?.data.alias as string}`,
|
title: `New Constituenta for RSForm id=${schema}: ${request.data.alias}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${schema}/cst-create/`,
|
endpoint: `${config.url.BASE}rsforms/${schema}/cst-create/`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patchDeleteConstituenta(schema: string, request?: IFrontRequest) {
|
export function patchDeleteConstituenta(schema: string, request: FrontExchange<IConstituentaList, IRSFormData>) {
|
||||||
await AxiosPatch<IRSForm>({
|
AxiosPatch({
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
title: `Delete Constituents for RSForm id=${schema}: ${request.data.items.map(item => String(item.id)).join(' ')}`,
|
||||||
title: `Delete Constituents for RSForm id=${schema}: ${request?.data.items.toString()}`,
|
|
||||||
endpoint: `${config.url.BASE}rsforms/${schema}/cst-multidelete/`,
|
endpoint: `${config.url.BASE}rsforms/${schema}/cst-multidelete/`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patchMoveConstituenta(schema: string, request?: IFrontRequest) {
|
export function patchConstituenta(target: string, request: FrontExchange<ICstUpdateData, IConstituentaMeta>) {
|
||||||
await AxiosPatch<IRSForm>({
|
AxiosPatch({
|
||||||
title: `Moving Constituents for RSForm id=${schema}: ${JSON.stringify(request?.data.items)} to ${request?.data.move_to as number}`,
|
title: `Constituenta id=${target}`,
|
||||||
|
endpoint: `${config.url.BASE}constituents/${target}/`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchMoveConstituenta(schema: string, request: FrontExchange<ICstMovetoData, IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
title: `Moving Constituents for RSForm id=${schema}: ${JSON.stringify(request.data.items)} to ${request.data.move_to}`,
|
||||||
endpoint: `${config.url.BASE}rsforms/${schema}/cst-moveto/`,
|
endpoint: `${config.url.BASE}rsforms/${schema}/cst-moveto/`,
|
||||||
request
|
request: request
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== Helper functions ===========
|
export function postCheckExpression(schema: string, request: FrontExchange<RSExpression, ExpressionParse>) {
|
||||||
async function AxiosGet<ReturnType>({ endpoint, request, title }: IAxiosRequest) {
|
AxiosPost({
|
||||||
if (title) console.log(`[[${title}]] requested`);
|
title: `Check expression for RSForm id=${schema}: ${request.data.expression }`,
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
endpoint: `${config.url.BASE}rsforms/${schema}/check/`,
|
||||||
axios.get<ReturnType>(endpoint)
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ Helper functions =============
|
||||||
|
function AxiosGet<ResponseData>({ endpoint, request, title, options }: IAxiosRequest<undefined, ResponseData>) {
|
||||||
|
console.log(`[[${title}]] requested`);
|
||||||
|
if (request.setLoading) request?.setLoading(true);
|
||||||
|
axios.get<ResponseData>(endpoint, options)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request?.onSuccess) request.onSuccess(response);
|
if (request.onSuccess) request.onSuccess(response.data);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request?.showError) toast.error(error.message);
|
if (request.showError) toast.error(error.message);
|
||||||
if (request?.onError) request.onError(error);
|
if (request.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AxiosGetBlob({ endpoint, request, title }: IAxiosRequest) {
|
function AxiosPost<RequestData, ResponseData>(
|
||||||
if (title) console.log(`[[${title}]] requested`);
|
{ endpoint, request, title, options }: IAxiosRequest<RequestData, ResponseData>
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
) {
|
||||||
axios.get(endpoint, { responseType: 'blob' })
|
console.log(`[[${title}]] posted`);
|
||||||
|
if (request.setLoading) request.setLoading(true);
|
||||||
|
axios.post<ResponseData>(endpoint, request.data, options)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request?.onSuccess) request.onSuccess(response);
|
if (request.onSuccess) request.onSuccess(response.data);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request?.showError) toast.error(error.message);
|
if (request.showError) toast.error(error.message);
|
||||||
if (request?.onError) request.onError(error);
|
if (request.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AxiosPost({ endpoint, request, title }: IAxiosRequest) {
|
function AxiosDelete<RequestData, ResponseData>(
|
||||||
if (title) console.log(`[[${title}]] posted`);
|
{ endpoint, request, title, options }: IAxiosRequest<RequestData, ResponseData>
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
) {
|
||||||
axios.post(endpoint, request?.data)
|
console.log(`[[${title}]] is being deleted`);
|
||||||
|
if (request.setLoading) request.setLoading(true);
|
||||||
|
axios.delete<ResponseData>(endpoint, options)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request?.onSuccess) request.onSuccess(response);
|
if (request.onSuccess) request.onSuccess(response.data);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request?.showError) toast.error(error.message);
|
if (request.showError) toast.error(error.message);
|
||||||
if (request?.onError) request.onError(error);
|
if (request.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AxiosDelete({ endpoint, request, title }: IAxiosRequest) {
|
function AxiosPatch<RequestData, ResponseData>(
|
||||||
if (title) console.log(`[[${title}]] is being deleted`);
|
{ endpoint, request, title, options }: IAxiosRequest<RequestData, ResponseData>
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
) {
|
||||||
axios.delete(endpoint)
|
console.log(`[[${title}]] is being patrially updated`);
|
||||||
|
if (request.setLoading) request.setLoading(true);
|
||||||
|
axios.patch<ResponseData>(endpoint, request.data, options)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request?.onSuccess) request.onSuccess(response);
|
if (request.onSuccess) request.onSuccess(response.data);
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
|
||||||
if (request?.showError) toast.error(error.message);
|
|
||||||
if (request?.onError) request.onError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function AxiosPatch<ReturnType>({ endpoint, request, title }: IAxiosRequest) {
|
|
||||||
if (title) console.log(`[[${title}]] is being patrially updated`);
|
|
||||||
if (request?.setLoading) request?.setLoading(true);
|
|
||||||
axios.patch<ReturnType>(endpoint, request?.data)
|
|
||||||
.then((response) => {
|
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
|
||||||
if (request?.onSuccess) request.onSuccess(response);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (request?.setLoading) request?.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request?.showError) toast.error(error.message);
|
if (request.showError) toast.error(error.message);
|
||||||
if (request?.onError) request.onError(error);
|
if (request.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
160
rsconcept/frontend/src/utils/enums.ts
Normal file
160
rsconcept/frontend/src/utils/enums.ts
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
//! RS language token types enumeration
|
||||||
|
export enum TokenID {
|
||||||
|
// Global, local IDs and literals
|
||||||
|
ID_LOCAL = 258,
|
||||||
|
ID_GLOBAL,
|
||||||
|
ID_FUNCTION,
|
||||||
|
ID_PREDICATE,
|
||||||
|
ID_RADICAL,
|
||||||
|
LIT_INTEGER,
|
||||||
|
LIT_INTSET,
|
||||||
|
LIT_EMPTYSET,
|
||||||
|
|
||||||
|
// Aithmetic
|
||||||
|
PLUS,
|
||||||
|
MINUS,
|
||||||
|
MULTIPLY,
|
||||||
|
|
||||||
|
// Integer predicate symbols
|
||||||
|
GREATER,
|
||||||
|
LESSER,
|
||||||
|
GREATER_OR_EQ,
|
||||||
|
LESSER_OR_EQ,
|
||||||
|
|
||||||
|
// Equality comparison
|
||||||
|
EQUAL,
|
||||||
|
NOTEQUAL,
|
||||||
|
|
||||||
|
// Logic predicate symbols
|
||||||
|
FORALL,
|
||||||
|
EXISTS,
|
||||||
|
NOT,
|
||||||
|
EQUIVALENT,
|
||||||
|
IMPLICATION,
|
||||||
|
OR,
|
||||||
|
AND,
|
||||||
|
|
||||||
|
// Set theory predicate symbols
|
||||||
|
IN,
|
||||||
|
NOTIN,
|
||||||
|
SUBSET,
|
||||||
|
SUBSET_OR_EQ,
|
||||||
|
NOTSUBSET,
|
||||||
|
|
||||||
|
// Set theory operators
|
||||||
|
DECART,
|
||||||
|
UNION,
|
||||||
|
INTERSECTION,
|
||||||
|
SET_MINUS,
|
||||||
|
SYMMINUS,
|
||||||
|
BOOLEAN,
|
||||||
|
|
||||||
|
// Structure operations
|
||||||
|
BIGPR,
|
||||||
|
SMALLPR,
|
||||||
|
FILTER,
|
||||||
|
CARD,
|
||||||
|
BOOL,
|
||||||
|
DEBOOL,
|
||||||
|
REDUCE,
|
||||||
|
|
||||||
|
// Term constructions prefixes
|
||||||
|
DECLARATIVE,
|
||||||
|
RECURSIVE,
|
||||||
|
IMPERATIVE,
|
||||||
|
|
||||||
|
// Punctuation
|
||||||
|
PUNC_DEFINE,
|
||||||
|
PUNC_STRUCT,
|
||||||
|
PUNC_ASSIGN,
|
||||||
|
PUNC_ITERATE,
|
||||||
|
PUNC_PL,
|
||||||
|
PUNC_PR,
|
||||||
|
PUNC_CL,
|
||||||
|
PUNC_CR,
|
||||||
|
PUNC_SL,
|
||||||
|
PUNC_SR,
|
||||||
|
PUNC_BAR,
|
||||||
|
PUNC_COMMA,
|
||||||
|
PUNC_SEMICOLON,
|
||||||
|
|
||||||
|
// ======= Non-terminal tokens =========
|
||||||
|
NT_ENUM_DECL, // Перечисление переменных в кванторной декларации
|
||||||
|
NT_TUPLE, // Кортеж (a,b,c), типизация B(T(a)xT(b)xT(c))
|
||||||
|
NT_ENUMERATION, // Задание множества перечислением
|
||||||
|
NT_TUPLE_DECL, // Декларация переменных с помощью кортежа
|
||||||
|
NT_ARG_DECL, // Объявление аргумента
|
||||||
|
|
||||||
|
NT_FUNC_DEFINITION, // Определение функции
|
||||||
|
NT_ARGUMENTS, // Задание аргументов функции
|
||||||
|
NT_FUNC_CALL, // Вызов функции
|
||||||
|
|
||||||
|
NT_DECLARATIVE_EXPR, // Задание множества с помощью выражения D{x из H | A(x) }
|
||||||
|
NT_IMPERATIVE_EXPR, // Императивное определение
|
||||||
|
NT_RECURSIVE_FULL, // Полная рекурсия
|
||||||
|
NT_RECURSIVE_SHORT, // Сокращенная рекурсия
|
||||||
|
|
||||||
|
NT_IMP_DECLARE, // Блок декларации
|
||||||
|
NT_IMP_ASSIGN, // Блок присвоения
|
||||||
|
NT_IMP_LOGIC, // Блок проверки
|
||||||
|
|
||||||
|
// ======= Helper tokens ========
|
||||||
|
INTERRUPT,
|
||||||
|
END,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RSError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// '8201': 'Число превышает максимально допустимое значение 2147483647!',
|
||||||
|
// '8203': 'Нераспознанный символ!',
|
||||||
|
// '8400': 'Неопределенная синтаксическая ошибка!',
|
||||||
|
// '8406': 'Пропущена скобка ‘)’!',
|
||||||
|
// '8407': 'Пропущена скобка ‘}’!',
|
||||||
|
// '8408': 'Некорректная кванторная декларация переменной!',
|
||||||
|
// '8414': 'Некорректное объявление аргументов функции!',
|
||||||
|
// '8415': 'Некорректное имя локальной переменной в декларации функции!',
|
||||||
|
// '2801': 'Повторное объявление локальной переменной!',
|
||||||
|
// '2802': 'Локальная переменная объявлена, но не использована!',
|
||||||
|
// '8801': 'Использование необъявленной локальной переменной!',
|
||||||
|
// '8802': 'Повторное объявление локальной переменной внутри области действия!',
|
||||||
|
// '8803': 'Типизация операндов не совпадает!',
|
||||||
|
// '8804': 'Использована конституента с неопределенной типизацией!',
|
||||||
|
// '8805': 'Одна из проекций декартова произведения не является типизированным множеством имеющим характер множества!',
|
||||||
|
// '8806': 'Аргумент булеана не является типизированным множеством имеющим характер множества!',
|
||||||
|
// '8807': 'Операнд теоретико-множественного оператора не является типизированным множеством имеющим характер множества!',
|
||||||
|
// '8808': 'Операнд оператора card не является типизированным множеством имеющим характер множества!',
|
||||||
|
// '8809': 'Операнд оператора debool не является типизированным множеством имеющим характер множества!',
|
||||||
|
// '880A': 'Неизвестное имя функции!',
|
||||||
|
// '880B': 'Некорректное использование имени функции без аргументов!',
|
||||||
|
// '8810': 'Операнд оператора red не является типизированным множеством имеющим характер двойного булеана!',
|
||||||
|
// '8811': 'Некорректная типизация аргумента: проекция не определена!',
|
||||||
|
// '8812': 'Некорректная типизация аргумента: T(Pri(a)) = B(Pi(D(T(a))))!',
|
||||||
|
// '8813': 'Типизация элементов перечисления не совпадает!',
|
||||||
|
// '8814': 'Некорректная декларация связанных локальных переменных: количестве переменных в кортеже не соответствует размерности декартова произведения типизации!',
|
||||||
|
// '8815': 'Локальная переменная используется вне области действия!',
|
||||||
|
// '8816': 'Несоответствие типизаций операндов для предиката!',
|
||||||
|
// '8818': 'Некорректное количество аргументов терм-функции!',
|
||||||
|
// '8819': 'Типизация аргумента терм-функции не совпадает с объявленной!',
|
||||||
|
// '881A': 'Сравнение кортежа или элемента с пустым множеством!',
|
||||||
|
// '881C': 'Выражение родовой структуры должно быть ступенью!',
|
||||||
|
// '881F': 'Ожидалось выражение объявления функции!',
|
||||||
|
// '8820': 'Некорректное использование пустого множества как типизированного выражения!',
|
||||||
|
// '8821': 'Радикалы запрещены вне деклараций терм-функций!',
|
||||||
|
// '8822': 'Типизация аргумента фильтра не корректна!',
|
||||||
|
// '8823': 'Количество параметров фильтра не соответствует количеству индексов!',
|
||||||
|
// '8824': 'Для выбранного типа не поддерживаются арифметические операции!',
|
||||||
|
// '8825': 'Типизации не совместимы для выбранной операции/предиката!',
|
||||||
|
// '8826': 'Для выбранного типа не поддерживаются предикаты порядка!',
|
||||||
|
// '8840': 'Используется неинтерпретируемый глобальный идентификатор!',
|
||||||
|
// '8841': 'Использование свойства в качестве значения!',
|
||||||
|
// '8842': 'Не удалось получить дерево разбора для глобального идентификатора!',
|
||||||
|
// '8843': 'Функция не интерпретируется для данных аргументов!',
|
||||||
|
// '8A00': 'Неизвестная ошибка: вычисление прервано!',
|
||||||
|
// '8A01': 'Превышен пределен количества элементов множества!',
|
||||||
|
// '8A02': 'Превышен пределен количества элементов в основании булеана!',
|
||||||
|
// '8A03': 'Использование конституенты с неопределенным значением!',
|
||||||
|
// '8A04': 'Превышен предел количества итераций!',
|
||||||
|
// '8A05': 'Попытка взять debool от многоэлементного множества!',
|
||||||
|
// '8A06': 'Попытка перебрать бесконечное множество!'
|
|
@ -1,56 +1,27 @@
|
||||||
// Current user info
|
// ========= Users ===========
|
||||||
export interface ICurrentUser {
|
export interface IUser {
|
||||||
id: number
|
id: number | null
|
||||||
username: string
|
username: string
|
||||||
is_staff: boolean
|
is_staff: boolean
|
||||||
}
|
|
||||||
|
|
||||||
// User profile data
|
|
||||||
export interface IUserProfile {
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
email: string
|
email: string
|
||||||
first_name: string
|
first_name: string
|
||||||
last_name: string
|
last_name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// User base info
|
export interface ICurrentUser extends Pick<IUser, 'id' | 'username' | 'is_staff'> {}
|
||||||
export interface IUserInfo {
|
|
||||||
id: number
|
export interface IUserLoginData extends Pick<IUser, 'username'> {
|
||||||
username: string
|
password: string
|
||||||
first_name: string
|
|
||||||
last_name: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// User data for signup
|
export interface IUserSignupData extends Omit<IUser, 'is_staff' | 'id'> {
|
||||||
export interface IUserSignupData {
|
|
||||||
username: string
|
|
||||||
email: string
|
|
||||||
first_name: string
|
|
||||||
last_name: string
|
|
||||||
password: string
|
password: string
|
||||||
password2: string
|
password2: string
|
||||||
}
|
}
|
||||||
|
export interface IUserProfile extends Omit<IUser, 'is_staff'> {}
|
||||||
|
export interface IUserInfo extends Omit<IUserProfile, 'email'> {}
|
||||||
|
|
||||||
// User data for signup
|
// ======== Parsing ============
|
||||||
export interface INewCstData {
|
|
||||||
alias: string
|
|
||||||
csttype: CstType
|
|
||||||
insert_after?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constituenta type
|
|
||||||
export enum CstType {
|
|
||||||
BASE = 'basic',
|
|
||||||
CONSTANT = 'constant',
|
|
||||||
STRUCTURED = 'structure',
|
|
||||||
AXIOM = 'axiom',
|
|
||||||
TERM = 'term',
|
|
||||||
FUNCTION = 'function',
|
|
||||||
PREDICATE = 'predicate',
|
|
||||||
THEOREM = 'theorem'
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValueClass
|
// ValueClass
|
||||||
export enum ValueClass {
|
export enum ValueClass {
|
||||||
INVALID = 'invalid',
|
INVALID = 'invalid',
|
||||||
|
@ -72,25 +43,56 @@ export enum ParsingStatus {
|
||||||
INCORRECT = 'incorrect'
|
INCORRECT = 'incorrect'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constituenta data
|
export interface RSErrorDescription {
|
||||||
|
errorType: number
|
||||||
|
position: number
|
||||||
|
isCritical: boolean
|
||||||
|
params: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExpressionParse {
|
||||||
|
parseResult: boolean
|
||||||
|
syntax: Syntax
|
||||||
|
typification: string
|
||||||
|
valueClass: ValueClass
|
||||||
|
astText: string
|
||||||
|
errors: RSErrorDescription[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RSExpression {
|
||||||
|
expression: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== Constituenta ==========
|
||||||
|
export enum CstType {
|
||||||
|
BASE = 'basic',
|
||||||
|
STRUCTURED = 'structure',
|
||||||
|
TERM = 'term',
|
||||||
|
AXIOM = 'axiom',
|
||||||
|
FUNCTION = 'function',
|
||||||
|
PREDICATE = 'predicate',
|
||||||
|
CONSTANT = 'constant',
|
||||||
|
THEOREM = 'theorem'
|
||||||
|
}
|
||||||
|
|
||||||
export interface IConstituenta {
|
export interface IConstituenta {
|
||||||
id: number
|
id: number
|
||||||
alias: string
|
alias: string
|
||||||
cstType: CstType
|
cstType: CstType
|
||||||
convention?: string
|
convention: string
|
||||||
term?: {
|
term: {
|
||||||
raw: string
|
raw: string
|
||||||
resolved?: string
|
resolved: string
|
||||||
forms?: string[]
|
forms: string[]
|
||||||
}
|
}
|
||||||
definition?: {
|
definition: {
|
||||||
formal: string
|
formal: string
|
||||||
text: {
|
text: {
|
||||||
raw: string
|
raw: string
|
||||||
resolved?: string
|
resolved: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parse?: {
|
parse: {
|
||||||
status: ParsingStatus
|
status: ParsingStatus
|
||||||
valueClass: ValueClass
|
valueClass: ValueClass
|
||||||
typification: string
|
typification: string
|
||||||
|
@ -98,7 +100,42 @@ export interface IConstituenta {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSForm stats
|
export interface IConstituentaMeta {
|
||||||
|
id: number
|
||||||
|
schema: number
|
||||||
|
order: number
|
||||||
|
alias: string
|
||||||
|
convention: string
|
||||||
|
cst_type: CstType
|
||||||
|
definition_formal: string
|
||||||
|
definition_raw: string
|
||||||
|
definition_resolved: string
|
||||||
|
term_raw: string
|
||||||
|
term_resolved: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConstituentaID extends Pick<IConstituentaMeta, 'id'>{}
|
||||||
|
export interface IConstituentaList {
|
||||||
|
items: IConstituentaID[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICstCreateData extends Pick<IConstituentaMeta, 'alias' | 'cst_type'> {
|
||||||
|
insert_after: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICstMovetoData extends IConstituentaList {
|
||||||
|
move_to: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICstUpdateData
|
||||||
|
extends Pick<IConstituentaMeta, 'id' | 'alias' | 'convention' | 'definition_formal' | 'definition_raw' | 'term_raw'> {}
|
||||||
|
|
||||||
|
export interface ICstCreatedResponse {
|
||||||
|
new_cst: IConstituentaMeta
|
||||||
|
schema: IRSFormData
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== RSForm ============
|
||||||
export interface IRSFormStats {
|
export interface IRSFormStats {
|
||||||
count_all: number
|
count_all: number
|
||||||
count_errors: number
|
count_errors: number
|
||||||
|
@ -117,7 +154,6 @@ export interface IRSFormStats {
|
||||||
count_theorem: number
|
count_theorem: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSForm data
|
|
||||||
export interface IRSForm {
|
export interface IRSForm {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
|
@ -126,125 +162,24 @@ export interface IRSForm {
|
||||||
is_common: boolean
|
is_common: boolean
|
||||||
time_create: string
|
time_create: string
|
||||||
time_update: string
|
time_update: string
|
||||||
owner?: number
|
owner: number | null
|
||||||
items?: IConstituenta[]
|
items: IConstituenta[]
|
||||||
stats?: IRSFormStats
|
stats: IRSFormStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSForm user input
|
export interface IRSFormData extends Omit<IRSForm, 'stats' > {}
|
||||||
export interface IRSFormCreateData {
|
export interface IRSFormMeta extends Omit<IRSForm, 'items' | 'stats'> {}
|
||||||
title: string
|
|
||||||
alias: string
|
export interface IRSFormUpdateData
|
||||||
comment: string
|
extends Omit<IRSFormMeta, 'time_create' | 'time_update' | 'id' | 'owner'> {}
|
||||||
is_common: boolean
|
|
||||||
|
export interface IRSFormCreateData
|
||||||
|
extends IRSFormUpdateData {
|
||||||
file?: File
|
file?: File
|
||||||
|
fileName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
//! RS language token types enumeration
|
// ================ Misc types ================
|
||||||
export enum TokenID {
|
|
||||||
// Global, local IDs and literals
|
|
||||||
ID_LOCAL = 258,
|
|
||||||
ID_GLOBAL,
|
|
||||||
ID_FUNCTION,
|
|
||||||
ID_PREDICATE,
|
|
||||||
ID_RADICAL,
|
|
||||||
LIT_INTEGER,
|
|
||||||
LIT_INTSET,
|
|
||||||
LIT_EMPTYSET,
|
|
||||||
|
|
||||||
// Aithmetic
|
|
||||||
PLUS,
|
|
||||||
MINUS,
|
|
||||||
MULTIPLY,
|
|
||||||
|
|
||||||
// Integer predicate symbols
|
|
||||||
GREATER,
|
|
||||||
LESSER,
|
|
||||||
GREATER_OR_EQ,
|
|
||||||
LESSER_OR_EQ,
|
|
||||||
|
|
||||||
// Equality comparison
|
|
||||||
EQUAL,
|
|
||||||
NOTEQUAL,
|
|
||||||
|
|
||||||
// Logic predicate symbols
|
|
||||||
FORALL,
|
|
||||||
EXISTS,
|
|
||||||
NOT,
|
|
||||||
EQUIVALENT,
|
|
||||||
IMPLICATION,
|
|
||||||
OR,
|
|
||||||
AND,
|
|
||||||
|
|
||||||
// Set theory predicate symbols
|
|
||||||
IN,
|
|
||||||
NOTIN,
|
|
||||||
SUBSET,
|
|
||||||
SUBSET_OR_EQ,
|
|
||||||
NOTSUBSET,
|
|
||||||
|
|
||||||
// Set theory operators
|
|
||||||
DECART,
|
|
||||||
UNION,
|
|
||||||
INTERSECTION,
|
|
||||||
SET_MINUS,
|
|
||||||
SYMMINUS,
|
|
||||||
BOOLEAN,
|
|
||||||
|
|
||||||
// Structure operations
|
|
||||||
BIGPR,
|
|
||||||
SMALLPR,
|
|
||||||
FILTER,
|
|
||||||
CARD,
|
|
||||||
BOOL,
|
|
||||||
DEBOOL,
|
|
||||||
REDUCE,
|
|
||||||
|
|
||||||
// Term constructions prefixes
|
|
||||||
DECLARATIVE,
|
|
||||||
RECURSIVE,
|
|
||||||
IMPERATIVE,
|
|
||||||
|
|
||||||
// Punctuation
|
|
||||||
PUNC_DEFINE,
|
|
||||||
PUNC_STRUCT,
|
|
||||||
PUNC_ASSIGN,
|
|
||||||
PUNC_ITERATE,
|
|
||||||
PUNC_PL,
|
|
||||||
PUNC_PR,
|
|
||||||
PUNC_CL,
|
|
||||||
PUNC_CR,
|
|
||||||
PUNC_SL,
|
|
||||||
PUNC_SR,
|
|
||||||
PUNC_BAR,
|
|
||||||
PUNC_COMMA,
|
|
||||||
PUNC_SEMICOLON,
|
|
||||||
|
|
||||||
// ======= Non-terminal tokens =========
|
|
||||||
NT_ENUM_DECL, // Перечисление переменных в кванторной декларации
|
|
||||||
NT_TUPLE, // Кортеж (a,b,c), типизация B(T(a)xT(b)xT(c))
|
|
||||||
NT_ENUMERATION, // Задание множества перечислением
|
|
||||||
NT_TUPLE_DECL, // Декларация переменных с помощью кортежа
|
|
||||||
NT_ARG_DECL, // Объявление аргумента
|
|
||||||
|
|
||||||
NT_FUNC_DEFINITION, // Определение функции
|
|
||||||
NT_ARGUMENTS, // Задание аргументов функции
|
|
||||||
NT_FUNC_CALL, // Вызов функции
|
|
||||||
|
|
||||||
NT_DECLARATIVE_EXPR, // Задание множества с помощью выражения D{x из H | A(x) }
|
|
||||||
NT_IMPERATIVE_EXPR, // Императивное определение
|
|
||||||
NT_RECURSIVE_FULL, // Полная рекурсия
|
|
||||||
NT_RECURSIVE_SHORT, // Сокращенная рекурсия
|
|
||||||
|
|
||||||
NT_IMP_DECLARE, // Блок декларации
|
|
||||||
NT_IMP_ASSIGN, // Блок присвоения
|
|
||||||
NT_IMP_LOGIC, // Блок проверки
|
|
||||||
|
|
||||||
// ======= Helper tokens ========
|
|
||||||
INTERRUPT,
|
|
||||||
END,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constituenta edit mode
|
// Constituenta edit mode
|
||||||
export enum EditMode {
|
export enum EditMode {
|
||||||
TEXT = 'text',
|
TEXT = 'text',
|
||||||
|
@ -280,9 +215,10 @@ export function inferStatus(parse?: ParsingStatus, value?: ValueClass): Expressi
|
||||||
return ExpressionStatus.VERIFIED
|
return ExpressionStatus.VERIFIED
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CalculateStats(schema: IRSForm) {
|
export function LoadRSFormData(schema: IRSFormData): IRSForm {
|
||||||
if (!schema.items) {
|
const result = schema as IRSForm
|
||||||
schema.stats = {
|
if (!result.items) {
|
||||||
|
result.stats = {
|
||||||
count_all: 0,
|
count_all: 0,
|
||||||
count_errors: 0,
|
count_errors: 0,
|
||||||
count_property: 0,
|
count_property: 0,
|
||||||
|
@ -299,22 +235,22 @@ export function CalculateStats(schema: IRSForm) {
|
||||||
count_predicate: 0,
|
count_predicate: 0,
|
||||||
count_theorem: 0
|
count_theorem: 0
|
||||||
}
|
}
|
||||||
return;
|
return result;
|
||||||
}
|
}
|
||||||
schema.stats = {
|
result.stats = {
|
||||||
count_all: schema.items?.length || 0,
|
count_all: schema.items.length || 0,
|
||||||
count_errors: schema.items?.reduce(
|
count_errors: schema.items.reduce(
|
||||||
(sum, cst) => sum + (cst.parse?.status === ParsingStatus.INCORRECT ? 1 : 0) || 0, 0),
|
(sum, cst) => sum + (cst.parse?.status === ParsingStatus.INCORRECT ? 1 : 0) || 0, 0),
|
||||||
count_property: schema.items?.reduce(
|
count_property: schema.items.reduce(
|
||||||
(sum, cst) => sum + (cst.parse?.valueClass === ValueClass.PROPERTY ? 1 : 0) || 0, 0),
|
(sum, cst) => sum + (cst.parse?.valueClass === ValueClass.PROPERTY ? 1 : 0) || 0, 0),
|
||||||
count_incalc: schema.items?.reduce(
|
count_incalc: schema.items.reduce(
|
||||||
(sum, cst) => sum +
|
(sum, cst) => sum +
|
||||||
((cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.INVALID) ? 1 : 0) || 0, 0),
|
((cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.INVALID) ? 1 : 0) || 0, 0),
|
||||||
|
|
||||||
count_termin: schema.items?.reduce(
|
count_termin: schema.items.reduce(
|
||||||
(sum, cst) => (sum + (cst.term?.raw ? 1 : 0) || 0), 0),
|
(sum, cst) => (sum + (cst.term?.raw ? 1 : 0) || 0), 0),
|
||||||
|
|
||||||
count_base: schema.items?.reduce(
|
count_base: schema.items.reduce(
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.BASE ? 1 : 0), 0),
|
(sum, cst) => sum + (cst.cstType === CstType.BASE ? 1 : 0), 0),
|
||||||
count_constant: schema.items?.reduce(
|
count_constant: schema.items?.reduce(
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.CONSTANT ? 1 : 0), 0),
|
(sum, cst) => sum + (cst.cstType === CstType.CONSTANT ? 1 : 0), 0),
|
||||||
|
@ -322,15 +258,16 @@ export function CalculateStats(schema: IRSForm) {
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.STRUCTURED ? 1 : 0), 0),
|
(sum, cst) => sum + (cst.cstType === CstType.STRUCTURED ? 1 : 0), 0),
|
||||||
count_axiom: schema.items?.reduce(
|
count_axiom: schema.items?.reduce(
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.AXIOM ? 1 : 0), 0),
|
(sum, cst) => sum + (cst.cstType === CstType.AXIOM ? 1 : 0), 0),
|
||||||
count_term: schema.items?.reduce(
|
count_term: schema.items.reduce(
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.TERM ? 1 : 0), 0),
|
(sum, cst) => sum + (cst.cstType === CstType.TERM ? 1 : 0), 0),
|
||||||
count_function: schema.items?.reduce(
|
count_function: schema.items.reduce(
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.FUNCTION ? 1 : 0), 0),
|
(sum, cst) => sum + (cst.cstType === CstType.FUNCTION ? 1 : 0), 0),
|
||||||
count_predicate: schema.items?.reduce(
|
count_predicate: schema.items.reduce(
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.PREDICATE ? 1 : 0), 0),
|
(sum, cst) => sum + (cst.cstType === CstType.PREDICATE ? 1 : 0), 0),
|
||||||
count_theorem: schema.items?.reduce(
|
count_theorem: schema.items.reduce(
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.THEOREM ? 1 : 0), 0)
|
(sum, cst) => sum + (cst.cstType === CstType.THEOREM ? 1 : 0), 0)
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function matchConstituenta(query: string, target?: IConstituenta) {
|
export function matchConstituenta(query: string, target?: IConstituenta) {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import fileDownload from 'js-file-download';
|
import fileDownload from 'js-file-download';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { type BackendCallback } from './backendAPI';
|
import { type DataCallback } from './backendAPI';
|
||||||
|
import { IRSFormMeta } from './models';
|
||||||
|
|
||||||
export function shareCurrentURLProc() {
|
export function shareCurrentURLProc() {
|
||||||
const url = window.location.href + '&share';
|
const url = window.location.href + '&share';
|
||||||
|
@ -11,7 +12,7 @@ export function shareCurrentURLProc() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function claimOwnershipProc(
|
export function claimOwnershipProc(
|
||||||
claim: (callback: BackendCallback) => void
|
claim: (callback: DataCallback<IRSFormMeta>) => void
|
||||||
) {
|
) {
|
||||||
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
||||||
return;
|
return;
|
||||||
|
@ -20,7 +21,7 @@ export function claimOwnershipProc(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteRSFormProc(
|
export function deleteRSFormProc(
|
||||||
destroy: (callback: BackendCallback) => void,
|
destroy: (callback: DataCallback) => void,
|
||||||
navigate: (path: string) => void
|
navigate: (path: string) => void
|
||||||
) {
|
) {
|
||||||
if (!window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
if (!window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
||||||
|
@ -33,14 +34,14 @@ export function deleteRSFormProc(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function downloadRSFormProc(
|
export function downloadRSFormProc(
|
||||||
download: (callback: BackendCallback) => void,
|
download: (callback: DataCallback<Blob>) => void,
|
||||||
fileName: string
|
fileName: string
|
||||||
) {
|
) {
|
||||||
download((response) => {
|
download((data) => {
|
||||||
try {
|
try {
|
||||||
fileDownload(response.data, fileName);
|
fileDownload(data, fileName);
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
toast.error(error.message);
|
console.error(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { CstType, ExpressionStatus, type IConstituenta, type IRSForm, ParsingStatus, TokenID } from './models';
|
import { TokenID } from './enums';
|
||||||
|
import { CstType, ExpressionStatus, type IConstituenta, type IRSForm, ParsingStatus, ValueClass } from './models';
|
||||||
|
|
||||||
export interface IRSButtonData {
|
export interface IRSButtonData {
|
||||||
text: string
|
text: string
|
||||||
|
@ -195,6 +196,20 @@ export function getCstTypeLabel(type: CstType) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCstTypeShortcut(type: CstType) {
|
||||||
|
const prefix = getCstTypeLabel(type) + ' [Alt + ';
|
||||||
|
switch (type) {
|
||||||
|
case CstType.BASE: return prefix + '1]';
|
||||||
|
case CstType.STRUCTURED: return prefix + '2]';
|
||||||
|
case CstType.TERM: return prefix + '3]';
|
||||||
|
case CstType.AXIOM: return prefix + '4]';
|
||||||
|
case CstType.FUNCTION: return prefix + 'Q]';
|
||||||
|
case CstType.PREDICATE: return prefix + 'W]';
|
||||||
|
case CstType.CONSTANT: return prefix + '5]';
|
||||||
|
case CstType.THEOREM: return prefix + '6]';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const CstTypeSelector = (Object.values(CstType)).map(
|
export const CstTypeSelector = (Object.values(CstType)).map(
|
||||||
(typeStr) => {
|
(typeStr) => {
|
||||||
const type = typeStr as CstType;
|
const type = typeStr as CstType;
|
||||||
|
@ -272,3 +287,30 @@ export function createAliasFor(type: CstType, schema: IRSForm): string {
|
||||||
}, 1);
|
}, 1);
|
||||||
return `${prefix}${index}`;
|
return `${prefix}${index}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMockConstituenta(id: number, alias: string, type: CstType, comment: string): IConstituenta {
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
alias: alias,
|
||||||
|
convention: comment,
|
||||||
|
cstType: type,
|
||||||
|
term: {
|
||||||
|
raw: '',
|
||||||
|
resolved: '',
|
||||||
|
forms: []
|
||||||
|
},
|
||||||
|
definition: {
|
||||||
|
formal: '',
|
||||||
|
text: {
|
||||||
|
raw: '',
|
||||||
|
resolved: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parse: {
|
||||||
|
status: ParsingStatus.INCORRECT,
|
||||||
|
valueClass: ValueClass.INVALID,
|
||||||
|
typification: 'N/A',
|
||||||
|
syntaxTree: ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
1
rsconcept/frontend/src/vite-env.d.ts
vendored
Normal file
1
rsconcept/frontend/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
|
@ -1,5 +1,5 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
export default {
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
content: [
|
content: [
|
||||||
'./src/**/*.{js,jsx,ts,tsx}',
|
'./src/**/*.{js,jsx,ts,tsx}',
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "ES2020",
|
||||||
"lib": [
|
"useDefineForClassFields": true,
|
||||||
"dom",
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
"dom.iterable",
|
"module": "ESNext",
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
/* Bundler mode */
|
||||||
"strict": true,
|
"moduleResolution": "bundler",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"allowImportingTsExtensions": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src"],
|
||||||
"src"
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
10
rsconcept/frontend/tsconfig.node.json
Normal file
10
rsconcept/frontend/tsconfig.node.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
10
rsconcept/frontend/vite.config.ts
Normal file
10
rsconcept/frontend/vite.config.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 3000
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user