Compare commits
4 Commits
6d7183620e
...
118c5459f3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
118c5459f3 | ||
![]() |
3cb769a1d4 | ||
![]() |
0c8577e913 | ||
![]() |
c6b192b841 |
1
TODO.txt
1
TODO.txt
|
@ -68,7 +68,6 @@ https://stackoverflow.com/questions/28838170/multilevel-json-diff-in-python
|
||||||
- shadcn-ui
|
- shadcn-ui
|
||||||
|
|
||||||
- Zod
|
- Zod
|
||||||
- use-debounce
|
|
||||||
|
|
||||||
- react-query
|
- react-query
|
||||||
- react-hook-form
|
- react-hook-form
|
|
@ -28,15 +28,6 @@ class LibraryTemplateAdmin(admin.ModelAdmin):
|
||||||
return 'N/A'
|
return 'N/A'
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionAdmin(admin.ModelAdmin):
|
|
||||||
''' Admin model: Subscriptions. '''
|
|
||||||
list_display = ['id', 'item', 'user']
|
|
||||||
search_fields = [
|
|
||||||
'item__title', 'item__alias',
|
|
||||||
'user__username', 'user__first_name', 'user__last_name'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EditorAdmin(admin.ModelAdmin):
|
class EditorAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Editors. '''
|
''' Admin model: Editors. '''
|
||||||
list_display = ['id', 'item', 'editor']
|
list_display = ['id', 'item', 'editor']
|
||||||
|
@ -57,6 +48,5 @@ class VersionAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
admin.site.register(models.LibraryItem, LibraryItemAdmin)
|
admin.site.register(models.LibraryItem, LibraryItemAdmin)
|
||||||
admin.site.register(models.LibraryTemplate, LibraryTemplateAdmin)
|
admin.site.register(models.LibraryTemplate, LibraryTemplateAdmin)
|
||||||
admin.site.register(models.Subscription, SubscriptionAdmin)
|
|
||||||
admin.site.register(models.Version, VersionAdmin)
|
admin.site.register(models.Version, VersionAdmin)
|
||||||
admin.site.register(models.Editor, EditorAdmin)
|
admin.site.register(models.Editor, EditorAdmin)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Generated by Django 5.1 on 2024-08-20 11:42
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('library', '0003_alter_librarytemplate_lib_source'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Subscription',
|
||||||
|
),
|
||||||
|
]
|
|
@ -112,10 +112,6 @@ class LibraryItem(Model):
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return f'/api/library/{self.pk}'
|
return f'/api/library/{self.pk}'
|
||||||
|
|
||||||
def subscribers(self) -> QuerySet[User]:
|
|
||||||
''' Get all subscribers for this item. '''
|
|
||||||
return User.objects.filter(subscription__item=self.pk)
|
|
||||||
|
|
||||||
def editors(self) -> QuerySet[User]:
|
def editors(self) -> QuerySet[User]:
|
||||||
''' Get all Editors of this item. '''
|
''' Get all Editors of this item. '''
|
||||||
return User.objects.filter(editor__item=self.pk)
|
return User.objects.filter(editor__item=self.pk)
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
''' Models: Subscription. '''
|
|
||||||
from django.db.models import CASCADE, ForeignKey, Model
|
|
||||||
|
|
||||||
from apps.users.models import User
|
|
||||||
|
|
||||||
|
|
||||||
class Subscription(Model):
|
|
||||||
''' User subscription to library item. '''
|
|
||||||
user: ForeignKey = ForeignKey(
|
|
||||||
verbose_name='Пользователь',
|
|
||||||
to=User,
|
|
||||||
on_delete=CASCADE
|
|
||||||
)
|
|
||||||
item: ForeignKey = ForeignKey(
|
|
||||||
verbose_name='Элемент',
|
|
||||||
to='library.LibraryItem',
|
|
||||||
on_delete=CASCADE
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
''' Model metadata. '''
|
|
||||||
verbose_name = 'Подписка'
|
|
||||||
verbose_name_plural = 'Подписки'
|
|
||||||
unique_together = [['user', 'item']]
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f'{self.user} -> {self.item}'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def subscribe(user: int, item: int) -> bool:
|
|
||||||
''' Add subscription. '''
|
|
||||||
if Subscription.objects.filter(user_id=user, item_id=item).exists():
|
|
||||||
return False
|
|
||||||
Subscription.objects.create(user_id=user, item_id=item)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def unsubscribe(user: int, item: int) -> bool:
|
|
||||||
''' Remove subscription. '''
|
|
||||||
sub = Subscription.objects.filter(user_id=user, item_id=item).only('pk')
|
|
||||||
if not sub.exists():
|
|
||||||
return False
|
|
||||||
sub.delete()
|
|
||||||
return True
|
|
|
@ -3,5 +3,4 @@
|
||||||
from .Editor import Editor
|
from .Editor import Editor
|
||||||
from .LibraryItem import AccessPolicy, LibraryItem, LibraryItemType, LocationHead, validate_location
|
from .LibraryItem import AccessPolicy, LibraryItem, LibraryItemType, LocationHead, validate_location
|
||||||
from .LibraryTemplate import LibraryTemplate
|
from .LibraryTemplate import LibraryTemplate
|
||||||
from .Subscription import Subscription
|
|
||||||
from .Version import Version
|
from .Version import Version
|
||||||
|
|
|
@ -72,7 +72,6 @@ class VersionCreateSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: LibraryItem detailed data. '''
|
''' Serializer: LibraryItem detailed data. '''
|
||||||
subscribers = serializers.SerializerMethodField()
|
|
||||||
editors = serializers.SerializerMethodField()
|
editors = serializers.SerializerMethodField()
|
||||||
versions = serializers.SerializerMethodField()
|
versions = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@ -82,9 +81,6 @@ class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = ('owner', 'id', 'item_type')
|
read_only_fields = ('owner', 'id', 'item_type')
|
||||||
|
|
||||||
def get_subscribers(self, instance: LibraryItem) -> list[int]:
|
|
||||||
return list(instance.subscribers().values_list('pk', flat=True))
|
|
||||||
|
|
||||||
def get_editors(self, instance: LibraryItem) -> list[int]:
|
def get_editors(self, instance: LibraryItem) -> list[int]:
|
||||||
return list(instance.editors().values_list('pk', flat=True))
|
return list(instance.editors().values_list('pk', flat=True))
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
''' Tests for Django Models. '''
|
''' Tests for Django Models. '''
|
||||||
from .t_Editor import *
|
from .t_Editor import *
|
||||||
from .t_LibraryItem import *
|
from .t_LibraryItem import *
|
||||||
from .t_Subscription import *
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ from apps.library.models import (
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
LibraryItemType,
|
LibraryItemType,
|
||||||
LocationHead,
|
LocationHead,
|
||||||
Subscription,
|
|
||||||
validate_location
|
validate_location
|
||||||
)
|
)
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
''' Testing models: Subscription. '''
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from apps.library.models import LibraryItem, LibraryItemType, Subscription
|
|
||||||
from apps.users.models import User
|
|
||||||
|
|
||||||
|
|
||||||
class TestSubscription(TestCase):
|
|
||||||
''' Testing Subscription model. '''
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.user1 = User.objects.create(username='User1')
|
|
||||||
self.user2 = User.objects.create(username='User2')
|
|
||||||
self.item = LibraryItem.objects.create(
|
|
||||||
item_type=LibraryItemType.RSFORM,
|
|
||||||
title='Test',
|
|
||||||
alias='КС1',
|
|
||||||
owner=self.user1
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_default(self):
|
|
||||||
subs = list(Subscription.objects.filter(item=self.item))
|
|
||||||
self.assertEqual(len(subs), 0)
|
|
||||||
|
|
||||||
|
|
||||||
def test_str(self):
|
|
||||||
testStr = 'User2 -> КС1'
|
|
||||||
item = Subscription.objects.create(
|
|
||||||
user=self.user2,
|
|
||||||
item=self.item
|
|
||||||
)
|
|
||||||
self.assertEqual(str(item), testStr)
|
|
||||||
|
|
||||||
|
|
||||||
def test_subscribe(self):
|
|
||||||
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test')
|
|
||||||
self.assertEqual(item.subscribers().count(), 0)
|
|
||||||
|
|
||||||
self.assertTrue(Subscription.subscribe(self.user1.pk, item.pk))
|
|
||||||
self.assertEqual(item.subscribers().count(), 1)
|
|
||||||
self.assertTrue(self.user1 in item.subscribers())
|
|
||||||
|
|
||||||
self.assertFalse(Subscription.subscribe(self.user1.pk, item.pk))
|
|
||||||
self.assertEqual(item.subscribers().count(), 1)
|
|
||||||
|
|
||||||
self.assertTrue(Subscription.subscribe(self.user2.pk, item.pk))
|
|
||||||
self.assertEqual(item.subscribers().count(), 2)
|
|
||||||
self.assertTrue(self.user1 in item.subscribers())
|
|
||||||
self.assertTrue(self.user2 in item.subscribers())
|
|
||||||
|
|
||||||
self.user1.delete()
|
|
||||||
self.assertEqual(item.subscribers().count(), 1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_unsubscribe(self):
|
|
||||||
item = LibraryItem.objects.create(item_type=LibraryItemType.RSFORM, title='Test')
|
|
||||||
self.assertFalse(Subscription.unsubscribe(self.user1.pk, item.pk))
|
|
||||||
Subscription.subscribe(self.user1.pk, item.pk)
|
|
||||||
Subscription.subscribe(self.user2.pk, item.pk)
|
|
||||||
self.assertEqual(item.subscribers().count(), 2)
|
|
||||||
|
|
||||||
self.assertTrue(Subscription.unsubscribe(self.user1.pk, item.pk))
|
|
||||||
self.assertEqual(item.subscribers().count(), 1)
|
|
||||||
self.assertTrue(self.user2 in item.subscribers())
|
|
||||||
|
|
||||||
self.assertFalse(Subscription.unsubscribe(self.user1.pk, item.pk))
|
|
|
@ -7,8 +7,7 @@ from apps.library.models import (
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
LibraryItemType,
|
LibraryItemType,
|
||||||
LibraryTemplate,
|
LibraryTemplate,
|
||||||
LocationHead,
|
LocationHead
|
||||||
Subscription
|
|
||||||
)
|
)
|
||||||
from apps.rsform.models import RSForm
|
from apps.rsform.models import RSForm
|
||||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||||
|
@ -49,7 +48,6 @@ class TestLibraryViewset(EndpointTester):
|
||||||
self.assertEqual(response.data['item_type'], LibraryItemType.RSFORM)
|
self.assertEqual(response.data['item_type'], LibraryItemType.RSFORM)
|
||||||
self.assertEqual(response.data['title'], data['title'])
|
self.assertEqual(response.data['title'], data['title'])
|
||||||
self.assertEqual(response.data['alias'], data['alias'])
|
self.assertEqual(response.data['alias'], data['alias'])
|
||||||
self.assertTrue(Subscription.objects.filter(user=self.user, item_id=response.data['id']).exists())
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'item_type': LibraryItemType.OPERATION_SCHEMA,
|
'item_type': LibraryItemType.OPERATION_SCHEMA,
|
||||||
|
@ -261,38 +259,6 @@ class TestLibraryViewset(EndpointTester):
|
||||||
self.executeForbidden()
|
self.executeForbidden()
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/active', method='get')
|
|
||||||
def test_retrieve_subscribed(self):
|
|
||||||
response = self.executeOK()
|
|
||||||
self.assertFalse(response_contains(response, self.unowned))
|
|
||||||
|
|
||||||
Subscription.subscribe(user=self.user.pk, item=self.unowned.pk)
|
|
||||||
Subscription.subscribe(user=self.user2.pk, item=self.unowned.pk)
|
|
||||||
Subscription.subscribe(user=self.user2.pk, item=self.owned.pk)
|
|
||||||
|
|
||||||
response = self.executeOK()
|
|
||||||
self.assertTrue(response_contains(response, self.unowned))
|
|
||||||
self.assertEqual(len(response.data), 3)
|
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/subscribe', method='post')
|
|
||||||
def test_subscriptions(self):
|
|
||||||
self.executeNotFound(item=self.invalid_item)
|
|
||||||
response = self.client.delete(f'/api/library/{self.unowned.pk}/unsubscribe')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertFalse(self.user in self.unowned.subscribers())
|
|
||||||
|
|
||||||
response = self.executeOK(item=self.unowned.pk)
|
|
||||||
self.assertTrue(self.user in self.unowned.subscribers())
|
|
||||||
|
|
||||||
response = self.executeOK(item=self.unowned.pk)
|
|
||||||
self.assertTrue(self.user in self.unowned.subscribers())
|
|
||||||
|
|
||||||
response = self.client.delete(f'/api/library/{self.unowned.pk}/unsubscribe')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertFalse(self.user in self.unowned.subscribers())
|
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/templates', method='get')
|
@decl_endpoint('/api/library/templates', method='get')
|
||||||
def test_retrieve_templates(self):
|
def test_retrieve_templates(self):
|
||||||
response = self.executeOK()
|
response = self.executeOK()
|
||||||
|
|
|
@ -39,11 +39,9 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
def perform_create(self, serializer) -> None:
|
def perform_create(self, serializer) -> None:
|
||||||
if not self.request.user.is_anonymous and 'owner' not in self.request.POST:
|
if not self.request.user.is_anonymous and 'owner' not in self.request.POST:
|
||||||
instance = serializer.save(owner=self.request.user)
|
serializer.save(owner=self.request.user)
|
||||||
else:
|
else:
|
||||||
instance = serializer.save()
|
serializer.save()
|
||||||
if instance.owner:
|
|
||||||
m.Subscription.subscribe(user=instance.owner_id, item=instance.pk)
|
|
||||||
|
|
||||||
def perform_update(self, serializer) -> None:
|
def perform_update(self, serializer) -> None:
|
||||||
instance = serializer.save()
|
instance = serializer.save()
|
||||||
|
@ -84,9 +82,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
access_level = permissions.ItemOwner
|
access_level = permissions.ItemOwner
|
||||||
elif self.action in [
|
elif self.action in [
|
||||||
'create',
|
'create',
|
||||||
'clone',
|
'clone'
|
||||||
'subscribe',
|
|
||||||
'unsubscribe'
|
|
||||||
]:
|
]:
|
||||||
access_level = permissions.GlobalUser
|
access_level = permissions.GlobalUser
|
||||||
else:
|
else:
|
||||||
|
@ -140,40 +136,6 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
data=RSFormParseSerializer(clone).data
|
data=RSFormParseSerializer(clone).data
|
||||||
)
|
)
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
summary='subscribe to item',
|
|
||||||
tags=['Library'],
|
|
||||||
request=None,
|
|
||||||
responses={
|
|
||||||
c.HTTP_200_OK: None,
|
|
||||||
c.HTTP_403_FORBIDDEN: None,
|
|
||||||
c.HTTP_404_NOT_FOUND: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@action(detail=True, methods=['post'])
|
|
||||||
def subscribe(self, request: Request, pk):
|
|
||||||
''' Endpoint: Subscribe current user to item. '''
|
|
||||||
item = self._get_item()
|
|
||||||
m.Subscription.subscribe(user=cast(int, self.request.user.pk), item=item.pk)
|
|
||||||
return Response(status=c.HTTP_200_OK)
|
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
summary='unsubscribe from item',
|
|
||||||
tags=['Library'],
|
|
||||||
request=None,
|
|
||||||
responses={
|
|
||||||
c.HTTP_200_OK: None,
|
|
||||||
c.HTTP_403_FORBIDDEN: None,
|
|
||||||
c.HTTP_404_NOT_FOUND: None
|
|
||||||
},
|
|
||||||
)
|
|
||||||
@action(detail=True, methods=['delete'])
|
|
||||||
def unsubscribe(self, request: Request, pk) -> HttpResponse:
|
|
||||||
''' Endpoint: Unsubscribe current user from item. '''
|
|
||||||
item = self._get_item()
|
|
||||||
m.Subscription.unsubscribe(user=cast(int, self.request.user.pk), item=item.pk)
|
|
||||||
return Response(status=c.HTTP_200_OK)
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='set owner for item',
|
summary='set owner for item',
|
||||||
tags=['Library'],
|
tags=['Library'],
|
||||||
|
@ -336,8 +298,7 @@ class LibraryActiveView(generics.ListAPIView):
|
||||||
return m.LibraryItem.objects.filter(
|
return m.LibraryItem.objects.filter(
|
||||||
(is_public & common_location) |
|
(is_public & common_location) |
|
||||||
Q(owner=user) |
|
Q(owner=user) |
|
||||||
Q(editor__editor=user) |
|
Q(editor__editor=user)
|
||||||
Q(subscription__user=user)
|
|
||||||
).distinct().order_by('-time_update')
|
).distinct().order_by('-time_update')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -96,9 +96,6 @@ class CstCreateSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class RSFormSerializer(serializers.ModelSerializer):
|
class RSFormSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: Detailed data for RSForm. '''
|
''' Serializer: Detailed data for RSForm. '''
|
||||||
subscribers = serializers.ListField(
|
|
||||||
child=serializers.IntegerField()
|
|
||||||
)
|
|
||||||
editors = serializers.ListField(
|
editors = serializers.ListField(
|
||||||
child=serializers.IntegerField()
|
child=serializers.IntegerField()
|
||||||
)
|
)
|
||||||
|
@ -137,7 +134,6 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
''' Create serializable version representation without redundant data. '''
|
''' Create serializable version representation without redundant data. '''
|
||||||
result = self.to_representation(cast(LibraryItem, self.instance))
|
result = self.to_representation(cast(LibraryItem, self.instance))
|
||||||
del result['versions']
|
del result['versions']
|
||||||
del result['subscribers']
|
|
||||||
del result['editors']
|
del result['editors']
|
||||||
del result['inheritance']
|
del result['inheritance']
|
||||||
del result['oss']
|
del result['oss']
|
||||||
|
@ -199,9 +195,6 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class RSFormParseSerializer(serializers.ModelSerializer):
|
class RSFormParseSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: Detailed data for RSForm including parse. '''
|
''' Serializer: Detailed data for RSForm including parse. '''
|
||||||
subscribers = serializers.ListField(
|
|
||||||
child=serializers.IntegerField()
|
|
||||||
)
|
|
||||||
editors = serializers.ListField(
|
editors = serializers.ListField(
|
||||||
child=serializers.IntegerField()
|
child=serializers.IntegerField()
|
||||||
)
|
)
|
||||||
|
|
|
@ -100,7 +100,6 @@ class TestRSFormViewset(EndpointTester):
|
||||||
self.assertEqual(response.data['items'][1]['id'], x2.pk)
|
self.assertEqual(response.data['items'][1]['id'], x2.pk)
|
||||||
self.assertEqual(response.data['items'][1]['term_raw'], x2.term_raw)
|
self.assertEqual(response.data['items'][1]['term_raw'], x2.term_raw)
|
||||||
self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved)
|
self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved)
|
||||||
self.assertEqual(response.data['subscribers'], [])
|
|
||||||
self.assertEqual(response.data['editors'], [])
|
self.assertEqual(response.data['editors'], [])
|
||||||
self.assertEqual(response.data['inheritance'], [])
|
self.assertEqual(response.data['inheritance'], [])
|
||||||
self.assertEqual(response.data['oss'], [])
|
self.assertEqual(response.data['oss'], [])
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth.password_validation import validate_password
|
from django.contrib.auth.password_validation import validate_password
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from apps.library.models import Editor, Subscription
|
from apps.library.models import Editor
|
||||||
from shared import messages as msg
|
from shared import messages as msg
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
@ -59,9 +59,6 @@ class AuthSerializer(serializers.Serializer):
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
username = serializers.CharField()
|
username = serializers.CharField()
|
||||||
is_staff = serializers.BooleanField()
|
is_staff = serializers.BooleanField()
|
||||||
subscriptions = serializers.ListField(
|
|
||||||
child=serializers.IntegerField()
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_representation(self, instance: models.User) -> dict:
|
def to_representation(self, instance: models.User) -> dict:
|
||||||
if instance.is_anonymous:
|
if instance.is_anonymous:
|
||||||
|
@ -69,7 +66,6 @@ class AuthSerializer(serializers.Serializer):
|
||||||
'id': None,
|
'id': None,
|
||||||
'username': '',
|
'username': '',
|
||||||
'is_staff': False,
|
'is_staff': False,
|
||||||
'subscriptions': [],
|
|
||||||
'editor': []
|
'editor': []
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
@ -77,7 +73,6 @@ class AuthSerializer(serializers.Serializer):
|
||||||
'id': instance.pk,
|
'id': instance.pk,
|
||||||
'username': instance.username,
|
'username': instance.username,
|
||||||
'is_staff': instance.is_staff,
|
'is_staff': instance.is_staff,
|
||||||
'subscriptions': [sub.item.pk for sub in Subscription.objects.filter(user=instance)],
|
|
||||||
'editor': [edit.item.pk for edit in Editor.objects.filter(editor=instance)]
|
'editor': [edit.item.pk for edit in Editor.objects.filter(editor=instance)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,6 @@ class TestUserAPIViews(EndpointTester):
|
||||||
self.assertEqual(response.data['id'], self.user.pk)
|
self.assertEqual(response.data['id'], self.user.pk)
|
||||||
self.assertEqual(response.data['username'], self.user.username)
|
self.assertEqual(response.data['username'], self.user.username)
|
||||||
self.assertEqual(response.data['is_staff'], self.user.is_staff)
|
self.assertEqual(response.data['is_staff'], self.user.is_staff)
|
||||||
self.assertEqual(response.data['subscriptions'], [])
|
|
||||||
self.assertEqual(response.data['editor'], [])
|
self.assertEqual(response.data['editor'], [])
|
||||||
|
|
||||||
self.logout()
|
self.logout()
|
||||||
|
@ -51,7 +50,6 @@ class TestUserAPIViews(EndpointTester):
|
||||||
self.assertEqual(response.data['id'], None)
|
self.assertEqual(response.data['id'], None)
|
||||||
self.assertEqual(response.data['username'], '')
|
self.assertEqual(response.data['username'], '')
|
||||||
self.assertEqual(response.data['is_staff'], False)
|
self.assertEqual(response.data['is_staff'], False)
|
||||||
self.assertEqual(response.data['subscriptions'], [])
|
|
||||||
self.assertEqual(response.data['editor'], [])
|
self.assertEqual(response.data['editor'], [])
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -11,7 +11,7 @@ from rest_framework.permissions import \
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from apps.library.models import AccessPolicy, Editor, LibraryItem, Subscription, Version
|
from apps.library.models import AccessPolicy, Editor, LibraryItem, Version
|
||||||
from apps.oss.models import Operation
|
from apps.oss.models import Operation
|
||||||
from apps.rsform.models import Constituenta
|
from apps.rsform.models import Constituenta
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
@ -24,7 +24,7 @@ def _extract_item(obj: Any) -> LibraryItem:
|
||||||
return cast(LibraryItem, obj.schema)
|
return cast(LibraryItem, obj.schema)
|
||||||
elif isinstance(obj, Operation):
|
elif isinstance(obj, Operation):
|
||||||
return cast(LibraryItem, obj.oss)
|
return cast(LibraryItem, obj.oss)
|
||||||
elif isinstance(obj, (Version, Subscription, Editor)):
|
elif isinstance(obj, (Version, Editor)):
|
||||||
return cast(LibraryItem, obj.item)
|
return cast(LibraryItem, obj.item)
|
||||||
raise PermissionDenied({
|
raise PermissionDenied({
|
||||||
'message': 'Invalid type error. Please contact developers',
|
'message': 'Invalid type error. Please contact developers',
|
||||||
|
|
|
@ -101,20 +101,6 @@ export function patchSetEditors(target: string, request: FrontPush<ITargetUsers>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postSubscribe(target: string, request: FrontAction) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/library/${target}/subscribe`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteUnsubscribe(target: string, request: FrontAction) {
|
|
||||||
AxiosDelete({
|
|
||||||
endpoint: `/api/library/${target}/unsubscribe`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postCreateVersion(target: string, request: FrontExchange<IVersionData, IVersionCreatedResponse>) {
|
export function postCreateVersion(target: string, request: FrontExchange<IVersionData, IVersionCreatedResponse>) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
endpoint: `/api/library/${target}/create-version`,
|
endpoint: `/api/library/${target}/create-version`,
|
||||||
|
|
|
@ -6,8 +6,6 @@ import {
|
||||||
IconAlias,
|
IconAlias,
|
||||||
IconBusiness,
|
IconBusiness,
|
||||||
IconFilter,
|
IconFilter,
|
||||||
IconFollow,
|
|
||||||
IconFollowOff,
|
|
||||||
IconFormula,
|
IconFormula,
|
||||||
IconGraphCollapse,
|
IconGraphCollapse,
|
||||||
IconGraphExpand,
|
IconGraphExpand,
|
||||||
|
@ -64,14 +62,6 @@ export function VisibilityIcon({ value, size = '1.25rem', className }: DomIconPr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SubscribeIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
|
|
||||||
if (value) {
|
|
||||||
return <IconFollow size={size} className={className ?? 'clr-text-green'} />;
|
|
||||||
} else {
|
|
||||||
return <IconFollowOff size={size} className={className ?? 'clr-text-red'} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LocationIcon({ value, size = '1.25rem', className }: DomIconProps<string>) {
|
export function LocationIcon({ value, size = '1.25rem', className }: DomIconProps<string>) {
|
||||||
switch (value.substring(0, 2) as LocationHead) {
|
switch (value.substring(0, 2) as LocationHead) {
|
||||||
case LocationHead.COMMON:
|
case LocationHead.COMMON:
|
||||||
|
|
|
@ -100,9 +100,7 @@ export { BiUpvote as IconMoveUp } from 'react-icons/bi';
|
||||||
export { BiDownvote as IconMoveDown } from 'react-icons/bi';
|
export { BiDownvote as IconMoveDown } from 'react-icons/bi';
|
||||||
export { BiRightArrow as IconMoveRight } from 'react-icons/bi';
|
export { BiRightArrow as IconMoveRight } from 'react-icons/bi';
|
||||||
export { BiLeftArrow as IconMoveLeft } from 'react-icons/bi';
|
export { BiLeftArrow as IconMoveLeft } from 'react-icons/bi';
|
||||||
export { FiBell as IconFollow } from 'react-icons/fi';
|
|
||||||
export { TbHexagonPlus2 as IconNewRSForm } from 'react-icons/tb';
|
export { TbHexagonPlus2 as IconNewRSForm } from 'react-icons/tb';
|
||||||
export { FiBellOff as IconFollowOff } from 'react-icons/fi';
|
|
||||||
export { BiPlusCircle as IconNewItem } from 'react-icons/bi';
|
export { BiPlusCircle as IconNewItem } from 'react-icons/bi';
|
||||||
export { FaSquarePlus as IconNewItem2 } from 'react-icons/fa6';
|
export { FaSquarePlus as IconNewItem2 } from 'react-icons/fa6';
|
||||||
export { BiDuplicate as IconClone } from 'react-icons/bi';
|
export { BiDuplicate as IconClone } from 'react-icons/bi';
|
||||||
|
|
|
@ -103,9 +103,6 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
if (filter.isOwned !== undefined) {
|
if (filter.isOwned !== undefined) {
|
||||||
result = result.filter(item => filter.isOwned === (item.owner === user?.id));
|
result = result.filter(item => filter.isOwned === (item.owner === user?.id));
|
||||||
}
|
}
|
||||||
if (filter.isSubscribed !== undefined) {
|
|
||||||
result = result.filter(item => filter.isSubscribed == user?.subscriptions.includes(item.id));
|
|
||||||
}
|
|
||||||
if (filter.isEditor !== undefined) {
|
if (filter.isEditor !== undefined) {
|
||||||
result = result.filter(item => filter.isEditor == user?.editor.includes(item.id));
|
result = result.filter(item => filter.isEditor == user?.editor.includes(item.id));
|
||||||
}
|
}
|
||||||
|
@ -213,9 +210,6 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
(data: ILibraryCreateData, callback?: DataCallback<ILibraryItem>) => {
|
(data: ILibraryCreateData, callback?: DataCallback<ILibraryItem>) => {
|
||||||
const onSuccess = (newSchema: ILibraryItem) =>
|
const onSuccess = (newSchema: ILibraryItem) =>
|
||||||
reloadItems(() => {
|
reloadItems(() => {
|
||||||
if (user && !user.subscriptions.includes(newSchema.id)) {
|
|
||||||
user.subscriptions.push(newSchema.id);
|
|
||||||
}
|
|
||||||
if (callback) callback(newSchema);
|
if (callback) callback(newSchema);
|
||||||
});
|
});
|
||||||
setProcessingError(undefined);
|
setProcessingError(undefined);
|
||||||
|
@ -249,12 +243,6 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
onError: setProcessingError,
|
onError: setProcessingError,
|
||||||
onSuccess: () =>
|
onSuccess: () =>
|
||||||
reloadItems(() => {
|
reloadItems(() => {
|
||||||
if (user?.subscriptions.includes(target)) {
|
|
||||||
user.subscriptions.splice(
|
|
||||||
user.subscriptions.findIndex(item => item === target),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -275,9 +263,6 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
onError: setProcessingError,
|
onError: setProcessingError,
|
||||||
onSuccess: newSchema =>
|
onSuccess: newSchema =>
|
||||||
reloadItems(() => {
|
reloadItems(() => {
|
||||||
if (user && !user.subscriptions.includes(newSchema.id)) {
|
|
||||||
user.subscriptions.push(newSchema.id);
|
|
||||||
}
|
|
||||||
if (callback) callback(newSchema);
|
if (callback) callback(newSchema);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,13 +4,11 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState }
|
||||||
|
|
||||||
import { DataCallback } from '@/backend/apiTransport';
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
import {
|
import {
|
||||||
deleteUnsubscribe,
|
|
||||||
patchLibraryItem,
|
patchLibraryItem,
|
||||||
patchSetAccessPolicy,
|
patchSetAccessPolicy,
|
||||||
patchSetEditors,
|
patchSetEditors,
|
||||||
patchSetLocation,
|
patchSetLocation,
|
||||||
patchSetOwner,
|
patchSetOwner
|
||||||
postSubscribe
|
|
||||||
} from '@/backend/library';
|
} from '@/backend/library';
|
||||||
import {
|
import {
|
||||||
patchCreateInput,
|
patchCreateInput,
|
||||||
|
@ -52,12 +50,9 @@ interface IOssContext {
|
||||||
processingError: ErrorData;
|
processingError: ErrorData;
|
||||||
|
|
||||||
isOwned: boolean;
|
isOwned: boolean;
|
||||||
isSubscribed: boolean;
|
|
||||||
|
|
||||||
update: (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => void;
|
update: (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => void;
|
||||||
|
|
||||||
subscribe: (callback?: () => void) => void;
|
|
||||||
unsubscribe: (callback?: () => void) => void;
|
|
||||||
setOwner: (newOwner: UserID, callback?: () => void) => void;
|
setOwner: (newOwner: UserID, callback?: () => void) => void;
|
||||||
setAccessPolicy: (newPolicy: AccessPolicy, callback?: () => void) => void;
|
setAccessPolicy: (newPolicy: AccessPolicy, callback?: () => void) => void;
|
||||||
setLocation: (newLocation: string, callback?: () => void) => void;
|
setLocation: (newLocation: string, callback?: () => void) => void;
|
||||||
|
@ -94,19 +89,10 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
const [processing, setProcessing] = useState(false);
|
const [processing, setProcessing] = useState(false);
|
||||||
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
||||||
|
|
||||||
const [toggleTracking, setToggleTracking] = useState(false);
|
|
||||||
|
|
||||||
const isOwned = useMemo(() => {
|
const isOwned = useMemo(() => {
|
||||||
return user?.id === model?.owner || false;
|
return user?.id === model?.owner || false;
|
||||||
}, [user, model?.owner]);
|
}, [user, model?.owner]);
|
||||||
|
|
||||||
const isSubscribed = useMemo(() => {
|
|
||||||
if (!user || !model || !user.id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return model.subscribers.includes(user.id);
|
|
||||||
}, [user, model, toggleTracking]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
oss.setID(itemID);
|
oss.setID(itemID);
|
||||||
}, [itemID, oss.setID]);
|
}, [itemID, oss.setID]);
|
||||||
|
@ -133,56 +119,6 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
[itemID, model, library.localUpdateItem, oss.setData]
|
[itemID, model, library.localUpdateItem, oss.setData]
|
||||||
);
|
);
|
||||||
|
|
||||||
const subscribe = useCallback(
|
|
||||||
(callback?: () => void) => {
|
|
||||||
if (!model || !user) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setProcessingError(undefined);
|
|
||||||
postSubscribe(itemID, {
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: setProcessingError,
|
|
||||||
onSuccess: () => {
|
|
||||||
if (user.id && !model.subscribers.includes(user.id)) {
|
|
||||||
model.subscribers.push(user.id);
|
|
||||||
}
|
|
||||||
if (!user.subscriptions.includes(model.id)) {
|
|
||||||
user.subscriptions.push(model.id);
|
|
||||||
}
|
|
||||||
setToggleTracking(prev => !prev);
|
|
||||||
if (callback) callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[itemID, user, model]
|
|
||||||
);
|
|
||||||
|
|
||||||
const unsubscribe = useCallback(
|
|
||||||
(callback?: () => void) => {
|
|
||||||
if (!model || !user) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setProcessingError(undefined);
|
|
||||||
deleteUnsubscribe(itemID, {
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: setProcessingError,
|
|
||||||
onSuccess: () => {
|
|
||||||
if (user.id && model.subscribers.includes(user.id)) {
|
|
||||||
model.subscribers.splice(model.subscribers.indexOf(user.id), 1);
|
|
||||||
}
|
|
||||||
if (user.subscriptions.includes(model.id)) {
|
|
||||||
user.subscriptions.splice(user.subscriptions.indexOf(model.id), 1);
|
|
||||||
}
|
|
||||||
setToggleTracking(prev => !prev);
|
|
||||||
if (callback) callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[itemID, model, user]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setOwner = useCallback(
|
const setOwner = useCallback(
|
||||||
(newOwner: UserID, callback?: () => void) => {
|
(newOwner: UserID, callback?: () => void) => {
|
||||||
if (!model) {
|
if (!model) {
|
||||||
|
@ -421,11 +357,8 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
processing,
|
processing,
|
||||||
processingError,
|
processingError,
|
||||||
isOwned,
|
isOwned,
|
||||||
isSubscribed,
|
|
||||||
update,
|
update,
|
||||||
|
|
||||||
subscribe,
|
|
||||||
unsubscribe,
|
|
||||||
setOwner,
|
setOwner,
|
||||||
setEditors,
|
setEditors,
|
||||||
setAccessPolicy,
|
setAccessPolicy,
|
||||||
|
|
|
@ -4,14 +4,12 @@ import { createContext, useCallback, useContext, useMemo, useState } from 'react
|
||||||
|
|
||||||
import { DataCallback } from '@/backend/apiTransport';
|
import { DataCallback } from '@/backend/apiTransport';
|
||||||
import {
|
import {
|
||||||
deleteUnsubscribe,
|
|
||||||
patchLibraryItem,
|
patchLibraryItem,
|
||||||
patchSetAccessPolicy,
|
patchSetAccessPolicy,
|
||||||
patchSetEditors,
|
patchSetEditors,
|
||||||
patchSetLocation,
|
patchSetLocation,
|
||||||
patchSetOwner,
|
patchSetOwner,
|
||||||
postCreateVersion,
|
postCreateVersion
|
||||||
postSubscribe
|
|
||||||
} from '@/backend/library';
|
} from '@/backend/library';
|
||||||
import { postFindPredecessor } from '@/backend/oss';
|
import { postFindPredecessor } from '@/backend/oss';
|
||||||
import {
|
import {
|
||||||
|
@ -68,14 +66,11 @@ interface IRSFormContext {
|
||||||
|
|
||||||
isArchive: boolean;
|
isArchive: boolean;
|
||||||
isOwned: boolean;
|
isOwned: boolean;
|
||||||
isSubscribed: boolean;
|
|
||||||
|
|
||||||
update: (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => void;
|
update: (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => void;
|
||||||
download: (callback: DataCallback<Blob>) => void;
|
download: (callback: DataCallback<Blob>) => void;
|
||||||
upload: (data: IRSFormUploadData, callback: () => void) => void;
|
upload: (data: IRSFormUploadData, callback: () => void) => void;
|
||||||
|
|
||||||
subscribe: (callback?: () => void) => void;
|
|
||||||
unsubscribe: (callback?: () => void) => void;
|
|
||||||
setOwner: (newOwner: UserID, callback?: () => void) => void;
|
setOwner: (newOwner: UserID, callback?: () => void) => void;
|
||||||
setAccessPolicy: (newPolicy: AccessPolicy, callback?: () => void) => void;
|
setAccessPolicy: (newPolicy: AccessPolicy, callback?: () => void) => void;
|
||||||
setLocation: (newLocation: string, callback?: () => void) => void;
|
setLocation: (newLocation: string, callback?: () => void) => void;
|
||||||
|
@ -132,21 +127,12 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
||||||
const [processing, setProcessing] = useState(false);
|
const [processing, setProcessing] = useState(false);
|
||||||
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
||||||
|
|
||||||
const [toggleTracking, setToggleTracking] = useState(false);
|
|
||||||
|
|
||||||
const isOwned = useMemo(() => {
|
const isOwned = useMemo(() => {
|
||||||
return user?.id === schema?.owner || false;
|
return user?.id === schema?.owner || false;
|
||||||
}, [user, schema?.owner]);
|
}, [user, schema?.owner]);
|
||||||
|
|
||||||
const isArchive = useMemo(() => !!versionID, [versionID]);
|
const isArchive = useMemo(() => !!versionID, [versionID]);
|
||||||
|
|
||||||
const isSubscribed = useMemo(() => {
|
|
||||||
if (!user || !schema || !user.id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return schema.subscribers.includes(user.id);
|
|
||||||
}, [user, schema, toggleTracking]);
|
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
|
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
|
@ -190,56 +176,6 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
||||||
[itemID, setSchema, schema, library.localUpdateItem]
|
[itemID, setSchema, schema, library.localUpdateItem]
|
||||||
);
|
);
|
||||||
|
|
||||||
const subscribe = useCallback(
|
|
||||||
(callback?: () => void) => {
|
|
||||||
if (!schema || !user) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setProcessingError(undefined);
|
|
||||||
postSubscribe(itemID, {
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: setProcessingError,
|
|
||||||
onSuccess: () => {
|
|
||||||
if (user.id && !schema.subscribers.includes(user.id)) {
|
|
||||||
schema.subscribers.push(user.id);
|
|
||||||
}
|
|
||||||
if (!user.subscriptions.includes(schema.id)) {
|
|
||||||
user.subscriptions.push(schema.id);
|
|
||||||
}
|
|
||||||
setToggleTracking(prev => !prev);
|
|
||||||
if (callback) callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[itemID, schema, user]
|
|
||||||
);
|
|
||||||
|
|
||||||
const unsubscribe = useCallback(
|
|
||||||
(callback?: () => void) => {
|
|
||||||
if (!schema || !user) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setProcessingError(undefined);
|
|
||||||
deleteUnsubscribe(itemID, {
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: setProcessingError,
|
|
||||||
onSuccess: () => {
|
|
||||||
if (user.id && schema.subscribers.includes(user.id)) {
|
|
||||||
schema.subscribers.splice(schema.subscribers.indexOf(user.id), 1);
|
|
||||||
}
|
|
||||||
if (user.subscriptions.includes(schema.id)) {
|
|
||||||
user.subscriptions.splice(user.subscriptions.indexOf(schema.id), 1);
|
|
||||||
}
|
|
||||||
setToggleTracking(prev => !prev);
|
|
||||||
if (callback) callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[itemID, schema, user]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setOwner = useCallback(
|
const setOwner = useCallback(
|
||||||
(newOwner: UserID, callback?: () => void) => {
|
(newOwner: UserID, callback?: () => void) => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
|
@ -635,7 +571,6 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
||||||
processing,
|
processing,
|
||||||
processingError,
|
processingError,
|
||||||
isOwned,
|
isOwned,
|
||||||
isSubscribed,
|
|
||||||
isArchive,
|
isArchive,
|
||||||
update,
|
update,
|
||||||
download,
|
download,
|
||||||
|
@ -644,9 +579,6 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
||||||
resetAliases,
|
resetAliases,
|
||||||
produceStructure,
|
produceStructure,
|
||||||
inlineSynthesis,
|
inlineSynthesis,
|
||||||
|
|
||||||
subscribe,
|
|
||||||
unsubscribe,
|
|
||||||
setOwner,
|
setOwner,
|
||||||
setEditors,
|
setEditors,
|
||||||
setAccessPolicy,
|
setAccessPolicy,
|
||||||
|
|
|
@ -78,7 +78,6 @@ export interface ILibraryItem {
|
||||||
* Represents {@link ILibraryItem} constant data loaded for both OSS and RSForm.
|
* Represents {@link ILibraryItem} constant data loaded for both OSS and RSForm.
|
||||||
*/
|
*/
|
||||||
export interface ILibraryItemData extends ILibraryItem {
|
export interface ILibraryItemData extends ILibraryItem {
|
||||||
subscribers: UserID[];
|
|
||||||
editors: UserID[];
|
editors: UserID[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +108,6 @@ export interface ILibraryItemEditor {
|
||||||
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
||||||
promptEditors: () => void;
|
promptEditors: () => void;
|
||||||
promptLocation: () => void;
|
promptLocation: () => void;
|
||||||
toggleSubscribe: () => void;
|
|
||||||
|
|
||||||
share: () => void;
|
share: () => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ export type FontStyle = 'controls' | 'main' | 'math' | 'math2';
|
||||||
export enum HelpTopic {
|
export enum HelpTopic {
|
||||||
MAIN = 'main',
|
MAIN = 'main',
|
||||||
|
|
||||||
|
THESAURUS = 'thesaurus',
|
||||||
|
|
||||||
INTERFACE = 'user-interface',
|
INTERFACE = 'user-interface',
|
||||||
UI_LIBRARY = 'ui-library',
|
UI_LIBRARY = 'ui-library',
|
||||||
UI_RS_MENU = 'ui-rsform-menu',
|
UI_RS_MENU = 'ui-rsform-menu',
|
||||||
|
@ -112,6 +114,8 @@ export enum HelpTopic {
|
||||||
export const topicParent = new Map<HelpTopic, HelpTopic>([
|
export const topicParent = new Map<HelpTopic, HelpTopic>([
|
||||||
[HelpTopic.MAIN, HelpTopic.MAIN],
|
[HelpTopic.MAIN, HelpTopic.MAIN],
|
||||||
|
|
||||||
|
[HelpTopic.THESAURUS, HelpTopic.THESAURUS],
|
||||||
|
|
||||||
[HelpTopic.INTERFACE, HelpTopic.INTERFACE],
|
[HelpTopic.INTERFACE, HelpTopic.INTERFACE],
|
||||||
[HelpTopic.UI_LIBRARY, HelpTopic.INTERFACE],
|
[HelpTopic.UI_LIBRARY, HelpTopic.INTERFACE],
|
||||||
[HelpTopic.UI_RS_MENU, HelpTopic.INTERFACE],
|
[HelpTopic.UI_RS_MENU, HelpTopic.INTERFACE],
|
||||||
|
@ -182,7 +186,6 @@ export interface ILibraryFilter {
|
||||||
|
|
||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
isOwned?: boolean;
|
isOwned?: boolean;
|
||||||
isSubscribed?: boolean;
|
|
||||||
isEditor?: boolean;
|
isEditor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,6 @@ function LibraryPage() {
|
||||||
filter.head !== undefined ||
|
filter.head !== undefined ||
|
||||||
filter.isEditor !== undefined ||
|
filter.isEditor !== undefined ||
|
||||||
filter.isOwned !== undefined ||
|
filter.isOwned !== undefined ||
|
||||||
filter.isSubscribed !== undefined ||
|
|
||||||
filter.isVisible !== true ||
|
filter.isVisible !== true ||
|
||||||
!!filter.location,
|
!!filter.location,
|
||||||
[filter]
|
[filter]
|
||||||
|
@ -69,7 +68,6 @@ function LibraryPage() {
|
||||||
|
|
||||||
const toggleVisible = useCallback(() => setIsVisible(prev => toggleTristateFlag(prev)), [setIsVisible]);
|
const toggleVisible = useCallback(() => setIsVisible(prev => toggleTristateFlag(prev)), [setIsVisible]);
|
||||||
const toggleOwned = useCallback(() => setIsOwned(prev => toggleTristateFlag(prev)), [setIsOwned]);
|
const toggleOwned = useCallback(() => setIsOwned(prev => toggleTristateFlag(prev)), [setIsOwned]);
|
||||||
const toggleSubscribed = useCallback(() => setIsSubscribed(prev => toggleTristateFlag(prev)), [setIsSubscribed]);
|
|
||||||
const toggleEditor = useCallback(() => setIsEditor(prev => toggleTristateFlag(prev)), [setIsEditor]);
|
const toggleEditor = useCallback(() => setIsEditor(prev => toggleTristateFlag(prev)), [setIsEditor]);
|
||||||
const toggleFolderMode = useCallback(() => setFolderMode(prev => !prev), [setFolderMode]);
|
const toggleFolderMode = useCallback(() => setFolderMode(prev => !prev), [setFolderMode]);
|
||||||
|
|
||||||
|
@ -129,8 +127,6 @@ function LibraryPage() {
|
||||||
isOwned={isOwned}
|
isOwned={isOwned}
|
||||||
toggleOwned={toggleOwned}
|
toggleOwned={toggleOwned}
|
||||||
toggleVisible={toggleVisible}
|
toggleVisible={toggleVisible}
|
||||||
isSubscribed={isSubscribed}
|
|
||||||
toggleSubscribed={toggleSubscribed}
|
|
||||||
isEditor={isEditor}
|
isEditor={isEditor}
|
||||||
toggleEditor={toggleEditor}
|
toggleEditor={toggleEditor}
|
||||||
resetFilter={resetFilter}
|
resetFilter={resetFilter}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { LocationIcon, SubscribeIcon, VisibilityIcon } from '@/components/DomainIcons';
|
import { LocationIcon, VisibilityIcon } from '@/components/DomainIcons';
|
||||||
import { IconEditor, IconFilterReset, IconFolder, IconFolderTree, IconOwner } from '@/components/Icons';
|
import { IconEditor, IconFilterReset, IconFolder, IconFolderTree, IconOwner } from '@/components/Icons';
|
||||||
import { CProps } from '@/components/props';
|
import { CProps } from '@/components/props';
|
||||||
import Dropdown from '@/components/ui/Dropdown';
|
import Dropdown from '@/components/ui/Dropdown';
|
||||||
|
@ -37,8 +37,6 @@ interface ToolbarSearchProps {
|
||||||
toggleVisible: () => void;
|
toggleVisible: () => void;
|
||||||
isOwned: boolean | undefined;
|
isOwned: boolean | undefined;
|
||||||
toggleOwned: () => void;
|
toggleOwned: () => void;
|
||||||
isSubscribed: boolean | undefined;
|
|
||||||
toggleSubscribed: () => void;
|
|
||||||
isEditor: boolean | undefined;
|
isEditor: boolean | undefined;
|
||||||
toggleEditor: () => void;
|
toggleEditor: () => void;
|
||||||
resetFilter: () => void;
|
resetFilter: () => void;
|
||||||
|
@ -63,8 +61,6 @@ function ToolbarSearch({
|
||||||
toggleVisible,
|
toggleVisible,
|
||||||
isOwned,
|
isOwned,
|
||||||
toggleOwned,
|
toggleOwned,
|
||||||
isSubscribed,
|
|
||||||
toggleSubscribed,
|
|
||||||
isEditor,
|
isEditor,
|
||||||
toggleEditor,
|
toggleEditor,
|
||||||
resetFilter
|
resetFilter
|
||||||
|
@ -118,11 +114,6 @@ function ToolbarSearch({
|
||||||
icon={<VisibilityIcon value={true} className={tripleToggleColor(isVisible)} />}
|
icon={<VisibilityIcon value={true} className={tripleToggleColor(isVisible)} />}
|
||||||
onClick={toggleVisible}
|
onClick={toggleVisible}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
|
||||||
title='Я - Подписчик'
|
|
||||||
icon={<SubscribeIcon value={true} className={tripleToggleColor(isSubscribed)} />}
|
|
||||||
onClick={toggleSubscribed}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Я - Владелец'
|
title='Я - Владелец'
|
||||||
|
|
|
@ -15,6 +15,7 @@ import HelpInterface from './items/HelpInterface';
|
||||||
import HelpMain from './items/HelpMain';
|
import HelpMain from './items/HelpMain';
|
||||||
import HelpRSLang from './items/HelpRSLang';
|
import HelpRSLang from './items/HelpRSLang';
|
||||||
import HelpTerminologyControl from './items/HelpTerminologyControl';
|
import HelpTerminologyControl from './items/HelpTerminologyControl';
|
||||||
|
import HelpThesaurus from './items/HelpThesaurus';
|
||||||
import HelpVersions from './items/HelpVersions';
|
import HelpVersions from './items/HelpVersions';
|
||||||
import HelpAPI from './items/info/HelpAPI';
|
import HelpAPI from './items/info/HelpAPI';
|
||||||
import HelpContributors from './items/info/HelpContributors';
|
import HelpContributors from './items/info/HelpContributors';
|
||||||
|
@ -51,6 +52,7 @@ function TopicPage({ topic }: TopicPageProps) {
|
||||||
const size = useWindowSize();
|
const size = useWindowSize();
|
||||||
|
|
||||||
if (topic === HelpTopic.MAIN) return <HelpMain />;
|
if (topic === HelpTopic.MAIN) return <HelpMain />;
|
||||||
|
if (topic === HelpTopic.THESAURUS) return <HelpThesaurus />;
|
||||||
|
|
||||||
if (topic === HelpTopic.INTERFACE) return <HelpInterface />;
|
if (topic === HelpTopic.INTERFACE) return <HelpInterface />;
|
||||||
if (topic === HelpTopic.UI_LIBRARY) return <HelpLibrary />;
|
if (topic === HelpTopic.UI_LIBRARY) return <HelpLibrary />;
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { IconRSForm } from '@/components/Icons';
|
||||||
|
import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
|
function HelpThesaurus() {
|
||||||
|
return (
|
||||||
|
<div className='text-justify'>
|
||||||
|
<h1>Тезаурус</h1>
|
||||||
|
<p>
|
||||||
|
Данные раздел содержит основные термины и определения, используемые в работе с Порталом. Термины сгруппированы
|
||||||
|
по ключевым сущностям. Более подробно описание отношений между терминами даются в отдельных разделах данной
|
||||||
|
Справки через гиперссылки. Также указываются графические обозначения (иконки, цвета), используемые для
|
||||||
|
обозначения соответствующих сущностей в интерфейсе Портала.
|
||||||
|
</p>
|
||||||
|
<h2>Концептуализация</h2>
|
||||||
|
<p>Раздел в разработке...</p>
|
||||||
|
<h2>Концептуальная схема</h2>
|
||||||
|
<p>
|
||||||
|
<IconRSForm size='1rem' className='inline-icon' />{' '}
|
||||||
|
<LinkTopic text='Концептуальная схема' topic={HelpTopic.CC_SYSTEM} /> (система определений, КС) – совокупность
|
||||||
|
отдельных понятий и утверждений, а также связей между ними, задаваемых определениями.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Экспликация КС – изложение (процесс и результат) концептуальной схемы с помощью заданного языка описания –
|
||||||
|
набора формальных конструкций и правил построения определений.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Родоструктурная экспликация КС – экспликация КС с помощью{' '}
|
||||||
|
<LinkTopic text='аппарата родов структур' topic={HelpTopic.RSLANG} />.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Конституента</h2>
|
||||||
|
<p>Раздел в разработке...</p>
|
||||||
|
|
||||||
|
<h2>Операционная схема синтеза</h2>
|
||||||
|
<p>Раздел в разработке...</p>
|
||||||
|
|
||||||
|
<h2>Операция</h2>
|
||||||
|
<p>Раздел в разработке...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HelpThesaurus;
|
|
@ -3,7 +3,6 @@ import {
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
IconDownload,
|
IconDownload,
|
||||||
IconEditor,
|
IconEditor,
|
||||||
IconFollow,
|
|
||||||
IconImmutable,
|
IconImmutable,
|
||||||
IconOSS,
|
IconOSS,
|
||||||
IconOwner,
|
IconOwner,
|
||||||
|
@ -20,7 +19,7 @@ function HelpRSCard() {
|
||||||
|
|
||||||
<p>Карточка содержит общую информацию и статистику</p>
|
<p>Карточка содержит общую информацию и статистику</p>
|
||||||
<p>
|
<p>
|
||||||
Карточка позволяет управлять атрибутами схемы и <LinkTopic text='версиями' topic={HelpTopic.VERSIONS} />
|
Карточка позволяет управлять атрибутами и <LinkTopic text='версиями' topic={HelpTopic.VERSIONS} />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Карточка позволяет назначать <IconEditor className='inline-icon' /> Редакторов
|
Карточка позволяет назначать <IconEditor className='inline-icon' /> Редакторов
|
||||||
|
@ -46,14 +45,11 @@ function HelpRSCard() {
|
||||||
<IconPublic className='inline-icon' /> Общедоступные схемы видны всем посетителям
|
<IconPublic className='inline-icon' /> Общедоступные схемы видны всем посетителям
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconImmutable className='inline-icon' /> Неизменные схемы редактируют только администраторы
|
<IconImmutable className='inline-icon' /> Неизменные схемы
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconClone className='inline-icon icon-green' /> Клонировать – создать копию схемы
|
<IconClone className='inline-icon icon-green' /> Клонировать – создать копию схемы
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<IconFollow className='inline-icon' /> Отслеживание – схема в персональном списке
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<IconDownload className='inline-icon' /> Загрузить/Выгрузить – взаимодействие с Экстеор
|
<IconDownload className='inline-icon' /> Загрузить/Выгрузить – взаимодействие с Экстеор
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -4,7 +4,6 @@ import clsx from 'clsx';
|
||||||
|
|
||||||
import FlexColumn from '@/components/ui/FlexColumn';
|
import FlexColumn from '@/components/ui/FlexColumn';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
|
||||||
import { useOSS } from '@/context/OssContext';
|
import { useOSS } from '@/context/OssContext';
|
||||||
import EditorLibraryItem from '@/pages/RSFormPage/EditorRSFormCard/EditorLibraryItem';
|
import EditorLibraryItem from '@/pages/RSFormPage/EditorRSFormCard/EditorLibraryItem';
|
||||||
import ToolbarRSFormCard from '@/pages/RSFormPage/EditorRSFormCard/ToolbarRSFormCard';
|
import ToolbarRSFormCard from '@/pages/RSFormPage/EditorRSFormCard/ToolbarRSFormCard';
|
||||||
|
@ -21,8 +20,7 @@ interface EditorOssCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardProps) {
|
function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardProps) {
|
||||||
const { schema, isSubscribed } = useOSS();
|
const { schema } = useOSS();
|
||||||
const { user } = useAuth();
|
|
||||||
const controller = useOssEdit();
|
const controller = useOssEdit();
|
||||||
|
|
||||||
function initiateSubmit() {
|
function initiateSubmit() {
|
||||||
|
@ -44,9 +42,7 @@ function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardPr
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToolbarRSFormCard
|
<ToolbarRSFormCard
|
||||||
subscribed={isSubscribed}
|
|
||||||
modified={isModified}
|
modified={isModified}
|
||||||
anonymous={!user}
|
|
||||||
onSubmit={initiateSubmit}
|
onSubmit={initiateSubmit}
|
||||||
onDestroy={onDestroy}
|
onDestroy={onDestroy}
|
||||||
controller={controller}
|
controller={controller}
|
||||||
|
|
|
@ -22,29 +22,24 @@ function NodeCore({ node }: NodeCoreProps) {
|
||||||
const longLabel = node.data.label.length > PARAMETER.ossLongLabel;
|
const longLabel = node.data.label.length > PARAMETER.ossLongLabel;
|
||||||
const labelText = truncateToLastWord(node.data.label, PARAMETER.ossTruncateLabel);
|
const labelText = truncateToLastWord(node.data.label, PARAMETER.ossTruncateLabel);
|
||||||
|
|
||||||
const handleOpenSchema = () => {
|
|
||||||
controller.openOperationSchema(Number(node.id));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Overlay position='top-0 right-0' className='flex flex-col gap-1 p-[2px]'>
|
<Overlay position='top-0 right-0' className='flex flex-col gap-1 p-[2px]'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='12px' />}
|
disabled
|
||||||
noHover
|
noHover
|
||||||
noPadding
|
noPadding
|
||||||
title={hasFile ? 'Связанная КС' : 'Нет связанной КС'}
|
title={hasFile ? 'Связанная КС' : 'Нет связанной КС'}
|
||||||
|
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='12px' />}
|
||||||
hideTitle={!controller.showTooltip}
|
hideTitle={!controller.showTooltip}
|
||||||
onClick={handleOpenSchema}
|
|
||||||
disabled={!hasFile}
|
|
||||||
/>
|
/>
|
||||||
{node.data.operation.is_consolidation ? (
|
{node.data.operation.is_consolidation ? (
|
||||||
<MiniButton
|
<MiniButton
|
||||||
icon={<IconConsolidation className='clr-text-primary' size='12px' />}
|
|
||||||
disabled
|
disabled
|
||||||
noPadding
|
noPadding
|
||||||
noHover
|
noHover
|
||||||
titleHtml='<b>Внимание!</b><br />Ромбовидный синтез</br/>Возможны дубликаты конституент'
|
titleHtml='<b>Внимание!</b><br />Ромбовидный синтез</br/>Возможны дубликаты конституент'
|
||||||
|
icon={<IconConsolidation className='clr-text-primary' size='12px' />}
|
||||||
hideTitle={!controller.showTooltip}
|
hideTitle={!controller.showTooltip}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -239,6 +239,9 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSaveImage = useCallback(() => {
|
const handleSaveImage = useCallback(() => {
|
||||||
|
if (!model.schema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport');
|
const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport');
|
||||||
if (canvas === null) {
|
if (canvas === null) {
|
||||||
toast.error(errors.imageFailed);
|
toast.error(errors.imageFailed);
|
||||||
|
@ -261,7 +264,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
})
|
})
|
||||||
.then(dataURL => {
|
.then(dataURL => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.setAttribute('download', 'reactflow.svg');
|
a.setAttribute('download', `${model.schema?.alias ?? 'oss'}.svg`);
|
||||||
a.setAttribute('href', dataURL);
|
a.setAttribute('href', dataURL);
|
||||||
a.click();
|
a.click();
|
||||||
})
|
})
|
||||||
|
@ -269,7 +272,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toast.error(errors.imageFailed);
|
toast.error(errors.imageFailed);
|
||||||
});
|
});
|
||||||
}, [colors, nodes]);
|
}, [colors, nodes, model.schema]);
|
||||||
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: CProps.EventMouse, node: OssNode) => {
|
(event: CProps.EventMouse, node: OssNode) => {
|
||||||
|
|
|
@ -58,7 +58,6 @@ export interface IOssEditContext extends ILibraryItemEditor {
|
||||||
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
||||||
promptEditors: () => void;
|
promptEditors: () => void;
|
||||||
promptLocation: () => void;
|
promptLocation: () => void;
|
||||||
toggleSubscribe: () => void;
|
|
||||||
|
|
||||||
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
||||||
|
|
||||||
|
@ -173,14 +172,6 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleSubscribe = useCallback(() => {
|
|
||||||
if (model.isSubscribed) {
|
|
||||||
model.unsubscribe(() => toast.success(information.unsubscribed));
|
|
||||||
} else {
|
|
||||||
model.subscribe(() => toast.success(information.subscribed));
|
|
||||||
}
|
|
||||||
}, [model]);
|
|
||||||
|
|
||||||
const setOwner = useCallback(
|
const setOwner = useCallback(
|
||||||
(newOwner: UserID) => {
|
(newOwner: UserID) => {
|
||||||
model.setOwner(newOwner, () => toast.success(information.changesSaved));
|
model.setOwner(newOwner, () => toast.success(information.changesSaved));
|
||||||
|
@ -372,7 +363,6 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
isProcessing: model.processing,
|
isProcessing: model.processing,
|
||||||
isAttachedToOSS: false,
|
isAttachedToOSS: false,
|
||||||
|
|
||||||
toggleSubscribe,
|
|
||||||
setOwner,
|
setOwner,
|
||||||
setAccessPolicy,
|
setAccessPolicy,
|
||||||
promptEditors,
|
promptEditors,
|
||||||
|
|
|
@ -109,16 +109,6 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
||||||
<InfoUsers items={item?.editors ?? []} prefix={prefixes.user_editors} />
|
<InfoUsers items={item?.editors ?? []} prefix={prefixes.user_editors} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<LabeledValue
|
|
||||||
id='sub_stats' //
|
|
||||||
className='sm:mb-1'
|
|
||||||
label='Отслеживают'
|
|
||||||
text={item?.subscribers.length ?? 0}
|
|
||||||
/>
|
|
||||||
<Tooltip anchorSelect='#sub_stats' layer='z-modalTooltip'>
|
|
||||||
<InfoUsers items={item?.subscribers ?? []} prefix={prefixes.user_subs} />
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<LabeledValue
|
<LabeledValue
|
||||||
className='sm:mb-1'
|
className='sm:mb-1'
|
||||||
label='Дата обновления'
|
label='Дата обновления'
|
||||||
|
|
|
@ -4,7 +4,6 @@ import clsx from 'clsx';
|
||||||
|
|
||||||
import FlexColumn from '@/components/ui/FlexColumn';
|
import FlexColumn from '@/components/ui/FlexColumn';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
|
||||||
import { useRSForm } from '@/context/RSFormContext';
|
import { useRSForm } from '@/context/RSFormContext';
|
||||||
import { globals } from '@/utils/constants';
|
import { globals } from '@/utils/constants';
|
||||||
|
|
||||||
|
@ -21,8 +20,7 @@ interface EditorRSFormCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSFormCardProps) {
|
function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSFormCardProps) {
|
||||||
const { schema, isSubscribed } = useRSForm();
|
const { schema } = useRSForm();
|
||||||
const { user } = useAuth();
|
|
||||||
const controller = useRSEdit();
|
const controller = useRSEdit();
|
||||||
|
|
||||||
function initiateSubmit() {
|
function initiateSubmit() {
|
||||||
|
@ -44,9 +42,7 @@ function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSForm
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToolbarRSFormCard
|
<ToolbarRSFormCard
|
||||||
subscribed={isSubscribed}
|
|
||||||
modified={isModified}
|
modified={isModified}
|
||||||
anonymous={!user}
|
|
||||||
onSubmit={initiateSubmit}
|
onSubmit={initiateSubmit}
|
||||||
onDestroy={onDestroy}
|
onDestroy={onDestroy}
|
||||||
controller={controller}
|
controller={controller}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { SubscribeIcon } from '@/components/DomainIcons';
|
|
||||||
import { IconDestroy, IconSave, IconShare } from '@/components/Icons';
|
import { IconDestroy, IconSave, IconShare } from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
|
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
|
||||||
|
@ -20,21 +19,12 @@ import { IRSEditContext } from '../RSEditContext';
|
||||||
|
|
||||||
interface ToolbarRSFormCardProps {
|
interface ToolbarRSFormCardProps {
|
||||||
modified: boolean;
|
modified: boolean;
|
||||||
subscribed: boolean;
|
|
||||||
anonymous: boolean;
|
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
onDestroy: () => void;
|
onDestroy: () => void;
|
||||||
controller: ILibraryItemEditor;
|
controller: ILibraryItemEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToolbarRSFormCard({
|
function ToolbarRSFormCard({ modified, controller, onSubmit, onDestroy }: ToolbarRSFormCardProps) {
|
||||||
modified,
|
|
||||||
anonymous,
|
|
||||||
controller,
|
|
||||||
subscribed,
|
|
||||||
onSubmit,
|
|
||||||
onDestroy
|
|
||||||
}: ToolbarRSFormCardProps) {
|
|
||||||
const { accessLevel } = useAccessMode();
|
const { accessLevel } = useAccessMode();
|
||||||
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
|
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
|
||||||
|
|
||||||
|
@ -71,14 +61,6 @@ function ToolbarRSFormCard({
|
||||||
onClick={controller.share}
|
onClick={controller.share}
|
||||||
disabled={controller.schema?.access_policy !== AccessPolicy.PUBLIC}
|
disabled={controller.schema?.access_policy !== AccessPolicy.PUBLIC}
|
||||||
/>
|
/>
|
||||||
{!anonymous ? (
|
|
||||||
<MiniButton
|
|
||||||
titleHtml={`Отслеживание <b>${subscribed ? 'включено' : 'выключено'}</b>`}
|
|
||||||
icon={<SubscribeIcon value={subscribed} className={subscribed ? 'icon-primary' : 'clr-text-controls'} />}
|
|
||||||
disabled={controller.isProcessing}
|
|
||||||
onClick={controller.toggleSubscribe}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{controller.isMutable ? (
|
{controller.isMutable ? (
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Удалить схему'
|
title='Удалить схему'
|
||||||
|
|
|
@ -74,7 +74,6 @@ export interface IRSEditContext extends ILibraryItemEditor {
|
||||||
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
||||||
promptEditors: () => void;
|
promptEditors: () => void;
|
||||||
promptLocation: () => void;
|
promptLocation: () => void;
|
||||||
toggleSubscribe: () => void;
|
|
||||||
|
|
||||||
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
||||||
select: (target: ConstituentaID) => void;
|
select: (target: ConstituentaID) => void;
|
||||||
|
@ -589,14 +588,6 @@ export const RSEditState = ({
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleSubscribe = useCallback(() => {
|
|
||||||
if (model.isSubscribed) {
|
|
||||||
model.unsubscribe(() => toast.success(information.unsubscribed));
|
|
||||||
} else {
|
|
||||||
model.subscribe(() => toast.success(information.subscribed));
|
|
||||||
}
|
|
||||||
}, [model]);
|
|
||||||
|
|
||||||
const setOwner = useCallback(
|
const setOwner = useCallback(
|
||||||
(newOwner: UserID) => {
|
(newOwner: UserID) => {
|
||||||
model.setOwner(newOwner, () => toast.success(information.changesSaved));
|
model.setOwner(newOwner, () => toast.success(information.changesSaved));
|
||||||
|
@ -632,7 +623,6 @@ export const RSEditState = ({
|
||||||
nothingSelected,
|
nothingSelected,
|
||||||
canDeleteSelected,
|
canDeleteSelected,
|
||||||
|
|
||||||
toggleSubscribe,
|
|
||||||
setOwner,
|
setOwner,
|
||||||
setAccessPolicy,
|
setAccessPolicy,
|
||||||
promptEditors,
|
promptEditors,
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { urls } from '@/app/urls';
|
|
||||||
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
|
|
||||||
import NoData from '@/components/ui/NoData';
|
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
|
||||||
import { ILibraryItem } from '@/models/library';
|
|
||||||
import { animateSideView } from '@/styling/animations';
|
|
||||||
|
|
||||||
interface TableSubscriptionsProps {
|
|
||||||
items: ILibraryItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<ILibraryItem>();
|
|
||||||
|
|
||||||
function TableSubscriptions({ items }: TableSubscriptionsProps) {
|
|
||||||
const router = useConceptNavigation();
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const openRSForm = (item: ILibraryItem) => router.push(urls.schema(item.id));
|
|
||||||
|
|
||||||
const columns = useMemo(
|
|
||||||
() => [
|
|
||||||
columnHelper.accessor('alias', {
|
|
||||||
id: 'alias',
|
|
||||||
header: 'Шифр',
|
|
||||||
size: 200,
|
|
||||||
minSize: 200,
|
|
||||||
maxSize: 200,
|
|
||||||
enableSorting: true
|
|
||||||
}),
|
|
||||||
columnHelper.accessor('title', {
|
|
||||||
id: 'title',
|
|
||||||
header: 'Название',
|
|
||||||
minSize: 200,
|
|
||||||
size: 2000,
|
|
||||||
maxSize: 2000,
|
|
||||||
enableSorting: true
|
|
||||||
}),
|
|
||||||
columnHelper.accessor('time_update', {
|
|
||||||
id: 'time_update',
|
|
||||||
header: 'Обновлена',
|
|
||||||
minSize: 150,
|
|
||||||
size: 150,
|
|
||||||
maxSize: 150,
|
|
||||||
cell: props => (
|
|
||||||
<div className='text-sm whitespace-nowrap'>{new Date(props.getValue()).toLocaleString(intl.locale)}</div>
|
|
||||||
),
|
|
||||||
enableSorting: true
|
|
||||||
})
|
|
||||||
],
|
|
||||||
[intl]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial={{ ...animateSideView.initial }}
|
|
||||||
animate={{ ...animateSideView.animate }}
|
|
||||||
exit={{ ...animateSideView.exit }}
|
|
||||||
>
|
|
||||||
<h1 className='mb-6 select-none'>Отслеживаемые схемы</h1>
|
|
||||||
<DataTable
|
|
||||||
dense
|
|
||||||
noFooter
|
|
||||||
className='max-h-[23.8rem] cc-scroll-y text-sm border'
|
|
||||||
columns={columns}
|
|
||||||
data={items}
|
|
||||||
headPosition='0'
|
|
||||||
enableSorting
|
|
||||||
initialSorting={{
|
|
||||||
id: 'time_update',
|
|
||||||
desc: true
|
|
||||||
}}
|
|
||||||
noDataComponent={<NoData className='h-[10rem]'>Отслеживаемые схемы отсутствуют</NoData>}
|
|
||||||
onRowClicked={openRSForm}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TableSubscriptions;
|
|
|
@ -1,31 +1,14 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { AnimatePresence } from 'framer-motion';
|
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
import { SubscribeIcon } from '@/components/DomainIcons';
|
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
|
||||||
import Overlay from '@/components/ui/Overlay';
|
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import DataLoader from '@/components/wrap/DataLoader';
|
import DataLoader from '@/components/wrap/DataLoader';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
|
||||||
import { useLibrary } from '@/context/LibraryContext';
|
|
||||||
import { useUserProfile } from '@/context/UserProfileContext';
|
import { useUserProfile } from '@/context/UserProfileContext';
|
||||||
|
|
||||||
import EditorPassword from './EditorPassword';
|
import EditorPassword from './EditorPassword';
|
||||||
import EditorProfile from './EditorProfile';
|
import EditorProfile from './EditorProfile';
|
||||||
import TableSubscriptions from './TableSubscriptions';
|
|
||||||
|
|
||||||
function UserContents() {
|
function UserContents() {
|
||||||
const { user, error, loading } = useUserProfile();
|
const { user, error, loading } = useUserProfile();
|
||||||
const { user: auth } = useAuth();
|
|
||||||
const { items } = useLibrary();
|
|
||||||
|
|
||||||
const [showSubs, setShowSubs] = useState(false);
|
|
||||||
|
|
||||||
const subscriptions = useMemo(() => {
|
|
||||||
return items.filter(item => auth?.subscriptions.includes(item.id));
|
|
||||||
}, [auth, items]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataLoader
|
<DataLoader
|
||||||
|
@ -36,22 +19,12 @@ function UserContents() {
|
||||||
>
|
>
|
||||||
<AnimateFade className='flex gap-6 py-2 mx-auto w-fit'>
|
<AnimateFade className='flex gap-6 py-2 mx-auto w-fit'>
|
||||||
<div className='w-fit'>
|
<div className='w-fit'>
|
||||||
<Overlay position='top-0 right-0'>
|
|
||||||
<MiniButton
|
|
||||||
title='Отслеживаемые схемы'
|
|
||||||
icon={<SubscribeIcon value={showSubs} className='icon-primary' />}
|
|
||||||
onClick={() => setShowSubs(prev => !prev)}
|
|
||||||
/>
|
|
||||||
</Overlay>
|
|
||||||
<h1 className='mb-4 select-none'>Учетные данные пользователя</h1>
|
<h1 className='mb-4 select-none'>Учетные данные пользователя</h1>
|
||||||
<div className='flex py-2'>
|
<div className='flex py-2'>
|
||||||
<EditorProfile />
|
<EditorProfile />
|
||||||
<EditorPassword />
|
<EditorPassword />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AnimatePresence>
|
|
||||||
{subscriptions.length > 0 && showSubs ? <TableSubscriptions items={subscriptions} /> : null}
|
|
||||||
</AnimatePresence>
|
|
||||||
</AnimateFade>
|
</AnimateFade>
|
||||||
</DataLoader>
|
</DataLoader>
|
||||||
);
|
);
|
||||||
|
|
|
@ -168,6 +168,7 @@ export function findReferenceAt(pos: number, state: EditorState) {
|
||||||
export function domTooltipConstituenta(cst?: IConstituenta, canClick?: boolean) {
|
export function domTooltipConstituenta(cst?: IConstituenta, canClick?: boolean) {
|
||||||
const dom = document.createElement('div');
|
const dom = document.createElement('div');
|
||||||
dom.className = clsx(
|
dom.className = clsx(
|
||||||
|
'z-topmost',
|
||||||
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
|
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
|
||||||
'dense',
|
'dense',
|
||||||
'p-2',
|
'p-2',
|
||||||
|
@ -246,6 +247,7 @@ export function domTooltipEntityReference(
|
||||||
) {
|
) {
|
||||||
const dom = document.createElement('div');
|
const dom = document.createElement('div');
|
||||||
dom.className = clsx(
|
dom.className = clsx(
|
||||||
|
'z-topmost',
|
||||||
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
|
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
|
||||||
'dense',
|
'dense',
|
||||||
'p-2 flex flex-col',
|
'p-2 flex flex-col',
|
||||||
|
@ -304,6 +306,7 @@ export function domTooltipSyntacticReference(
|
||||||
) {
|
) {
|
||||||
const dom = document.createElement('div');
|
const dom = document.createElement('div');
|
||||||
dom.className = clsx(
|
dom.className = clsx(
|
||||||
|
'z-topmost',
|
||||||
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
|
'max-h-[25rem] max-w-[25rem] min-w-[10rem]',
|
||||||
'dense',
|
'dense',
|
||||||
'p-2 flex flex-col',
|
'p-2 flex flex-col',
|
||||||
|
|
|
@ -365,6 +365,8 @@ export function labelHelpTopic(topic: HelpTopic): string {
|
||||||
switch (topic) {
|
switch (topic) {
|
||||||
case HelpTopic.MAIN: return 'Портал';
|
case HelpTopic.MAIN: return 'Портал';
|
||||||
|
|
||||||
|
case HelpTopic.THESAURUS: return 'Тезаурус';
|
||||||
|
|
||||||
case HelpTopic.INTERFACE: return 'Интерфейс';
|
case HelpTopic.INTERFACE: return 'Интерфейс';
|
||||||
case HelpTopic.UI_LIBRARY: return 'Библиотека';
|
case HelpTopic.UI_LIBRARY: return 'Библиотека';
|
||||||
case HelpTopic.UI_RS_MENU: return 'Меню схемы';
|
case HelpTopic.UI_RS_MENU: return 'Меню схемы';
|
||||||
|
@ -414,6 +416,8 @@ export function describeHelpTopic(topic: HelpTopic): string {
|
||||||
switch (topic) {
|
switch (topic) {
|
||||||
case HelpTopic.MAIN: return 'общая справка по порталу';
|
case HelpTopic.MAIN: return 'общая справка по порталу';
|
||||||
|
|
||||||
|
case HelpTopic.THESAURUS: return 'термины Портала';
|
||||||
|
|
||||||
case HelpTopic.INTERFACE: return 'описание интерфейса пользователя';
|
case HelpTopic.INTERFACE: return 'описание интерфейса пользователя';
|
||||||
case HelpTopic.UI_LIBRARY: return 'поиск и просмотр схем';
|
case HelpTopic.UI_LIBRARY: return 'поиск и просмотр схем';
|
||||||
case HelpTopic.UI_RS_MENU: return 'меню редактирования схемы';
|
case HelpTopic.UI_RS_MENU: return 'меню редактирования схемы';
|
||||||
|
@ -920,9 +924,6 @@ export function describeOperationType(itemType: OperationType): string {
|
||||||
export const information = {
|
export const information = {
|
||||||
changesSaved: 'Изменения сохранены',
|
changesSaved: 'Изменения сохранены',
|
||||||
|
|
||||||
subscribed: 'Отслеживание отключено',
|
|
||||||
unsubscribed: 'Отслеживание выключено',
|
|
||||||
|
|
||||||
pathReady: 'Путь скопирован',
|
pathReady: 'Путь скопирован',
|
||||||
substituteSingle: 'Отождествление завершено',
|
substituteSingle: 'Отождествление завершено',
|
||||||
reorderComplete: 'Упорядочение завершено',
|
reorderComplete: 'Упорядочение завершено',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user