mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Add option to clone only selected constituents
This commit is contained in:
parent
2d707eca72
commit
604578d9da
37
TODO.txt
37
TODO.txt
|
@ -1,28 +1,27 @@
|
|||
!! This is not complete list of todos !!
|
||||
This list only contains global tech refactorings and tech debt
|
||||
!! This is not complete list of TODOs !!
|
||||
For more specific TODOs see comments in code
|
||||
|
||||
[Functionality]
|
||||
- landing page
|
||||
- home page (user specific)
|
||||
- export PDF
|
||||
- блок нотификаций пользователей
|
||||
- блок синтеза
|
||||
[Functionality - PROGRESS]
|
||||
- Landing page
|
||||
- Home page (user specific)
|
||||
|
||||
- статический анализ схемы
|
||||
- конфигурации правил для разных статусов
|
||||
- Operational synthesis schema as LibraryItem ?
|
||||
|
||||
- Library organization, search and exploration. Consider new user experience
|
||||
- поиск по содержимому КС в Библиотеке
|
||||
|
||||
- private projects and permissions. Consider cooperative editing
|
||||
|
||||
- draggable rows in constituents table
|
||||
- Draggable rows in constituents table
|
||||
- Clickable IDs in RSEditor tooltips
|
||||
|
||||
- ARIA (accessibility considerations) - for now machine reading not supported
|
||||
- Library organization, search and exploration. Consider new user experience
|
||||
- Private projects and permissions. Consider cooperative editing
|
||||
- Rework access setup: project-based, user-based, enable sharing. Prevent enumerating access to private schemas by default
|
||||
|
||||
- rework access setup: project-based, user-based, enable sharing. Prevent enumerating access to private schemas by default
|
||||
[functionality - PLANNING]
|
||||
- User notifications on edit - consider spam prevention and change aggregation
|
||||
- Static analyzer for RSForm
|
||||
- Content based search in Library
|
||||
|
||||
- Export PDF (Items list, Graph)
|
||||
- ARIA (accessibility considerations) - for now machine reading not supported
|
||||
- Internationalization - at least english version. Consider react.intl
|
||||
|
||||
|
||||
[Tech]
|
||||
|
@ -41,7 +40,7 @@ For more specific TODOs see comments in code
|
|||
|
||||
[Research]
|
||||
Research and consider integration
|
||||
- django-allauth
|
||||
- django-allauth - consider supporting popular auth providers
|
||||
- drf-messages
|
||||
|
||||
- radix-ui
|
||||
|
|
|
@ -11,6 +11,7 @@ from .basics import (
|
|||
)
|
||||
from .data_access import (
|
||||
LibraryItemSerializer,
|
||||
LibraryItemCloneSerializer,
|
||||
RSFormSerializer,
|
||||
RSFormParseSerializer,
|
||||
VersionSerializer,
|
||||
|
|
|
@ -256,6 +256,11 @@ class CstListSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class LibraryItemCloneSerializer(LibraryItemSerializer):
|
||||
''' Serializer: LibraryItem cloning. '''
|
||||
items = PKField(many=True, required=False, queryset=Constituenta.objects.all())
|
||||
|
||||
|
||||
class CstMoveSerializer(CstListSerializer):
|
||||
''' Serializer: Change constituenta position. '''
|
||||
move_to = serializers.IntegerField()
|
||||
|
|
|
@ -80,6 +80,10 @@ class EndpointTester(APITestCase):
|
|||
response = self.execute(data, **kwargs)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def assertCreated(self, data=None, **kwargs):
|
||||
response = self.execute(data, **kwargs)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
def assertBadData(self, data=None, **kwargs):
|
||||
response = self.execute(data, **kwargs)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
|
|
@ -31,6 +31,7 @@ class TestLibraryViewset(EndpointTester):
|
|||
alias='T3',
|
||||
is_common=True
|
||||
)
|
||||
self.invalid_item = 1337 + self.common.pk
|
||||
|
||||
|
||||
@decl_endpoint('/api/library', method='post')
|
||||
|
@ -49,6 +50,7 @@ class TestLibraryViewset(EndpointTester):
|
|||
@decl_endpoint('/api/library/{item}', method='patch')
|
||||
def test_update(self):
|
||||
data = {'id': self.unowned.id, 'title': 'New title'}
|
||||
self.assertNotFound(data, item=self.invalid_item)
|
||||
self.assertForbidden(data, item=self.unowned.id)
|
||||
|
||||
data = {'id': self.owned.id, 'title': 'New title'}
|
||||
|
@ -71,6 +73,7 @@ class TestLibraryViewset(EndpointTester):
|
|||
|
||||
@decl_endpoint('/api/library/{item}/claim', method='post')
|
||||
def test_claim(self):
|
||||
self.assertNotFound(item=self.invalid_item)
|
||||
self.assertForbidden(item=self.owned.id)
|
||||
|
||||
self.owned.is_common = True
|
||||
|
@ -127,6 +130,7 @@ class TestLibraryViewset(EndpointTester):
|
|||
|
||||
@decl_endpoint('/api/library/{item}/subscribe', method='post')
|
||||
def test_subscriptions(self):
|
||||
self.assertNotFound(item=self.invalid_item)
|
||||
response = self.client.delete(f'/api/library/{self.unowned.id}/unsubscribe')
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
self.assertFalse(self.user in self.unowned.subscribers())
|
||||
|
@ -174,11 +178,31 @@ class TestLibraryViewset(EndpointTester):
|
|||
)
|
||||
|
||||
data = {'title': 'Title1337'}
|
||||
self.assertNotFound(data, item=self.invalid_item)
|
||||
self.assertCreated(data, item=self.unowned.id)
|
||||
|
||||
data = {'title': 'Title1338'}
|
||||
response = self.execute(data, item=self.owned.id)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
self.assertEqual(len(response.data['items']), 2)
|
||||
self.assertEqual(response.data['items'][0]['alias'], x12.alias)
|
||||
self.assertEqual(response.data['items'][0]['term_raw'], x12.term_raw)
|
||||
self.assertEqual(response.data['items'][0]['term_resolved'], x12.term_resolved)
|
||||
self.assertEqual(response.data['items'][1]['term_raw'], d2.term_raw)
|
||||
self.assertEqual(response.data['items'][1]['term_resolved'], d2.term_resolved)
|
||||
|
||||
data = {'title': 'Title1340', 'items': []}
|
||||
response = self.execute(data, item=self.owned.id)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
self.assertEqual(len(response.data['items']), 0)
|
||||
|
||||
data = {'title': 'Title1341', 'items': [x12.pk]}
|
||||
response = self.execute(data, item=self.owned.id)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
self.assertEqual(len(response.data['items']), 1)
|
||||
self.assertEqual(response.data['items'][0]['alias'], x12.alias)
|
||||
self.assertEqual(response.data['items'][0]['term_raw'], x12.term_raw)
|
||||
self.assertEqual(response.data['items'][0]['term_resolved'], x12.term_resolved)
|
||||
|
|
|
@ -81,9 +81,11 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
@extend_schema(
|
||||
summary='clone item including contents',
|
||||
tags=['Library'],
|
||||
request=s.LibraryItemSerializer,
|
||||
request=s.LibraryItemCloneSerializer,
|
||||
responses={
|
||||
c.HTTP_201_CREATED: s.RSFormParseSerializer,
|
||||
c.HTTP_400_BAD_REQUEST: None,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
|
@ -91,7 +93,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
@action(detail=True, methods=['post'], url_path='clone')
|
||||
def clone(self, request: Request, pk):
|
||||
''' Endpoint: Create deep copy of library item. '''
|
||||
serializer = s.LibraryItemSerializer(data=request.data)
|
||||
serializer = s.LibraryItemCloneSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
item = self._get_item()
|
||||
if item.item_type == m.LibraryItemType.RSFORM:
|
||||
|
@ -104,6 +106,13 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
clone_data['comment'] = serializer.validated_data.get('comment', '')
|
||||
clone_data['is_common'] = serializer.validated_data.get('is_common', False)
|
||||
clone_data['is_canonical'] = serializer.validated_data.get('is_canonical', False)
|
||||
if 'items' in request.data:
|
||||
filtered_items = []
|
||||
for cst in clone_data['items']:
|
||||
if cst['entityUID'] in request.data['items']:
|
||||
filtered_items.append(cst)
|
||||
clone_data['items'] = filtered_items
|
||||
|
||||
clone = s.RSFormTRSSerializer(data=clone_data, context={'load_meta': True})
|
||||
clone.is_valid(raise_exception=True)
|
||||
new_schema = clone.save()
|
||||
|
@ -111,13 +120,17 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
status=c.HTTP_201_CREATED,
|
||||
data=s.RSFormParseSerializer(new_schema.item).data
|
||||
)
|
||||
return Response(status=c.HTTP_404_NOT_FOUND)
|
||||
return Response(status=c.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@extend_schema(
|
||||
summary='claim item',
|
||||
tags=['Library'],
|
||||
request=None,
|
||||
responses={c.HTTP_200_OK: s.LibraryItemSerializer}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.LibraryItemSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@transaction.atomic
|
||||
@action(detail=True, methods=['post'])
|
||||
|
@ -139,7 +152,11 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
summary='subscribe to item',
|
||||
tags=['Library'],
|
||||
request=None,
|
||||
responses={c.HTTP_204_NO_CONTENT: None}
|
||||
responses={
|
||||
c.HTTP_204_NO_CONTENT: None,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['post'])
|
||||
def subscribe(self, request: Request, pk):
|
||||
|
@ -152,7 +169,11 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
summary='unsubscribe from item',
|
||||
tags=['Library'],
|
||||
request=None,
|
||||
responses={c.HTTP_204_NO_CONTENT: None},
|
||||
responses={
|
||||
c.HTTP_204_NO_CONTENT: None,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
},
|
||||
)
|
||||
@action(detail=True, methods=['delete'])
|
||||
def unsubscribe(self, request: Request, pk):
|
||||
|
|
|
@ -41,7 +41,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='create constituenta',
|
||||
tags=['Constituenta'],
|
||||
request=s.CstCreateSerializer,
|
||||
responses={c.HTTP_201_CREATED: s.NewCstResponse}
|
||||
responses={
|
||||
c.HTTP_201_CREATED: s.NewCstResponse,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['post'], url_path='cst-create')
|
||||
def cst_create(self, request: Request, pk):
|
||||
|
@ -69,7 +73,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='produce the structure of a given constituenta',
|
||||
tags=['RSForm'],
|
||||
request=s.CstTargetSerializer,
|
||||
responses={c.HTTP_200_OK: s.NewMultiCstResponse}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.NewMultiCstResponse,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='cst-produce-structure')
|
||||
def produce_structure(self, request: Request, pk):
|
||||
|
@ -101,7 +109,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='rename constituenta',
|
||||
tags=['Constituenta'],
|
||||
request=s.CstRenameSerializer,
|
||||
responses={c.HTTP_200_OK: s.NewCstResponse}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.NewCstResponse,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@transaction.atomic
|
||||
@action(detail=True, methods=['patch'], url_path='cst-rename')
|
||||
|
@ -135,7 +147,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='substitute constituenta',
|
||||
tags=['RSForm'],
|
||||
request=s.CstSubstituteSerializer,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@transaction.atomic
|
||||
@action(detail=True, methods=['patch'], url_path='cst-substitute')
|
||||
|
@ -161,7 +177,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='delete constituents',
|
||||
tags=['RSForm'],
|
||||
request=s.CstListSerializer,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='cst-delete-multiple')
|
||||
def cst_delete_multiple(self, request: Request, pk):
|
||||
|
@ -183,7 +203,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='move constituenta',
|
||||
tags=['RSForm'],
|
||||
request=s.CstMoveSerializer,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='cst-moveto')
|
||||
def cst_moveto(self, request: Request, pk):
|
||||
|
@ -208,7 +232,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='reset aliases, update expressions and references',
|
||||
tags=['RSForm'],
|
||||
request=None,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='reset-aliases')
|
||||
def reset_aliases(self, request: Request, pk):
|
||||
|
@ -224,7 +252,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='load data from TRS file',
|
||||
tags=['RSForm'],
|
||||
request=s.RSFormUploadSerializer,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='load-trs')
|
||||
def load_trs(self, request: Request, pk):
|
||||
|
@ -251,7 +283,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='get all constituents data from DB',
|
||||
tags=['RSForm'],
|
||||
request=None,
|
||||
responses={c.HTTP_200_OK: s.RSFormSerializer}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.RSFormSerializer,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['get'])
|
||||
def contents(self, request: Request, pk):
|
||||
|
@ -266,7 +301,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='get all constituents data and parses',
|
||||
tags=['RSForm'],
|
||||
request=None,
|
||||
responses={c.HTTP_200_OK: s.RSFormParseSerializer}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['get'])
|
||||
def details(self, request: Request, pk):
|
||||
|
@ -281,7 +319,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='check RSLang expression',
|
||||
tags=['RSForm', 'FormalLanguage'],
|
||||
request=s.ExpressionSerializer,
|
||||
responses={c.HTTP_200_OK: s.ExpressionParseSerializer},
|
||||
responses={
|
||||
c.HTTP_200_OK: s.ExpressionParseSerializer,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
},
|
||||
)
|
||||
@action(detail=True, methods=['post'])
|
||||
def check(self, request: Request, pk):
|
||||
|
@ -300,7 +341,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='resolve text with references',
|
||||
tags=['RSForm', 'NaturalLanguage'],
|
||||
request=s.TextSerializer,
|
||||
responses={c.HTTP_200_OK: s.ResolverSerializer}
|
||||
responses={
|
||||
c.HTTP_200_OK: s.ResolverSerializer,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['post'])
|
||||
def resolve(self, request: Request, pk):
|
||||
|
@ -319,7 +363,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
summary='export as TRS file',
|
||||
tags=['RSForm'],
|
||||
request=None,
|
||||
responses={(c.HTTP_200_OK, 'application/zip'): bytes}
|
||||
responses={
|
||||
(c.HTTP_200_OK, 'application/zip'): bytes,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['get'], url_path='export-trs')
|
||||
def export_trs(self, request: Request, pk):
|
||||
|
@ -341,7 +388,10 @@ class TrsImportView(views.APIView):
|
|||
summary='import TRS file into RSForm',
|
||||
tags=['RSForm'],
|
||||
request=s.FileSerializer,
|
||||
responses={c.HTTP_201_CREATED: s.LibraryItemSerializer}
|
||||
responses={
|
||||
c.HTTP_201_CREATED: s.LibraryItemSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None
|
||||
}
|
||||
)
|
||||
def post(self, request: Request):
|
||||
data = utils.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
|
||||
|
@ -364,7 +414,10 @@ class TrsImportView(views.APIView):
|
|||
summary='create new RSForm empty or from file',
|
||||
tags=['RSForm'],
|
||||
request=s.LibraryItemSerializer,
|
||||
responses={c.HTTP_201_CREATED: s.LibraryItemSerializer}
|
||||
responses={
|
||||
c.HTTP_201_CREATED: s.LibraryItemSerializer,
|
||||
c.HTTP_403_FORBIDDEN: None
|
||||
}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
def create_rsform(request: Request):
|
||||
|
|
|
@ -6,7 +6,7 @@ import { ErrorData } from '@/components/info/InfoError';
|
|||
import { ILibraryItem } from '@/models/library';
|
||||
import { matchLibraryItem } from '@/models/libraryAPI';
|
||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||
import { IRSForm, IRSFormCreateData, IRSFormData } from '@/models/rsform';
|
||||
import { IRSForm, IRSFormCloneData, IRSFormCreateData, IRSFormData } from '@/models/rsform';
|
||||
import { loadRSFormData } from '@/models/rsformAPI';
|
||||
import {
|
||||
DataCallback,
|
||||
|
@ -31,7 +31,7 @@ interface ILibraryContext {
|
|||
applyFilter: (params: ILibraryFilter) => ILibraryItem[];
|
||||
retrieveTemplate: (templateID: number, callback: (schema: IRSForm) => void) => void;
|
||||
createItem: (data: IRSFormCreateData, callback?: DataCallback<ILibraryItem>) => void;
|
||||
cloneItem: (target: number, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => void;
|
||||
cloneItem: (target: number, data: IRSFormCloneData, callback: DataCallback<IRSFormData>) => void;
|
||||
destroyItem: (target: number, callback?: () => void) => void;
|
||||
|
||||
localUpdateItem: (data: ILibraryItem) => void;
|
||||
|
@ -194,7 +194,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
);
|
||||
|
||||
const cloneItem = useCallback(
|
||||
(target: number, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => {
|
||||
(target: number, data: IRSFormCloneData, callback: DataCallback<IRSFormData>) => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { urls } from '@/app/urls';
|
||||
|
@ -13,43 +13,38 @@ import { useLibrary } from '@/context/LibraryContext';
|
|||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { ILibraryItem } from '@/models/library';
|
||||
import { cloneTitle } from '@/models/libraryAPI';
|
||||
import { IRSFormCreateData } from '@/models/rsform';
|
||||
import { ConstituentaID, IRSFormCloneData } from '@/models/rsform';
|
||||
|
||||
interface DlgCloneLibraryItemProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
base: ILibraryItem;
|
||||
selected: ConstituentaID[];
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
function DlgCloneLibraryItem({ hideWindow, base }: DlgCloneLibraryItemProps) {
|
||||
function DlgCloneLibraryItem({ hideWindow, base, selected, totalCount }: DlgCloneLibraryItemProps) {
|
||||
const router = useConceptNavigation();
|
||||
const [title, setTitle] = useState('');
|
||||
const [alias, setAlias] = useState('');
|
||||
const [comment, setComment] = useState('');
|
||||
const [common, setCommon] = useState(false);
|
||||
const [canonical, setCanonical] = useState(false);
|
||||
const [title, setTitle] = useState(cloneTitle(base));
|
||||
const [alias, setAlias] = useState(base.alias);
|
||||
const [comment, setComment] = useState(base.comment);
|
||||
const [common, setCommon] = useState(base.is_common);
|
||||
const [onlySelected, setOnlySelected] = useState(false);
|
||||
|
||||
const { cloneItem } = useLibrary();
|
||||
|
||||
const canSubmit = useMemo(() => title !== '' && alias !== '', [title, alias]);
|
||||
|
||||
useEffect(() => {
|
||||
if (base) {
|
||||
setTitle(cloneTitle(base));
|
||||
setAlias(base.alias);
|
||||
setComment(base.comment);
|
||||
setCommon(base.is_common);
|
||||
setCanonical(false);
|
||||
}
|
||||
}, [base, base?.title, base?.alias, base?.comment, base?.is_common]);
|
||||
|
||||
function handleSubmit() {
|
||||
const data: IRSFormCreateData = {
|
||||
const data: IRSFormCloneData = {
|
||||
item_type: base.item_type,
|
||||
title: title,
|
||||
alias: alias,
|
||||
comment: comment,
|
||||
is_common: common,
|
||||
is_canonical: canonical
|
||||
is_canonical: false
|
||||
};
|
||||
if (onlySelected) {
|
||||
data.items = selected;
|
||||
}
|
||||
cloneItem(base.id, data, newSchema => {
|
||||
toast.success(`Копия создана: ${newSchema.alias}`);
|
||||
router.push(urls.schema(newSchema.id));
|
||||
|
@ -78,11 +73,12 @@ function DlgCloneLibraryItem({ hideWindow, base }: DlgCloneLibraryItemProps) {
|
|||
className='max-w-sm'
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
/>
|
||||
<TextArea
|
||||
id='dlg_comment'
|
||||
label='Комментарий'
|
||||
value={comment}
|
||||
onChange={event => setComment(event.target.value)}
|
||||
<TextArea id='dlg_comment' label='Описание' value={comment} onChange={event => setComment(event.target.value)} />
|
||||
<Checkbox
|
||||
id='dlg_only_selected'
|
||||
label={`Только выбранные конституенты [${selected.length} из ${totalCount}]`}
|
||||
value={onlySelected}
|
||||
setValue={value => setOnlySelected(value)}
|
||||
/>
|
||||
<Checkbox id='dlg_is_common' label='Общедоступная схема' value={common} setValue={value => setCommon(value)} />
|
||||
</Modal>
|
||||
|
|
|
@ -234,6 +234,13 @@ export interface IRSFormCreateData extends ILibraryUpdateData {
|
|||
fileName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data, used for cloning {@link IRSForm}.
|
||||
*/
|
||||
export interface IRSFormCloneData extends ILibraryUpdateData {
|
||||
items?: ConstituentaID[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data, used for uploading {@link IRSForm} as file.
|
||||
*/
|
||||
|
|
|
@ -527,7 +527,14 @@ export const RSEditState = ({
|
|||
{model.schema ? (
|
||||
<AnimatePresence>
|
||||
{showUpload ? <DlgUploadRSForm hideWindow={() => setShowUpload(false)} /> : null}
|
||||
{showClone ? <DlgCloneLibraryItem base={model.schema} hideWindow={() => setShowClone(false)} /> : null}
|
||||
{showClone ? (
|
||||
<DlgCloneLibraryItem
|
||||
base={model.schema}
|
||||
hideWindow={() => setShowClone(false)}
|
||||
selected={selected}
|
||||
totalCount={model.schema.items.length}
|
||||
/>
|
||||
) : null}
|
||||
{showCreateCst ? (
|
||||
<DlgCreateCst
|
||||
hideWindow={() => setShowCreateCst(false)}
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
ICstUpdateData,
|
||||
IInlineSynthesisData,
|
||||
IProduceStructureResponse,
|
||||
IRSFormCloneData,
|
||||
IRSFormCreateData,
|
||||
IRSFormData,
|
||||
IRSFormUploadData,
|
||||
|
@ -212,7 +213,7 @@ export function postNewRSForm(request: FrontExchange<IRSFormCreateData, ILibrary
|
|||
});
|
||||
}
|
||||
|
||||
export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCreateData, IRSFormData>) {
|
||||
export function postCloneLibraryItem(target: string, request: FrontExchange<IRSFormCloneData, IRSFormData>) {
|
||||
AxiosPost({
|
||||
title: 'Clone RSForm',
|
||||
endpoint: `/api/library/${target}/clone`,
|
||||
|
|
Loading…
Reference in New Issue
Block a user