mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactor backend using drf-spectacular
This commit is contained in:
parent
c322e2e8eb
commit
f21a01bbc0
|
@ -58,6 +58,7 @@ This readme file is used mostly to document project dependencies
|
||||||
- djangorestframework
|
- djangorestframework
|
||||||
- django-cors-headers
|
- django-cors-headers
|
||||||
- django-filter
|
- django-filter
|
||||||
|
- drf-spectacular
|
||||||
- tzdata
|
- tzdata
|
||||||
- gunicorn
|
- gunicorn
|
||||||
- coreapi
|
- coreapi
|
||||||
|
@ -72,7 +73,6 @@ This readme file is used mostly to document project dependencies
|
||||||
- coverage
|
- coverage
|
||||||
- pylint
|
- pylint
|
||||||
- mypy
|
- mypy
|
||||||
- django-stubs[compatible-mypy]
|
|
||||||
- djangorestframework-stubs[compatible-mypy]
|
- djangorestframework-stubs[compatible-mypy]
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
|
|
|
@ -27,6 +27,11 @@ class ExpressionSerializer(serializers.Serializer):
|
||||||
expression = serializers.CharField()
|
expression = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class ResultTextSerializer(serializers.Serializer):
|
||||||
|
''' Serializer: Text result of a function call. '''
|
||||||
|
result = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
class TextSerializer(serializers.Serializer):
|
class TextSerializer(serializers.Serializer):
|
||||||
''' Serializer: Text with references. '''
|
''' Serializer: Text with references. '''
|
||||||
text = serializers.CharField()
|
text = serializers.CharField()
|
||||||
|
@ -379,6 +384,7 @@ class CstRenameSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class CstListSerializer(serializers.Serializer):
|
class CstListSerializer(serializers.Serializer):
|
||||||
''' Serializer: List of constituents from one origin. '''
|
''' Serializer: List of constituents from one origin. '''
|
||||||
|
# TODO: fix schema
|
||||||
items = serializers.ListField(
|
items = serializers.ListField(
|
||||||
child=CstStandaloneSerializer()
|
child=CstStandaloneSerializer()
|
||||||
)
|
)
|
||||||
|
@ -403,6 +409,7 @@ class CstMoveSerializer(CstListSerializer):
|
||||||
|
|
||||||
class ResolverSerializer(serializers.Serializer):
|
class ResolverSerializer(serializers.Serializer):
|
||||||
''' Serializer: Resolver results serializer. '''
|
''' Serializer: Resolver results serializer. '''
|
||||||
|
# TODO: add schema
|
||||||
def to_representation(self, instance: Resolver) -> dict:
|
def to_representation(self, instance: Resolver) -> dict:
|
||||||
return {
|
return {
|
||||||
'input': instance.input,
|
'input': instance.input,
|
||||||
|
|
|
@ -9,6 +9,7 @@ from rest_framework import views, viewsets, filters, generics, permissions
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
|
|
||||||
import pyconcept
|
import pyconcept
|
||||||
from . import models as m
|
from . import models as m
|
||||||
|
@ -16,6 +17,8 @@ from . import serializers as s
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['Library'])
|
||||||
|
@extend_schema_view()
|
||||||
class LibraryActiveView(generics.ListAPIView):
|
class LibraryActiveView(generics.ListAPIView):
|
||||||
''' Endpoint: Get list of rsforms available for active user. '''
|
''' Endpoint: Get list of rsforms available for active user. '''
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
@ -32,6 +35,8 @@ class LibraryActiveView(generics.ListAPIView):
|
||||||
return m.LibraryItem.objects.filter(is_common=True).order_by('-time_update')
|
return m.LibraryItem.objects.filter(is_common=True).order_by('-time_update')
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['Constituenta'])
|
||||||
|
@extend_schema_view()
|
||||||
class ConstituentAPIView(generics.RetrieveUpdateAPIView):
|
class ConstituentAPIView(generics.RetrieveUpdateAPIView):
|
||||||
''' Endpoint: Get / Update Constituenta. '''
|
''' Endpoint: Get / Update Constituenta. '''
|
||||||
queryset = m.Constituenta.objects.all()
|
queryset = m.Constituenta.objects.all()
|
||||||
|
@ -45,7 +50,10 @@ class ConstituentAPIView(generics.RetrieveUpdateAPIView):
|
||||||
result.append(utils.SchemaOwnerOrAdmin())
|
result.append(utils.SchemaOwnerOrAdmin())
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-ancestors
|
# pylint: disable=too-many-ancestors
|
||||||
|
@extend_schema(tags=['Library'])
|
||||||
|
@extend_schema_view()
|
||||||
class LibraryViewSet(viewsets.ModelViewSet):
|
class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
''' Endpoint: Library operations. '''
|
''' Endpoint: Library operations. '''
|
||||||
queryset = m.LibraryItem.objects.all()
|
queryset = m.LibraryItem.objects.all()
|
||||||
|
@ -76,6 +84,12 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
def _get_item(self) -> m.LibraryItem:
|
def _get_item(self) -> m.LibraryItem:
|
||||||
return cast(m.LibraryItem, self.get_object())
|
return cast(m.LibraryItem, self.get_object())
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=s.LibraryItemSerializer,
|
||||||
|
summary='clone item including contents',
|
||||||
|
tags=['Library']
|
||||||
|
)
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@action(detail=True, methods=['post'], url_path='clone')
|
@action(detail=True, methods=['post'], url_path='clone')
|
||||||
def clone(self, request, pk):
|
def clone(self, request, pk):
|
||||||
|
@ -99,6 +113,12 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
return Response(status=201, data=s.RSFormParseSerializer(new_schema).data)
|
return Response(status=201, data=s.RSFormParseSerializer(new_schema).data)
|
||||||
return Response(status=404)
|
return Response(status=404)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=None,
|
||||||
|
responses={200: s.LibraryItemSerializer},
|
||||||
|
summary='claim item',
|
||||||
|
tags=['Library']
|
||||||
|
)
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'])
|
||||||
def claim(self, request, pk=None):
|
def claim(self, request, pk=None):
|
||||||
|
@ -112,6 +132,12 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
m.Subscription.subscribe(user=item.owner, item=item)
|
m.Subscription.subscribe(user=item.owner, item=item)
|
||||||
return Response(status=200, data=s.LibraryItemSerializer(item).data)
|
return Response(status=200, data=s.LibraryItemSerializer(item).data)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=None,
|
||||||
|
responses={200: None},
|
||||||
|
summary='subscribe to item',
|
||||||
|
tags=['Library']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'])
|
||||||
def subscribe(self, request, pk):
|
def subscribe(self, request, pk):
|
||||||
''' Endpoint: Subscribe current user to item. '''
|
''' Endpoint: Subscribe current user to item. '''
|
||||||
|
@ -119,6 +145,12 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
m.Subscription.subscribe(user=self.request.user, item=item)
|
m.Subscription.subscribe(user=self.request.user, item=item)
|
||||||
return Response(status=200)
|
return Response(status=200)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=None,
|
||||||
|
responses={200: None},
|
||||||
|
summary='unsubscribe from item',
|
||||||
|
tags=['Library']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['delete'])
|
@action(detail=True, methods=['delete'])
|
||||||
def unsubscribe(self, request, pk):
|
def unsubscribe(self, request, pk):
|
||||||
''' Endpoint: Unsubscribe current user from item. '''
|
''' Endpoint: Unsubscribe current user from item. '''
|
||||||
|
@ -127,6 +159,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
return Response(status=200)
|
return Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['RSForm'])
|
||||||
|
@extend_schema_view()
|
||||||
class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
|
||||||
''' Endpoint: RSForm operations. '''
|
''' Endpoint: RSForm operations. '''
|
||||||
queryset = m.LibraryItem.objects.filter(item_type=m.LibraryItemType.RSFORM)
|
queryset = m.LibraryItem.objects.filter(item_type=m.LibraryItemType.RSFORM)
|
||||||
|
@ -144,6 +178,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
return [permission() for permission in permission_classes]
|
return [permission() for permission in permission_classes]
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=s.CstCreateSerializer,
|
||||||
|
summary='create constituenta',
|
||||||
|
tags=['Constituenta']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['post'], url_path='cst-create')
|
@action(detail=True, methods=['post'], url_path='cst-create')
|
||||||
def cst_create(self, request, pk):
|
def cst_create(self, request, pk):
|
||||||
''' Create new constituenta. '''
|
''' Create new constituenta. '''
|
||||||
|
@ -160,6 +200,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
response['Location'] = new_cst.get_absolute_url()
|
response['Location'] = new_cst.get_absolute_url()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=s.CstRenameSerializer,
|
||||||
|
summary='rename constituenta',
|
||||||
|
tags=['Constituenta']
|
||||||
|
)
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@action(detail=True, methods=['patch'], url_path='cst-rename')
|
@action(detail=True, methods=['patch'], url_path='cst-rename')
|
||||||
def cst_rename(self, request, pk):
|
def cst_rename(self, request, pk):
|
||||||
|
@ -178,6 +224,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
'schema': s.RSFormParseSerializer(schema).data
|
'schema': s.RSFormParseSerializer(schema).data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=s.CstListSerializer,
|
||||||
|
summary='delete constituents',
|
||||||
|
tags=['Constituenta']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='cst-multidelete')
|
@action(detail=True, methods=['patch'], url_path='cst-multidelete')
|
||||||
def cst_multidelete(self, request, pk):
|
def cst_multidelete(self, request, pk):
|
||||||
''' Endpoint: Delete multiple constituents. '''
|
''' Endpoint: Delete multiple constituents. '''
|
||||||
|
@ -188,6 +240,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
schema.item.refresh_from_db()
|
schema.item.refresh_from_db()
|
||||||
return Response(status=202, data=s.RSFormParseSerializer(schema).data)
|
return Response(status=202, data=s.RSFormParseSerializer(schema).data)
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=s.CstMoveSerializer,
|
||||||
|
summary='move constituenta',
|
||||||
|
tags=['Constituenta']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='cst-moveto')
|
@action(detail=True, methods=['patch'], url_path='cst-moveto')
|
||||||
def cst_moveto(self, request, pk):
|
def cst_moveto(self, request, pk):
|
||||||
''' Endpoint: Move multiple constituents. '''
|
''' Endpoint: Move multiple constituents. '''
|
||||||
|
@ -198,6 +256,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
schema.item.refresh_from_db()
|
schema.item.refresh_from_db()
|
||||||
return Response(status=200, data=s.RSFormParseSerializer(schema).data)
|
return Response(status=200, data=s.RSFormParseSerializer(schema).data)
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=None,
|
||||||
|
summary='reset aliases, update expressions and references',
|
||||||
|
tags=['RSForm']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='reset-aliases')
|
@action(detail=True, methods=['patch'], url_path='reset-aliases')
|
||||||
def reset_aliases(self, request, pk):
|
def reset_aliases(self, request, pk):
|
||||||
''' Endpoint: Recreate all aliases based on order. '''
|
''' Endpoint: Recreate all aliases based on order. '''
|
||||||
|
@ -205,6 +269,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
schema.reset_aliases()
|
schema.reset_aliases()
|
||||||
return Response(status=200, data=s.RSFormParseSerializer(schema).data)
|
return Response(status=200, data=s.RSFormParseSerializer(schema).data)
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=s.RSFormUploadSerializer,
|
||||||
|
summary='load data from TRS file',
|
||||||
|
tags=['RSForm']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['patch'], url_path='load-trs')
|
@action(detail=True, methods=['patch'], url_path='load-trs')
|
||||||
def load_trs(self, request, pk):
|
def load_trs(self, request, pk):
|
||||||
''' Endpoint: Load data from file and replace current schema. '''
|
''' Endpoint: Load data from file and replace current schema. '''
|
||||||
|
@ -220,12 +290,24 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
schema = serializer.save()
|
schema = serializer.save()
|
||||||
return Response(status=200, data=s.RSFormParseSerializer(schema).data)
|
return Response(status=200, data=s.RSFormParseSerializer(schema).data)
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=None,
|
||||||
|
summary='get all constituents data from DB',
|
||||||
|
tags=['RSForm']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['get'])
|
@action(detail=True, methods=['get'])
|
||||||
def contents(self, request, pk):
|
def contents(self, request, pk):
|
||||||
''' Endpoint: View schema db contents (including constituents). '''
|
''' Endpoint: View schema db contents (including constituents). '''
|
||||||
schema = s.RSFormSerializer(self._get_schema()).data
|
schema = s.RSFormSerializer(self._get_schema()).data
|
||||||
return Response(schema)
|
return Response(schema)
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=None,
|
||||||
|
summary='get all constituents data and parses',
|
||||||
|
tags=['RSForm']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['get'])
|
@action(detail=True, methods=['get'])
|
||||||
def details(self, request, pk):
|
def details(self, request, pk):
|
||||||
''' Endpoint: Detailed schema view including statuses and parse. '''
|
''' Endpoint: Detailed schema view including statuses and parse. '''
|
||||||
|
@ -233,6 +315,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
serializer = s.RSFormParseSerializer(schema)
|
serializer = s.RSFormParseSerializer(schema)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
# TODO: response schema
|
||||||
|
@extend_schema(
|
||||||
|
request=s.ExpressionSerializer,
|
||||||
|
summary='check RSLang expression',
|
||||||
|
tags=['RSForm', 'Functions']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'])
|
||||||
def check(self, request, pk):
|
def check(self, request, pk):
|
||||||
''' Endpoint: Check RSLang expression against schema context. '''
|
''' Endpoint: Check RSLang expression against schema context. '''
|
||||||
|
@ -243,6 +331,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
result = pyconcept.check_expression(json.dumps(schema.data), expression)
|
result = pyconcept.check_expression(json.dumps(schema.data), expression)
|
||||||
return Response(json.loads(result))
|
return Response(json.loads(result))
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=s.TextSerializer,
|
||||||
|
responses={200: s.ResolverSerializer},
|
||||||
|
summary='resolve text with references',
|
||||||
|
tags=['RSForm', 'Functions']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'])
|
||||||
def resolve(self, request, pk):
|
def resolve(self, request, pk):
|
||||||
''' Endpoint: Resolve refenrces in text against schema terms context. '''
|
''' Endpoint: Resolve refenrces in text against schema terms context. '''
|
||||||
|
@ -253,6 +347,12 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
resolver.resolve(text)
|
resolver.resolve(text)
|
||||||
return Response(status=200, data=s.ResolverSerializer(resolver).data)
|
return Response(status=200, data=s.ResolverSerializer(resolver).data)
|
||||||
|
|
||||||
|
# TODO: create a proper file response schema
|
||||||
|
@extend_schema(
|
||||||
|
responses={(200, 'application/zip'): bytes},
|
||||||
|
summary='export as TRS file',
|
||||||
|
tags=['RSForm']
|
||||||
|
)
|
||||||
@action(detail=True, methods=['get'], url_path='export-trs')
|
@action(detail=True, methods=['get'], url_path='export-trs')
|
||||||
def export_trs(self, request, pk):
|
def export_trs(self, request, pk):
|
||||||
''' Endpoint: Download Exteor compatible file. '''
|
''' Endpoint: Download Exteor compatible file. '''
|
||||||
|
@ -274,6 +374,10 @@ class TrsImportView(views.APIView):
|
||||||
''' Endpoint: Upload RS form in Exteor format. '''
|
''' Endpoint: Upload RS form in Exteor format. '''
|
||||||
serializer_class = s.FileSerializer
|
serializer_class = s.FileSerializer
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='import TRS file into RSForm',
|
||||||
|
tags=['RSForm']
|
||||||
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
data = utils.read_trs(request.FILES['file'].file)
|
data = utils.read_trs(request.FILES['file'].file)
|
||||||
owner = self.request.user
|
owner = self.request.user
|
||||||
|
@ -287,6 +391,10 @@ class TrsImportView(views.APIView):
|
||||||
return Response(status=201, data=result.data)
|
return Response(status=201, data=result.data)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='create new RSForm empty or from file',
|
||||||
|
tags=['RSForm']
|
||||||
|
)
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def create_rsform(request):
|
def create_rsform(request):
|
||||||
''' Endpoint: Create RSForm from user input and/or trs file. '''
|
''' Endpoint: Create RSForm from user input and/or trs file. '''
|
||||||
|
@ -334,6 +442,13 @@ def _prepare_rsform_data(data: dict, request, owner: m.User):
|
||||||
is_canonical = request.data['is_canonical'] == 'true'
|
is_canonical = request.data['is_canonical'] == 'true'
|
||||||
data['is_canonical'] = is_canonical
|
data['is_canonical'] = is_canonical
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: define schema for response
|
||||||
|
@extend_schema(
|
||||||
|
request=s.ExpressionSerializer,
|
||||||
|
summary='RS expression into Syntax Tree',
|
||||||
|
tags=['Functions']
|
||||||
|
)
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def parse_expression(request):
|
def parse_expression(request):
|
||||||
''' Endpoint: Parse RS expression. '''
|
''' Endpoint: Parse RS expression. '''
|
||||||
|
@ -344,6 +459,12 @@ def parse_expression(request):
|
||||||
return Response(json.loads(result))
|
return Response(json.loads(result))
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=s.ExpressionSerializer,
|
||||||
|
responses={200: s.ResultTextSerializer},
|
||||||
|
summary='Unicode syntax to ASCII TeX',
|
||||||
|
tags=['Functions']
|
||||||
|
)
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def convert_to_ascii(request):
|
def convert_to_ascii(request):
|
||||||
''' Endpoint: Convert expression to ASCII syntax. '''
|
''' Endpoint: Convert expression to ASCII syntax. '''
|
||||||
|
@ -354,6 +475,12 @@ def convert_to_ascii(request):
|
||||||
return Response({'result': result})
|
return Response({'result': result})
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=s.ExpressionSerializer,
|
||||||
|
responses={200: s.ResultTextSerializer},
|
||||||
|
summary='ASCII TeX syntax to Unicode symbols',
|
||||||
|
tags=['Functions']
|
||||||
|
)
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def convert_to_math(request):
|
def convert_to_math(request):
|
||||||
''' Endpoint: Convert expression to MATH syntax. '''
|
''' Endpoint: Convert expression to MATH syntax. '''
|
||||||
|
|
|
@ -9,6 +9,7 @@ from . import models
|
||||||
|
|
||||||
class LoginSerializer(serializers.Serializer):
|
class LoginSerializer(serializers.Serializer):
|
||||||
''' Serializer: User authentification by login/password. '''
|
''' Serializer: User authentification by login/password. '''
|
||||||
|
# TODO: declare schema
|
||||||
username = serializers.CharField(
|
username = serializers.CharField(
|
||||||
label='Имя пользователя',
|
label='Имя пользователя',
|
||||||
write_only=True
|
write_only=True
|
||||||
|
@ -43,6 +44,7 @@ class LoginSerializer(serializers.Serializer):
|
||||||
|
|
||||||
class AuthSerializer(serializers.Serializer):
|
class AuthSerializer(serializers.Serializer):
|
||||||
''' Serializer: Authentication data. '''
|
''' Serializer: Authentication data. '''
|
||||||
|
# TODO: declare schema
|
||||||
def to_representation(self, instance: models.User) -> dict:
|
def to_representation(self, instance: models.User) -> dict:
|
||||||
if instance.is_anonymous:
|
if instance.is_anonymous:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -3,10 +3,14 @@ from django.contrib.auth import login, logout
|
||||||
|
|
||||||
from rest_framework import status, permissions, views, generics
|
from rest_framework import status, permissions, views, generics
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['Auth'])
|
||||||
|
@extend_schema_view()
|
||||||
class LoginAPIView(views.APIView):
|
class LoginAPIView(views.APIView):
|
||||||
''' Endpoint: Login via username + password. '''
|
''' Endpoint: Login via username + password. '''
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
@ -22,6 +26,8 @@ class LoginAPIView(views.APIView):
|
||||||
return Response(None, status=status.HTTP_202_ACCEPTED)
|
return Response(None, status=status.HTTP_202_ACCEPTED)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['Auth'])
|
||||||
|
@extend_schema_view()
|
||||||
class LogoutAPIView(views.APIView):
|
class LogoutAPIView(views.APIView):
|
||||||
''' Endpoint: Logout. '''
|
''' Endpoint: Logout. '''
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
|
@ -31,12 +37,16 @@ class LogoutAPIView(views.APIView):
|
||||||
return Response(None, status=status.HTTP_204_NO_CONTENT)
|
return Response(None, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['User'])
|
||||||
|
@extend_schema_view()
|
||||||
class SignupAPIView(generics.CreateAPIView):
|
class SignupAPIView(generics.CreateAPIView):
|
||||||
''' Endpoint: Register user. '''
|
''' Endpoint: Register user. '''
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
serializer_class = serializers.SignupSerializer
|
serializer_class = serializers.SignupSerializer
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['Auth'])
|
||||||
|
@extend_schema_view()
|
||||||
class AuthAPIView(generics.RetrieveAPIView):
|
class AuthAPIView(generics.RetrieveAPIView):
|
||||||
''' Endpoint: Current user info. '''
|
''' Endpoint: Current user info. '''
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
@ -46,6 +56,8 @@ class AuthAPIView(generics.RetrieveAPIView):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['User'])
|
||||||
|
@extend_schema_view()
|
||||||
class ActiveUsersView(generics.ListAPIView):
|
class ActiveUsersView(generics.ListAPIView):
|
||||||
''' Endpoint: Get list of active users. '''
|
''' Endpoint: Get list of active users. '''
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
@ -55,6 +67,8 @@ class ActiveUsersView(generics.ListAPIView):
|
||||||
return models.User.objects.filter(is_active=True)
|
return models.User.objects.filter(is_active=True)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['User'])
|
||||||
|
@extend_schema_view()
|
||||||
class UserProfileAPIView(generics.RetrieveUpdateAPIView):
|
class UserProfileAPIView(generics.RetrieveUpdateAPIView):
|
||||||
''' Endpoint: User profile. '''
|
''' Endpoint: User profile. '''
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
|
@ -64,6 +78,8 @@ class UserProfileAPIView(generics.RetrieveUpdateAPIView):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['Auth'])
|
||||||
|
@extend_schema_view()
|
||||||
class UpdatePassword(views.APIView):
|
class UpdatePassword(views.APIView):
|
||||||
''' Endpoint: Change password for current user. '''
|
''' Endpoint: Change password for current user. '''
|
||||||
permission_classes = (permissions.IsAuthenticated, )
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
warn_return_any = True
|
warn_return_any = True
|
||||||
warn_unused_configs = True
|
warn_unused_configs = True
|
||||||
|
|
||||||
plugins = mypy_drf_plugin.main, mypy_django_plugin.main
|
plugins = mypy_django_plugin.main
|
||||||
|
|
||||||
# Per-module options:
|
# Per-module options:
|
||||||
[mypy.plugins.django-stubs]
|
[mypy.plugins.django-stubs]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""
|
'''
|
||||||
Django settings for project.
|
Django settings for project.
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 4.1.7.
|
Generated by 'django-admin startproject' using Django 4.1.7.
|
||||||
|
@ -8,7 +8,7 @@ https://docs.djangoproject.com/en/4.1/topics/settings/
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/4.1/ref/settings/
|
https://docs.djangoproject.com/en/4.1/ref/settings/
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
@ -27,10 +27,7 @@ SECRET_KEY = os.environ.get('SECRET_KEY', 'not-a-secret')
|
||||||
DEBUG = os.environ.get('DEBUG', True) in [True, 'True', '1']
|
DEBUG = os.environ.get('DEBUG', True) in [True, 'True', '1']
|
||||||
|
|
||||||
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '*').split(';')
|
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '*').split(';')
|
||||||
|
INTERNAL_IPS = ['127.0.0.1'] if DEBUG else []
|
||||||
INTERNAL_IPS = [
|
|
||||||
"127.0.0.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
@ -47,12 +44,14 @@ INSTALLED_APPS = [
|
||||||
|
|
||||||
'apps.users',
|
'apps.users',
|
||||||
'apps.rsform',
|
'apps.rsform',
|
||||||
|
|
||||||
|
'drf_spectacular',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
|
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
|
||||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
|
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
],
|
],
|
||||||
|
@ -64,7 +63,7 @@ REST_FRAMEWORK = {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# CORS_ORIGIN_ALLOW_ALL = True
|
|
||||||
CORS_ALLOW_CREDENTIALS = True
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
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(';')
|
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000').split(';')
|
||||||
|
@ -74,8 +73,6 @@ if _domain != '':
|
||||||
CSRF_COOKIE_DOMAIN = _domain
|
CSRF_COOKIE_DOMAIN = _domain
|
||||||
SESSION_COOKIE_DOMAIN = _domain
|
SESSION_COOKIE_DOMAIN = _domain
|
||||||
|
|
||||||
# CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
|
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
@ -93,6 +90,16 @@ LOGIN_URL = '/admin/login/'
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
LOGOUT_REDIRECT_URL = '/'
|
LOGOUT_REDIRECT_URL = '/'
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/4.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_ROOT = os.environ.get('STATIC_ROOT', os.path.join(BASE_DIR, 'static'))
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
MEDIA_ROOT = os.environ.get('MEDIA_ROOT', os.path.join(BASE_DIR, 'media'))
|
||||||
|
MEDIA_URL = 'media/'
|
||||||
|
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
@ -126,6 +133,15 @@ DATABASES = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# drf-spectacular settings. API docs generator
|
||||||
|
# https://drf-spectacular.readthedocs.io/en/latest/settings.html
|
||||||
|
SPECTACULAR_SETTINGS = {
|
||||||
|
'TITLE': 'ConceptPortal API',
|
||||||
|
'DESCRIPTION': 'Портал для работы с экспликациями концептуальных схем',
|
||||||
|
'VERSION': '0.1.0',
|
||||||
|
'SERVE_INCLUDE_SCHEMA': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
|
||||||
|
@ -156,14 +172,6 @@ USE_I18N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/4.1/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_ROOT = os.environ.get('STATIC_ROOT', os.path.join(BASE_DIR, 'static'))
|
|
||||||
STATIC_URL = 'static/'
|
|
||||||
MEDIA_ROOT = os.environ.get('MEDIA_ROOT', os.path.join(BASE_DIR, 'media'))
|
|
||||||
MEDIA_URL = 'media/'
|
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
''' Main URL router '''
|
''' Main URL router '''
|
||||||
from rest_framework.documentation import include_docs_urls
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin', admin.site.urls),
|
||||||
path('api/', include('apps.rsform.urls')),
|
path('api/', include('apps.rsform.urls')),
|
||||||
path('users/', include('apps.users.urls')),
|
path('users/', include('apps.users.urls')),
|
||||||
path('docs/', include_docs_urls(title='ConceptPortal API'), name='docs'),
|
path('docs', SpectacularSwaggerView.as_view(), name='docs'),
|
||||||
path('', lambda request: redirect('docs/', permanent=True)),
|
path('schema', SpectacularAPIView.as_view(), name='schema'),
|
||||||
|
path('redoc', SpectacularRedocView.as_view()),
|
||||||
|
path('', lambda: redirect('docs', permanent=True)),
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
|
@ -3,6 +3,7 @@ django
|
||||||
djangorestframework
|
djangorestframework
|
||||||
django-cors-headers
|
django-cors-headers
|
||||||
django-filter
|
django-filter
|
||||||
|
drf-spectacular
|
||||||
coreapi
|
coreapi
|
||||||
pymorphy2
|
pymorphy2
|
||||||
pymorphy2-dicts-ru
|
pymorphy2-dicts-ru
|
||||||
|
|
|
@ -3,6 +3,7 @@ django
|
||||||
djangorestframework
|
djangorestframework
|
||||||
django-cors-headers
|
django-cors-headers
|
||||||
django-filter
|
django-filter
|
||||||
|
drf-spectacular
|
||||||
coreapi
|
coreapi
|
||||||
pymorphy2
|
pymorphy2
|
||||||
pymorphy2-dicts-ru
|
pymorphy2-dicts-ru
|
||||||
|
@ -12,5 +13,4 @@ razdel
|
||||||
mypy
|
mypy
|
||||||
pylint
|
pylint
|
||||||
coverage
|
coverage
|
||||||
django-stubs[compatible-mypy]
|
|
||||||
djangorestframework-stubs[compatible-mypy]
|
djangorestframework-stubs[compatible-mypy]
|
|
@ -1,80 +1,52 @@
|
||||||
// Module: Natural language model declarations.
|
// Module: Natural language model declarations.
|
||||||
|
|
||||||
|
/**
|
||||||
// ====== Morphology ========
|
* Represents single unit of language Morphology.
|
||||||
|
*/
|
||||||
export enum Grammeme {
|
export enum Grammeme {
|
||||||
// Неизвестная граммема
|
// Неизвестная граммема
|
||||||
UNKN = 'UNKN',
|
UNKN = 'UNKN',
|
||||||
|
|
||||||
// Части речи
|
// Части речи
|
||||||
NOUN = 'NOUN',
|
NOUN = 'NOUN', ADJF = 'ADJF', ADJS = 'ADJS', COMP = 'COMP',
|
||||||
ADJF = 'ADJF',
|
VERB = 'VERB', INFN = 'INFN', PRTF = 'PRTF', PRTS = 'PRTS',
|
||||||
ADJS = 'ADJS',
|
GRND = 'GRND', NUMR = 'NUMR', ADVB = 'ADVB', NPRO = 'NPRO',
|
||||||
COMP = 'COMP',
|
PRED = 'PRED', PREP = 'PREP', CONJ = 'CONJ', PRCL = 'PRCL',
|
||||||
VERB = 'VERB',
|
|
||||||
INFN = 'INFN',
|
|
||||||
PRTF = 'PRTF',
|
|
||||||
PRTS = 'PRTS',
|
|
||||||
GRND = 'GRND',
|
|
||||||
NUMR = 'NUMR',
|
|
||||||
ADVB = 'ADVB',
|
|
||||||
NPRO = 'NPRO',
|
|
||||||
PRED = 'PRED',
|
|
||||||
PREP = 'PREP',
|
|
||||||
CONJ = 'CONJ',
|
|
||||||
PRCL = 'PRCL',
|
|
||||||
INTJ = 'INTJ',
|
INTJ = 'INTJ',
|
||||||
|
|
||||||
// Одушевленность
|
// Одушевленность
|
||||||
anim = 'anim',
|
anim = 'anim', inan = 'inan',
|
||||||
inan = 'inan',
|
|
||||||
|
|
||||||
// Род
|
// Род
|
||||||
masc = 'masc',
|
masc = 'masc', femn = 'femn', neut = 'neut',
|
||||||
femn = 'femn',
|
|
||||||
neut = 'neut',
|
|
||||||
|
|
||||||
// Число
|
// Число
|
||||||
sing = 'sing',
|
sing = 'sing', plur = 'plur',
|
||||||
plur = 'plur',
|
|
||||||
|
|
||||||
// Падеж (основные)
|
// Падеж (основные)
|
||||||
nomn = 'nomn',
|
nomn = 'nomn', gent = 'gent', datv = 'datv',
|
||||||
gent = 'gent',
|
accs = 'accs', ablt = 'ablt', loct = 'loct',
|
||||||
datv = 'datv',
|
|
||||||
accs = 'accs',
|
|
||||||
ablt = 'ablt',
|
|
||||||
loct = 'loct',
|
|
||||||
|
|
||||||
// Совершенный / несовершенный вид
|
// Совершенный / несовершенный вид
|
||||||
perf = 'perf',
|
perf = 'perf', impf = 'impf',
|
||||||
impf = 'impf',
|
|
||||||
|
|
||||||
// Переходность
|
// Переходность
|
||||||
tran = 'tran',
|
tran = 'tran', intr = 'intr',
|
||||||
intr = 'intr',
|
|
||||||
|
|
||||||
// Время
|
// Время
|
||||||
pres = 'pres',
|
pres = 'pres', past = 'past', futr = 'futr',
|
||||||
past = 'past',
|
|
||||||
futr = 'futr',
|
|
||||||
|
|
||||||
// Лицо
|
// Лицо
|
||||||
per1 = '1per',
|
per1 = '1per', per2 = '2per', per3 = '3per',
|
||||||
per2 = '2per',
|
|
||||||
per3 = '3per',
|
|
||||||
|
|
||||||
// Наклонение
|
// Наклонение
|
||||||
indc = 'indc',
|
indc = 'indc', impr = 'impr',
|
||||||
impr = 'impr',
|
|
||||||
|
|
||||||
// Включение говорящего в действие
|
// Включение говорящего в действие
|
||||||
incl = 'incl',
|
incl = 'incl', excl = 'excl',
|
||||||
excl = 'excl',
|
|
||||||
|
|
||||||
// Залог
|
// Залог
|
||||||
actv = 'actv',
|
actv = 'actv', pssv = 'pssv',
|
||||||
pssv = 'pssv',
|
|
||||||
|
|
||||||
// Стиль речи
|
// Стиль речи
|
||||||
Infr = 'Infr', // Неформальный
|
Infr = 'Infr', // Неформальный
|
||||||
|
@ -86,6 +58,11 @@ export enum Grammeme {
|
||||||
Abbr = 'Abbr'
|
Abbr = 'Abbr'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents part of speech language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const PartOfSpeech = [
|
export const PartOfSpeech = [
|
||||||
Grammeme.NOUN, Grammeme.ADJF, Grammeme.ADJS, Grammeme.COMP,
|
Grammeme.NOUN, Grammeme.ADJF, Grammeme.ADJS, Grammeme.COMP,
|
||||||
Grammeme.VERB, Grammeme.INFN, Grammeme.PRTF, Grammeme.PRTS,
|
Grammeme.VERB, Grammeme.INFN, Grammeme.PRTF, Grammeme.PRTS,
|
||||||
|
@ -93,40 +70,106 @@ export const PartOfSpeech = [
|
||||||
Grammeme.PREP, Grammeme.CONJ, Grammeme.PRCL, Grammeme.INTJ
|
Grammeme.PREP, Grammeme.CONJ, Grammeme.PRCL, Grammeme.INTJ
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents gender language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Gender = [
|
export const Gender = [
|
||||||
Grammeme.masc, Grammeme.femn, Grammeme.neut
|
Grammeme.masc, Grammeme.femn, Grammeme.neut
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents case language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Case = [
|
export const Case = [
|
||||||
Grammeme.nomn, Grammeme.gent, Grammeme.datv,
|
Grammeme.nomn, Grammeme.gent, Grammeme.datv,
|
||||||
Grammeme.accs, Grammeme.ablt, Grammeme.loct
|
Grammeme.accs, Grammeme.ablt, Grammeme.loct
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents plurality language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Plurality = [ Grammeme.sing, Grammeme.plur ];
|
export const Plurality = [ Grammeme.sing, Grammeme.plur ];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents verb perfectivity language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Perfectivity = [ Grammeme.perf, Grammeme.impf ];
|
export const Perfectivity = [ Grammeme.perf, Grammeme.impf ];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents verb transitivity language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Transitivity = [ Grammeme.tran, Grammeme.intr ];
|
export const Transitivity = [ Grammeme.tran, Grammeme.intr ];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents verb mood language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Mood = [ Grammeme.indc, Grammeme.impr ];
|
export const Mood = [ Grammeme.indc, Grammeme.impr ];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents verb self-inclusion language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Inclusion = [ Grammeme.incl, Grammeme.excl ];
|
export const Inclusion = [ Grammeme.incl, Grammeme.excl ];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents verb voice language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Voice = [ Grammeme.actv, Grammeme.pssv ];
|
export const Voice = [ Grammeme.actv, Grammeme.pssv ];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents verb tense language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Tense = [
|
export const Tense = [
|
||||||
Grammeme.pres,
|
Grammeme.pres,
|
||||||
Grammeme.past,
|
Grammeme.past,
|
||||||
Grammeme.futr
|
Grammeme.futr
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents verb person language concept.
|
||||||
|
*
|
||||||
|
* Implemented as a list of mututally exclusive {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const Person = [
|
export const Person = [
|
||||||
Grammeme.per1,
|
Grammeme.per1,
|
||||||
Grammeme.per2,
|
Grammeme.per2,
|
||||||
Grammeme.per3
|
Grammeme.per3
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents complete list of language concepts.
|
||||||
|
*
|
||||||
|
* Implemented as a list of lists of {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const GrammemeGroups = [
|
export const GrammemeGroups = [
|
||||||
PartOfSpeech, Gender, Case, Plurality, Perfectivity,
|
PartOfSpeech, Gender, Case, Plurality, Perfectivity,
|
||||||
Transitivity, Mood, Inclusion, Voice, Tense, Person
|
Transitivity, Mood, Inclusion, Voice, Tense, Person
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents NOUN-ish list of language concepts.
|
||||||
|
*
|
||||||
|
* Represented concepts can be target of inflection or coalition in a sentence.
|
||||||
|
*
|
||||||
|
* Implemented as a list of lists of {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const NounGrams = [
|
export const NounGrams = [
|
||||||
Grammeme.NOUN, Grammeme.ADJF, Grammeme.ADJS,
|
Grammeme.NOUN, Grammeme.ADJF, Grammeme.ADJS,
|
||||||
...Gender,
|
...Gender,
|
||||||
|
@ -134,6 +177,13 @@ export const NounGrams = [
|
||||||
...Plurality
|
...Plurality
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents VERB-ish list of language concepts.
|
||||||
|
*
|
||||||
|
* Represented concepts can be target of inflection or coalition in a sentence.
|
||||||
|
*
|
||||||
|
* Implemented as a list of lists of {@link Grammeme}s.
|
||||||
|
*/
|
||||||
export const VerbGrams = [
|
export const VerbGrams = [
|
||||||
Grammeme.VERB, Grammeme.INFN, Grammeme.PRTF, Grammeme.PRTS,
|
Grammeme.VERB, Grammeme.INFN, Grammeme.PRTF, Grammeme.PRTS,
|
||||||
...Perfectivity,
|
...Perfectivity,
|
||||||
|
@ -145,18 +195,45 @@ export const VerbGrams = [
|
||||||
...Person
|
...Person
|
||||||
];
|
];
|
||||||
|
|
||||||
// Grammeme parse data
|
/**
|
||||||
|
* Represents {@link Grammeme} parse data.
|
||||||
|
*/
|
||||||
export interface IGramData {
|
export interface IGramData {
|
||||||
type: Grammeme
|
type: Grammeme
|
||||||
data: string
|
data: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equality comparator for IGramData
|
/**
|
||||||
export function matchGrammeme(value: IGramData, test: IGramData): boolean {
|
* Represents specific wordform attached to {@link Grammeme}s.
|
||||||
if (value.type !== test.type) {
|
*/
|
||||||
|
export interface IWordForm {
|
||||||
|
text: string
|
||||||
|
grams: IGramData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equality comparator for {@link IGramData}. Compares text data for unknown grammemes
|
||||||
|
*/
|
||||||
|
export function matchGrammeme(left: IGramData, right: IGramData): boolean {
|
||||||
|
if (left.type !== right.type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return value.type !== Grammeme.UNKN || value.data === test.data;
|
return left.type !== Grammeme.UNKN || left.data === right.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equality comparator for {@link IWordForm}. Compares a set of Grammemes attached to wordforms
|
||||||
|
*/
|
||||||
|
export function matchWordForm(left: IWordForm, right: IWordForm): boolean {
|
||||||
|
if (left.grams.length !== right.grams.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let index = 0; index < left.grams.length; ++index) {
|
||||||
|
if (!matchGrammeme(left.grams[index], right.grams[index])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSingleGrammeme(text: string): IGramData {
|
function parseSingleGrammeme(text: string): IGramData {
|
||||||
|
@ -173,6 +250,18 @@ function parseSingleGrammeme(text: string): IGramData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sortGrammemes<TData extends IGramData>(input: TData[]): TData[] {
|
||||||
|
const result: TData[] = [];
|
||||||
|
Object.values(Grammeme).forEach(
|
||||||
|
gram => {
|
||||||
|
const item = input.find(data => data.type === gram);
|
||||||
|
if (item) {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function parseGrammemes(termForm: string): IGramData[] {
|
export function parseGrammemes(termForm: string): IGramData[] {
|
||||||
const result: IGramData[] = [];
|
const result: IGramData[] = [];
|
||||||
const chunks = termForm.split(',');
|
const chunks = termForm.split(',');
|
||||||
|
@ -182,12 +271,7 @@ export function parseGrammemes(termForm: string): IGramData[] {
|
||||||
result.push(parseSingleGrammeme(chunk));
|
result.push(parseSingleGrammeme(chunk));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return sortGrammemes(result);
|
||||||
}
|
|
||||||
|
|
||||||
export interface IWordForm {
|
|
||||||
text: string
|
|
||||||
grams: IGramData[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== Reference resolution =====
|
// ====== Reference resolution =====
|
||||||
|
|
|
@ -7,8 +7,14 @@ import SelectMulti from '../../components/Common/SelectMulti';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../components/Common/TextArea';
|
||||||
import DataTable, { createColumnHelper } from '../../components/DataTable';
|
import DataTable, { createColumnHelper } from '../../components/DataTable';
|
||||||
import { CheckIcon, ChevronDoubleUpIcon, ChevronUpIcon, CrossIcon } from '../../components/Icons';
|
import { CheckIcon, ChevronDoubleUpIcon, ChevronUpIcon, CrossIcon } from '../../components/Icons';
|
||||||
import { Grammeme, GrammemeGroups, IWordForm, NounGrams, parseGrammemes,VerbGrams } from '../../models/language';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
import {
|
||||||
|
Grammeme, GrammemeGroups, IWordForm,
|
||||||
|
matchWordForm, NounGrams, parseGrammemes,
|
||||||
|
sortGrammemes, VerbGrams
|
||||||
|
} from '../../models/language';
|
||||||
import { IConstituenta, TermForm } from '../../models/rsform';
|
import { IConstituenta, TermForm } from '../../models/rsform';
|
||||||
|
import { colorfgGrammeme } from '../../utils/color';
|
||||||
import { labelGrammeme } from '../../utils/labels';
|
import { labelGrammeme } from '../../utils/labels';
|
||||||
import { IGrammemeOption, SelectorGrammems } from '../../utils/selectors';
|
import { IGrammemeOption, SelectorGrammems } from '../../utils/selectors';
|
||||||
|
|
||||||
|
@ -21,6 +27,7 @@ interface DlgEditTermProps {
|
||||||
const columnHelper = createColumnHelper<IWordForm>();
|
const columnHelper = createColumnHelper<IWordForm>();
|
||||||
|
|
||||||
function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
|
const { colors } = useConceptTheme();
|
||||||
const [term, setTerm] = useState('');
|
const [term, setTerm] = useState('');
|
||||||
|
|
||||||
const [inputText, setInputText] = useState('');
|
const [inputText, setInputText] = useState('');
|
||||||
|
@ -50,55 +57,73 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
}));
|
}));
|
||||||
setForms(initForms);
|
setForms(initForms);
|
||||||
setTerm(target.term_resolved);
|
setTerm(target.term_resolved);
|
||||||
|
setInputText(target.term_resolved);
|
||||||
}, [target]);
|
}, [target]);
|
||||||
|
|
||||||
// Filter grammemes when input changes
|
// Filter grammemes when input changes
|
||||||
useEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
let newFilter: Grammeme[] = [];
|
let newFilter: Grammeme[] = [];
|
||||||
inputGrams.forEach(({value: gram}) => {
|
inputGrams.forEach(({type: gram}) => {
|
||||||
if (!newFilter.includes(gram)) {
|
if (!newFilter.includes(gram)) {
|
||||||
if (NounGrams.includes(gram)) {
|
if (NounGrams.includes(gram)) {
|
||||||
newFilter.push(...NounGrams);
|
newFilter.push(...NounGrams);
|
||||||
}
|
}
|
||||||
if (VerbGrams.includes(gram)) {
|
if (VerbGrams.includes(gram)) {
|
||||||
newFilter.push(...NounGrams);
|
newFilter.push(...VerbGrams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
inputGrams.forEach(({value: gram}) =>
|
inputGrams.forEach(({type: gram}) =>
|
||||||
GrammemeGroups.forEach(group => {
|
GrammemeGroups.forEach(group => {
|
||||||
if (group.includes(gram)) {
|
if (group.includes(gram)) {
|
||||||
newFilter = newFilter.filter(item => !group.includes(item) || item === gram);
|
newFilter = newFilter.filter(item => !group.includes(item) || item === gram);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
newFilter.push(...inputGrams.map(({value: gram}) => gram));
|
newFilter.push(...inputGrams.map(({type: gram}) => gram));
|
||||||
if (newFilter.length === 0) {
|
if (newFilter.length === 0) {
|
||||||
newFilter = [...VerbGrams, ...NounGrams];
|
newFilter = [...VerbGrams, ...NounGrams];
|
||||||
}
|
}
|
||||||
|
|
||||||
newFilter = [... new Set(newFilter)];
|
newFilter = [... new Set(newFilter)];
|
||||||
setOptions(SelectorGrammems.filter(({value: gram}) => newFilter.includes(gram)));
|
setOptions(SelectorGrammems.filter(({type: gram}) => newFilter.includes(gram)));
|
||||||
}, [inputGrams]);
|
}, [inputGrams]);
|
||||||
|
|
||||||
const handleSubmit = () => onSave(getData());
|
const handleSubmit = () => onSave(getData());
|
||||||
|
|
||||||
function handleAddForm() {
|
function handleAddForm() {
|
||||||
|
const newForm: IWordForm = {
|
||||||
|
text: inputText,
|
||||||
|
grams: inputGrams.map(item => ({
|
||||||
|
type: item.type,
|
||||||
|
data: item.data
|
||||||
|
}))
|
||||||
|
};
|
||||||
setForms(forms => [
|
setForms(forms => [
|
||||||
...forms,
|
...forms.filter(value => !matchWordForm(value, newForm)),
|
||||||
{
|
newForm
|
||||||
text: inputText,
|
|
||||||
grams: inputGrams.map(item => ({
|
|
||||||
type: item.value, data: item.value as string
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleResetForm() {
|
function handleDeleteRow(row: number) {
|
||||||
|
setForms(
|
||||||
|
(prev) => {
|
||||||
|
const newForms: IWordForm[] = [];
|
||||||
|
prev.forEach(
|
||||||
|
(form, index) => {
|
||||||
|
if (index !== row) {
|
||||||
|
newForms.push(form);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newForms;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResetForm() {
|
||||||
|
setInputText('');
|
||||||
|
setInputGrams([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGenerateSelected() {
|
function handleGenerateSelected() {
|
||||||
|
@ -106,6 +131,11 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGenerateBasics() {
|
function handleGenerateBasics() {
|
||||||
|
if (forms.length > 0) {
|
||||||
|
if (!window.confirm('Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +146,8 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
header: 'Текст',
|
header: 'Текст',
|
||||||
size: 350,
|
size: 350,
|
||||||
minSize: 350,
|
minSize: 350,
|
||||||
maxSize: 350
|
maxSize: 350,
|
||||||
|
cell: props => <div className='min-w-[20rem]'>{props.getValue()}</div>
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('grams', {
|
columnHelper.accessor('grams', {
|
||||||
id: 'grams',
|
id: 'grams',
|
||||||
|
@ -124,41 +155,42 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
size: 250,
|
size: 250,
|
||||||
minSize: 250,
|
minSize: 250,
|
||||||
maxSize: 250,
|
maxSize: 250,
|
||||||
cell: props => {
|
cell: props =>
|
||||||
return (
|
|
||||||
<div className='flex justify-start gap-1 select-none'>
|
<div className='flex justify-start gap-1 select-none'>
|
||||||
{ props.getValue().map(
|
{ props.getValue().map(
|
||||||
data => (<>
|
gram =>
|
||||||
<div
|
<div
|
||||||
className='min-w-[3rem] px-1 text-center rounded-md whitespace-nowrap border clr-border clr-input'
|
className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap'
|
||||||
title=''
|
title=''
|
||||||
// style={{
|
style={{
|
||||||
// borderWidth: '1px',
|
borderWidth: '1px',
|
||||||
// borderColor: getCstStatusFgColor(cst.status, colors),
|
borderColor: colorfgGrammeme(gram.type, colors),
|
||||||
// color: getCstStatusFgColor(cst.status, colors),
|
color: colorfgGrammeme(gram.type, colors),
|
||||||
// fontWeight: 600,
|
fontWeight: 600,
|
||||||
// backgroundColor: isMockCst(cst) ? colors.bgWarning : colors.bgInput
|
backgroundColor: colors.bgInput
|
||||||
// }}
|
}}
|
||||||
>
|
>
|
||||||
{labelGrammeme(data)}
|
{labelGrammeme(gram)}
|
||||||
</div>
|
</div>
|
||||||
{/* <ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_list}${cst.alias}`} /> */}
|
)}
|
||||||
</>))}
|
</div>
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
// cell: props =>
|
|
||||||
// <div style={{
|
|
||||||
// fontSize: 12,
|
|
||||||
// color: isMockCst(props.row.original) ? colors.fgWarning : undefined
|
|
||||||
// }}>
|
|
||||||
// {props.getValue()}
|
|
||||||
// </div>
|
|
||||||
}),
|
}),
|
||||||
// columnHelper.accessor(, {
|
columnHelper.display({
|
||||||
|
id: 'actions',
|
||||||
// })
|
size: 50,
|
||||||
], []);
|
minSize: 50,
|
||||||
|
maxSize: 50,
|
||||||
|
cell: props =>
|
||||||
|
<div>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Удалить словоформу'
|
||||||
|
icon={<CrossIcon size={4} color='text-warning'/>}
|
||||||
|
noHover
|
||||||
|
onClick={() => handleDeleteRow(props.row.index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
], [colors]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -193,7 +225,8 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
<div className='flex items-center justify-start'>
|
<div className='flex items-center justify-start'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Добавить словоформу'
|
tooltip='Добавить словоформу'
|
||||||
icon={<CheckIcon size={6} color='text-success'/>}
|
icon={<CheckIcon size={6} color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'text-success'}/>}
|
||||||
|
disabled={!inputText || inputGrams.length == 0}
|
||||||
onClick={handleAddForm}
|
onClick={handleAddForm}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
|
@ -203,7 +236,8 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Генерировать словоформу'
|
tooltip='Генерировать словоформу'
|
||||||
icon={<ChevronUpIcon size={6} color='text-primary'/>}
|
icon={<ChevronUpIcon size={6} color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'}/>}
|
||||||
|
disabled={inputGrams.length == 0}
|
||||||
onClick={handleGenerateSelected}
|
onClick={handleGenerateSelected}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
|
@ -219,11 +253,11 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
placeholder='Выберите граммемы'
|
placeholder='Выберите граммемы'
|
||||||
|
|
||||||
value={inputGrams}
|
value={inputGrams}
|
||||||
onChange={data => setInputGrams(data.map(value => value))}
|
onChange={newValue => setInputGrams(sortGrammemes([...newValue]))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='border overflow-y-auto max-h-[20rem]'>
|
<div className='border overflow-y-auto max-h-[17.4rem] min-h-[17.4rem]'>
|
||||||
<DataTable
|
<DataTable
|
||||||
data={forms}
|
data={forms}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
@ -232,6 +266,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
noDataComponent={
|
noDataComponent={
|
||||||
<span className='flex flex-col justify-center p-2 text-center min-h-[2rem]'>
|
<span className='flex flex-col justify-center p-2 text-center min-h-[2rem]'>
|
||||||
<p>Список пуст</p>
|
<p>Список пуст</p>
|
||||||
|
<p>Добавьте словоформу</p>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// =========== Modules contains all dynamic color definitions ==========
|
// =========== Modules contains all dynamic color definitions ==========
|
||||||
|
|
||||||
|
import { Grammeme, NounGrams, PartOfSpeech, VerbGrams } from '../models/language'
|
||||||
import { CstClass, ExpressionStatus } from '../models/rsform'
|
import { CstClass, ExpressionStatus } from '../models/rsform'
|
||||||
import { ISyntaxTreeNode, TokenID } from '../models/rslang'
|
import { ISyntaxTreeNode, TokenID } from '../models/rslang'
|
||||||
|
|
||||||
|
@ -384,3 +385,29 @@ export function colorbgCstClass(cstClass: CstClass, colors: IColorTheme): string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function colorfgGrammeme(gram: Grammeme, colors: IColorTheme): string {
|
||||||
|
if (PartOfSpeech.includes(gram)) {
|
||||||
|
return colors.fgBlue;
|
||||||
|
}
|
||||||
|
if (NounGrams.includes(gram)) {
|
||||||
|
return colors.fgGreen;
|
||||||
|
}
|
||||||
|
if (VerbGrams.includes(gram)) {
|
||||||
|
return colors.fgTeal;
|
||||||
|
}
|
||||||
|
return colors.fgDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function colorbgGrammeme(gram: Grammeme, colors: IColorTheme): string {
|
||||||
|
if (PartOfSpeech.includes(gram)) {
|
||||||
|
return colors.bgBlue;
|
||||||
|
}
|
||||||
|
if (NounGrams.includes(gram)) {
|
||||||
|
return colors.bgGreen;
|
||||||
|
}
|
||||||
|
if (VerbGrams.includes(gram)) {
|
||||||
|
return colors.bgTeal;
|
||||||
|
}
|
||||||
|
return colors.bgInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const urls = {
|
||||||
|
|
||||||
gitrepo: 'https://github.com/IRBorisov/ConceptPortal',
|
gitrepo: 'https://github.com/IRBorisov/ConceptPortal',
|
||||||
mailportal: 'mailto:portal@acconcept.ru',
|
mailportal: 'mailto:portal@acconcept.ru',
|
||||||
restapi: 'https://api.portal.acconcept.ru/docs/'
|
restapi: 'https://api.portal.acconcept.ru/docs'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resources = {
|
export const resources = {
|
||||||
|
|
|
@ -38,7 +38,7 @@ export const SelectorCstType = (
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface IGrammemeOption {
|
export interface IGrammemeOption extends IGramData {
|
||||||
value: Grammeme
|
value: Grammeme
|
||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,8 @@ export const SelectorGrammems: IGrammemeOption[] =
|
||||||
Grammeme.pssv, Grammeme.actv,
|
Grammeme.pssv, Grammeme.actv,
|
||||||
].map(
|
].map(
|
||||||
gram => ({
|
gram => ({
|
||||||
|
type: gram,
|
||||||
|
data: gram as string,
|
||||||
value: gram,
|
value: gram,
|
||||||
label: labelGrammeme({type: gram, data: ''} as IGramData)
|
label: labelGrammeme({type: gram, data: ''} as IGramData)
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in New Issue
Block a user