F: Remove subscriptions and notification icon

This commit is contained in:
Ivan 2024-08-20 15:09:36 +03:00
parent c6b192b841
commit 0c8577e913
38 changed files with 8113 additions and 8765 deletions

View File

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

View File

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

View File

@ -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',
),
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'], [])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -182,7 +182,6 @@ export interface ILibraryFilter {
isVisible?: boolean; isVisible?: boolean;
isOwned?: boolean; isOwned?: boolean;
isSubscribed?: boolean;
isEditor?: boolean; isEditor?: boolean;
} }

View File

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

View File

@ -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='Я - Владелец'

View File

@ -3,7 +3,6 @@ import {
IconDestroy, IconDestroy,
IconDownload, IconDownload,
IconEditor, IconEditor,
IconFollow,
IconImmutable, IconImmutable,
IconOSS, IconOSS,
IconOwner, IconOwner,
@ -51,9 +50,6 @@ function HelpRSCard() {
<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>

View File

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

View File

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

View File

@ -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='Дата обновления'

View File

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

View File

@ -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='Удалить схему'

View File

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

View File

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

View File

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

View File

@ -920,9 +920,6 @@ export function describeOperationType(itemType: OperationType): string {
export const information = { export const information = {
changesSaved: 'Изменения сохранены', changesSaved: 'Изменения сохранены',
subscribed: 'Отслеживание отключено',
unsubscribed: 'Отслеживание выключено',
pathReady: 'Путь скопирован', pathReady: 'Путь скопирован',
substituteSingle: 'Отождествление завершено', substituteSingle: 'Отождествление завершено',
reorderComplete: 'Упорядочение завершено', reorderComplete: 'Упорядочение завершено',