mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Implement restoreVersion
This commit is contained in:
parent
b8dd8376ea
commit
9ff6a92c4f
|
@ -16,7 +16,7 @@ from .data_access import (
|
||||||
RSFormParseSerializer,
|
RSFormParseSerializer,
|
||||||
VersionSerializer,
|
VersionSerializer,
|
||||||
VersionCreateSerializer,
|
VersionCreateSerializer,
|
||||||
ConstituentaSerializer,
|
CstSerializer,
|
||||||
CstTargetSerializer,
|
CstTargetSerializer,
|
||||||
CstMoveSerializer,
|
CstMoveSerializer,
|
||||||
CstSubstituteSerializer,
|
CstSubstituteSerializer,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from typing import Optional, cast
|
from typing import Optional, cast
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db import transaction
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ class LibraryItemSerializer(serializers.ModelSerializer):
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
model = LibraryItem
|
model = LibraryItem
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = ('owner', 'id', 'item_type')
|
read_only_fields = ('id', 'item_type')
|
||||||
|
|
||||||
|
|
||||||
class VersionSerializer(serializers.ModelSerializer):
|
class VersionSerializer(serializers.ModelSerializer):
|
||||||
|
@ -66,7 +67,16 @@ class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||||
return [VersionInnerSerializer(item).data for item in instance.versions()]
|
return [VersionInnerSerializer(item).data for item in instance.versions()]
|
||||||
|
|
||||||
|
|
||||||
class ConstituentaSerializer(serializers.ModelSerializer):
|
class CstBaseSerializer(serializers.ModelSerializer):
|
||||||
|
''' Serializer: Constituenta all data. '''
|
||||||
|
class Meta:
|
||||||
|
''' serializer metadata. '''
|
||||||
|
model = Constituenta
|
||||||
|
fields = '__all__'
|
||||||
|
read_only_fields = ('id',)
|
||||||
|
|
||||||
|
|
||||||
|
class CstSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: Constituenta data. '''
|
''' Serializer: Constituenta data. '''
|
||||||
class Meta:
|
class Meta:
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
|
@ -124,7 +134,7 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
child=serializers.IntegerField()
|
child=serializers.IntegerField()
|
||||||
)
|
)
|
||||||
items = serializers.ListField(
|
items = serializers.ListField(
|
||||||
child=ConstituentaSerializer()
|
child=CstSerializer()
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -137,7 +147,7 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
schema = RSForm(instance)
|
schema = RSForm(instance)
|
||||||
result['items'] = []
|
result['items'] = []
|
||||||
for cst in schema.constituents().order_by('order'):
|
for cst in schema.constituents().order_by('order'):
|
||||||
result['items'].append(ConstituentaSerializer(cst).data)
|
result['items'].append(CstSerializer(cst).data)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def to_versioned_data(self) -> dict:
|
def to_versioned_data(self) -> dict:
|
||||||
|
@ -159,6 +169,45 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
result['version'] = version
|
result['version'] = version
|
||||||
return result | data
|
return result | data
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def restore_from_version(self, data: dict):
|
||||||
|
''' Load data from version. '''
|
||||||
|
schema = RSForm(cast(LibraryItem, self.instance))
|
||||||
|
items: list[dict] = data['items']
|
||||||
|
ids: list[int] = [item['id'] for item in items]
|
||||||
|
processed: list[int] = []
|
||||||
|
|
||||||
|
for cst in schema.constituents():
|
||||||
|
if not cst.pk in ids:
|
||||||
|
cst.delete()
|
||||||
|
else:
|
||||||
|
cst_data = next(x for x in items if x['id'] == cst.pk)
|
||||||
|
new_cst = CstBaseSerializer(data=cst_data)
|
||||||
|
new_cst.is_valid(raise_exception=True)
|
||||||
|
new_cst.update(
|
||||||
|
instance=cst,
|
||||||
|
validated_data=new_cst.validated_data
|
||||||
|
)
|
||||||
|
processed.append(cst.pk)
|
||||||
|
|
||||||
|
for cst_data in items:
|
||||||
|
if cst_data['id'] not in processed:
|
||||||
|
cst = schema.insert_new(cst_data['alias'])
|
||||||
|
cst_data['id'] = cst.pk
|
||||||
|
new_cst = CstBaseSerializer(data=cst_data)
|
||||||
|
new_cst.is_valid(raise_exception=True)
|
||||||
|
new_cst.update(
|
||||||
|
instance=cst,
|
||||||
|
validated_data=new_cst.validated_data
|
||||||
|
)
|
||||||
|
|
||||||
|
loaded_item = LibraryItemSerializer(data=data)
|
||||||
|
loaded_item.is_valid(raise_exception=True)
|
||||||
|
loaded_item.update(
|
||||||
|
instance=cast(LibraryItem, self.instance),
|
||||||
|
validated_data=loaded_item.validated_data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RSFormParseSerializer(serializers.ModelSerializer):
|
class RSFormParseSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: Detailed data for RSForm including parse. '''
|
''' Serializer: Detailed data for RSForm including parse. '''
|
||||||
|
|
|
@ -15,13 +15,11 @@ class TestVersionViews(EndpointTester):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.owned = RSForm.create(title='Test', alias='T1', owner=self.user).item
|
self.owned = RSForm.create(title='Test', alias='T1', owner=self.user).item
|
||||||
|
self.schema = RSForm(self.owned)
|
||||||
self.unowned = RSForm.create(title='Test2', alias='T2').item
|
self.unowned = RSForm.create(title='Test2', alias='T2').item
|
||||||
self.x1 = Constituenta.objects.create(
|
self.x1 = self.schema.insert_new(
|
||||||
schema=self.owned,
|
|
||||||
alias='X1',
|
alias='X1',
|
||||||
cst_type='basic',
|
convention='testStart'
|
||||||
convention='testStart',
|
|
||||||
order=1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,6 +136,39 @@ class TestVersionViews(EndpointTester):
|
||||||
self.assertIn('document.json', zipped_file.namelist())
|
self.assertIn('document.json', zipped_file.namelist())
|
||||||
|
|
||||||
|
|
||||||
|
@decl_endpoint('/api/versions/{version}/restore', method='patch')
|
||||||
|
def test_restore_version(self):
|
||||||
|
x1 = self.x1
|
||||||
|
x2 = self.schema.insert_new('X2')
|
||||||
|
d1 = self.schema.insert_new('D1', term_raw='TestTerm')
|
||||||
|
data = {'version': '1.0.0', 'description': 'test'}
|
||||||
|
version_id = self._create_version(data)
|
||||||
|
invalid_id = version_id + 1337
|
||||||
|
|
||||||
|
d1.delete()
|
||||||
|
x3 = self.schema.insert_new('X3')
|
||||||
|
x1.order = x3.order
|
||||||
|
x1.convention = 'Test2'
|
||||||
|
x1.term_raw = 'Test'
|
||||||
|
x1.save()
|
||||||
|
x3.order = 1
|
||||||
|
x3.save()
|
||||||
|
|
||||||
|
self.assertNotFound(version=invalid_id)
|
||||||
|
|
||||||
|
response = self.execute(version=version_id)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
x1.refresh_from_db()
|
||||||
|
x2.refresh_from_db()
|
||||||
|
self.assertEqual(len(response.data['items']), 3)
|
||||||
|
self.assertEqual(x1.order, 1)
|
||||||
|
self.assertEqual(x1.convention, 'testStart')
|
||||||
|
self.assertEqual(x1.term_raw, '')
|
||||||
|
self.assertEqual(x2.order, 2)
|
||||||
|
self.assertEqual(response.data['items'][2]['alias'], 'D1')
|
||||||
|
self.assertEqual(response.data['items'][2]['term_raw'], 'TestTerm')
|
||||||
|
|
||||||
|
|
||||||
def _create_version(self, data) -> int:
|
def _create_version(self, data) -> int:
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
f'/api/rsforms/{self.owned.id}/versions/create',
|
f'/api/rsforms/{self.owned.id}/versions/create',
|
||||||
|
|
|
@ -6,6 +6,7 @@ from . import views
|
||||||
library_router = routers.SimpleRouter(trailing_slash=False)
|
library_router = routers.SimpleRouter(trailing_slash=False)
|
||||||
library_router.register('library', views.LibraryViewSet, 'Library')
|
library_router.register('library', views.LibraryViewSet, 'Library')
|
||||||
library_router.register('rsforms', views.RSFormViewSet, 'RSForm')
|
library_router.register('rsforms', views.RSFormViewSet, 'RSForm')
|
||||||
|
library_router.register('versions', views.VersionViewset, 'Version')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('library/active', views.LibraryActiveView.as_view()),
|
path('library/active', views.LibraryActiveView.as_view()),
|
||||||
|
@ -15,7 +16,6 @@ urlpatterns = [
|
||||||
path('rsforms/import-trs', views.TrsImportView.as_view()),
|
path('rsforms/import-trs', views.TrsImportView.as_view()),
|
||||||
path('rsforms/create-detailed', views.create_rsform),
|
path('rsforms/create-detailed', views.create_rsform),
|
||||||
|
|
||||||
path('versions/<int:pk>', views.VersionAPIView.as_view()),
|
|
||||||
path('versions/<int:pk>/export-file', views.export_file),
|
path('versions/<int:pk>/export-file', views.export_file),
|
||||||
path('rsforms/<int:pk_item>/versions/create', views.create_version),
|
path('rsforms/<int:pk_item>/versions/create', views.create_version),
|
||||||
path('rsforms/<int:pk_item>/versions/<int:pk_version>', views.retrieve_version),
|
path('rsforms/<int:pk_item>/versions/<int:pk_version>', views.retrieve_version),
|
||||||
|
|
|
@ -7,7 +7,7 @@ from .library import (
|
||||||
)
|
)
|
||||||
from .constituents import ConstituentAPIView
|
from .constituents import ConstituentAPIView
|
||||||
from .versions import (
|
from .versions import (
|
||||||
VersionAPIView,
|
VersionViewset,
|
||||||
create_version,
|
create_version,
|
||||||
export_file,
|
export_file,
|
||||||
retrieve_version
|
retrieve_version
|
||||||
|
|
|
@ -12,7 +12,7 @@ from .. import utils
|
||||||
class ConstituentAPIView(generics.RetrieveUpdateAPIView):
|
class ConstituentAPIView(generics.RetrieveUpdateAPIView):
|
||||||
''' Endpoint: Get / Update Constituenta. '''
|
''' Endpoint: Get / Update Constituenta. '''
|
||||||
queryset = m.Constituenta.objects.all()
|
queryset = m.Constituenta.objects.all()
|
||||||
serializer_class = s.ConstituentaSerializer
|
serializer_class = s.CstSerializer
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
result = super().get_permissions()
|
result = super().get_permissions()
|
||||||
|
|
|
@ -62,7 +62,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
response = Response(
|
response = Response(
|
||||||
status=c.HTTP_201_CREATED,
|
status=c.HTTP_201_CREATED,
|
||||||
data={
|
data={
|
||||||
'new_cst': s.ConstituentaSerializer(new_cst).data,
|
'new_cst': s.CstSerializer(new_cst).data,
|
||||||
'schema': s.RSFormParseSerializer(schema.item).data
|
'schema': s.RSFormParseSerializer(schema.item).data
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -138,7 +138,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data={
|
data={
|
||||||
'new_cst': s.ConstituentaSerializer(cst).data,
|
'new_cst': s.CstSerializer(cst).data,
|
||||||
'schema': s.RSFormParseSerializer(schema.item).data
|
'schema': s.RSFormParseSerializer(schema.item).data
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
''' Endpoints for versions. '''
|
''' Endpoints for versions. '''
|
||||||
|
from typing import cast
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics, permissions, viewsets
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import action, api_view, permission_classes
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
|
@ -14,7 +15,7 @@ from .. import utils
|
||||||
|
|
||||||
@extend_schema(tags=['Version'])
|
@extend_schema(tags=['Version'])
|
||||||
@extend_schema_view()
|
@extend_schema_view()
|
||||||
class VersionAPIView(generics.RetrieveUpdateDestroyAPIView):
|
class VersionViewset(viewsets.GenericViewSet, generics.RetrieveUpdateDestroyAPIView):
|
||||||
''' Endpoint: Get / Update Constituenta. '''
|
''' Endpoint: Get / Update Constituenta. '''
|
||||||
queryset = m.Version.objects.all()
|
queryset = m.Version.objects.all()
|
||||||
serializer_class = s.VersionSerializer
|
serializer_class = s.VersionSerializer
|
||||||
|
@ -27,6 +28,26 @@ class VersionAPIView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
result.append(utils.ItemOwnerOrAdmin())
|
result.append(utils.ItemOwnerOrAdmin())
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='restore version data into current item',
|
||||||
|
request=None,
|
||||||
|
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='restore')
|
||||||
|
def restore(self, request: Request, pk):
|
||||||
|
''' Restore version data into current item. '''
|
||||||
|
version = cast(m.Version, self.get_object())
|
||||||
|
item = cast(m.LibraryItem, version.item)
|
||||||
|
s.RSFormSerializer(item).restore_from_version(version.data)
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data=s.RSFormParseSerializer(item).data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='save version for RSForm copying current content',
|
summary='save version for RSForm copying current content',
|
||||||
|
|
|
@ -453,6 +453,14 @@ export function patchVersion(target: string, request: FrontPush<IVersionData>) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function patchRestoreVersion(target: string, request: FrontPull<IRSFormData>) {
|
||||||
|
AxiosPatch({
|
||||||
|
title: `Restore version id=${target}`,
|
||||||
|
endpoint: `/api/versions/${target}/restore`,
|
||||||
|
request: request
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteVersion(target: string, request: FrontAction) {
|
export function deleteVersion(target: string, request: FrontAction) {
|
||||||
AxiosDelete({
|
AxiosDelete({
|
||||||
title: `Version id=${target}`,
|
title: `Version id=${target}`,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
patchRenameConstituenta,
|
patchRenameConstituenta,
|
||||||
patchResetAliases,
|
patchResetAliases,
|
||||||
patchRestoreOrder,
|
patchRestoreOrder,
|
||||||
|
patchRestoreVersion,
|
||||||
patchSubstituteConstituents,
|
patchSubstituteConstituents,
|
||||||
patchUploadTRS,
|
patchUploadTRS,
|
||||||
patchVersion,
|
patchVersion,
|
||||||
|
@ -50,6 +51,7 @@ import { useLibrary } from './LibraryContext';
|
||||||
interface IRSFormContext {
|
interface IRSFormContext {
|
||||||
schema?: IRSForm;
|
schema?: IRSForm;
|
||||||
schemaID: string;
|
schemaID: string;
|
||||||
|
versionID?: string;
|
||||||
|
|
||||||
error: ErrorData;
|
error: ErrorData;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
@ -82,6 +84,7 @@ interface IRSFormContext {
|
||||||
versionCreate: (data: IVersionData, callback?: (version: number) => void) => void;
|
versionCreate: (data: IVersionData, callback?: (version: number) => void) => void;
|
||||||
versionUpdate: (target: number, data: IVersionData, callback?: () => void) => void;
|
versionUpdate: (target: number, data: IVersionData, callback?: () => void) => void;
|
||||||
versionDelete: (target: number, callback?: () => void) => void;
|
versionDelete: (target: number, callback?: () => void) => void;
|
||||||
|
versionRestore: (target: string, callback?: () => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RSFormContext = createContext<IRSFormContext | null>(null);
|
const RSFormContext = createContext<IRSFormContext | null>(null);
|
||||||
|
@ -490,6 +493,22 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps)
|
||||||
[setError, schema, setSchema]
|
[setError, schema, setSchema]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const versionRestore = useCallback(
|
||||||
|
(target: string, callback?: () => void) => {
|
||||||
|
setError(undefined);
|
||||||
|
patchRestoreVersion(target, {
|
||||||
|
showError: true,
|
||||||
|
setLoading: setProcessing,
|
||||||
|
onError: setError,
|
||||||
|
onSuccess: () => {
|
||||||
|
setSchema(schema);
|
||||||
|
if (callback) callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setError, schema, setSchema]
|
||||||
|
);
|
||||||
|
|
||||||
const inlineSynthesis = useCallback(
|
const inlineSynthesis = useCallback(
|
||||||
(data: IInlineSynthesisData, callback?: DataCallback<IRSFormData>) => {
|
(data: IInlineSynthesisData, callback?: DataCallback<IRSFormData>) => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
@ -513,6 +532,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps)
|
||||||
value={{
|
value={{
|
||||||
schema,
|
schema,
|
||||||
schemaID,
|
schemaID,
|
||||||
|
versionID,
|
||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
processing,
|
processing,
|
||||||
|
@ -538,7 +558,8 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps)
|
||||||
cstMoveTo,
|
cstMoveTo,
|
||||||
versionCreate,
|
versionCreate,
|
||||||
versionUpdate,
|
versionUpdate,
|
||||||
versionDelete
|
versionDelete,
|
||||||
|
versionRestore
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -6,9 +6,12 @@ import {
|
||||||
IconDownload,
|
IconDownload,
|
||||||
IconFollow,
|
IconFollow,
|
||||||
IconImmutable,
|
IconImmutable,
|
||||||
|
IconList,
|
||||||
|
IconNewItem,
|
||||||
IconOwner,
|
IconOwner,
|
||||||
IconPublic,
|
IconPublic,
|
||||||
IconSave
|
IconSave,
|
||||||
|
IconUpload
|
||||||
} from '../../../components/Icons';
|
} from '../../../components/Icons';
|
||||||
import LinkTopic from '../../../components/ui/LinkTopic';
|
import LinkTopic from '../../../components/ui/LinkTopic';
|
||||||
|
|
||||||
|
@ -26,10 +29,15 @@ function HelpRSFormCard() {
|
||||||
<li><IconOwner className='inline-icon'/> Владелец обладает правом редактирования</li>
|
<li><IconOwner className='inline-icon'/> Владелец обладает правом редактирования</li>
|
||||||
<li><IconPublic className='inline-icon'/> Общедоступные схемы доступны для всех</li>
|
<li><IconPublic className='inline-icon'/> Общедоступные схемы доступны для всех</li>
|
||||||
<li><IconImmutable className='inline-icon'/> Неизменные схемы редактируют только администраторы</li>
|
<li><IconImmutable className='inline-icon'/> Неизменные схемы редактируют только администраторы</li>
|
||||||
<li><IconClone className='inline-icon'/> Клонировать – создать копию схемы</li>
|
<li><IconClone className='inline-icon icon-green'/> Клонировать – создать копию схемы</li>
|
||||||
<li><IconFollow className='inline-icon'/> Отслеживание – схема в персональном списке</li>
|
<li><IconFollow className='inline-icon'/> Отслеживание – схема в персональном списке</li>
|
||||||
<li><IconDownload className='inline-icon'/> Загрузить/Выгрузить – взаимодействие с Экстеор</li>
|
<li><IconDownload className='inline-icon'/> Загрузить/Выгрузить – взаимодействие с Экстеор</li>
|
||||||
<li><IconDestroy className='inline-icon icon-red'/> Удалить – полностью удаляет схему из базы Портала</li>
|
<li><IconDestroy className='inline-icon icon-red'/> Удалить – полностью удаляет схему из базы Портала</li>
|
||||||
|
|
||||||
|
<h2>Версионирование</h2>
|
||||||
|
<li><IconNewItem className='inline-icon icon-green'/> Создать версию можно только из актуальной схемы</li>
|
||||||
|
<li><IconUpload className='inline-icon icon-red'/> Загрузить версию в актуальную схему</li>
|
||||||
|
<li><IconList className='inline-icon'/> Редактировать атрибуты версий</li>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ function HelpRSFormMenu() {
|
||||||
<IconShare className='inline-icon' /> Поделиться – скопировать ссылку на схему
|
<IconShare className='inline-icon' /> Поделиться – скопировать ссылку на схему
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconClone className='inline-icon' /> Клонировать – создать копию схемы
|
<IconClone className='inline-icon icon-green' /> Клонировать – создать копию схемы
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconDownload className='inline-icon' /> Выгрузить – сохранить в файле формата Экстеор
|
<IconDownload className='inline-icon' /> Выгрузить – сохранить в файле формата Экстеор
|
||||||
|
@ -87,7 +87,7 @@ function HelpRSFormMenu() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<IconEdit2 size='1.25rem' className='inline-icon' /> операции над концептуальной схемой описаны в{' '}
|
<IconEdit2 size='1.25rem' className='inline-icon icon-green' /> операции над концептуальной схемой описаны в{' '}
|
||||||
<LinkTopic text='разделе Экспликация' topic={HelpTopic.RSL_OPERATIONS} />.
|
<LinkTopic text='разделе Экспликация' topic={HelpTopic.RSL_OPERATIONS} />.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
function HelpVersions() {
|
function HelpVersions() {
|
||||||
return (
|
return (
|
||||||
<div className='text-justify'>
|
<div className='text-justify'>
|
||||||
|
@ -6,9 +9,11 @@ function HelpVersions() {
|
||||||
Версионирование позволяет сохранить текущее состояние схемы под определенным именем (версией) и использовать
|
Версионирование позволяет сохранить текущее состояние схемы под определенным именем (версией) и использовать
|
||||||
ссылку на него для совместной работы. После создания версии ее содержание изменить нельзя
|
ссылку на него для совместной работы. После создания версии ее содержание изменить нельзя
|
||||||
</p>
|
</p>
|
||||||
<p>Владелец обладает правом редактирования названий и создания новых версий</p>
|
<li>Владелец обладает правом редактирования названий и создания новых версий</li>
|
||||||
<p>Управление версиями происходит в Карточке схемы</p>
|
<li>
|
||||||
<p>Функция Поделиться включает версию в ссылку</p>
|
Управление версиями происходит в <LinkTopic text='Карточке схемы' topic={HelpTopic.UI_RS_CARD} />
|
||||||
|
</li>
|
||||||
|
<li>Функция Поделиться включает версию в ссылку</li>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import clsx from 'clsx';
|
||||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { IconList, IconNewItem, IconSave } from '@/components/Icons';
|
import { IconList, IconNewItem, IconSave, IconUpload } from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import SelectVersion from '@/components/select/SelectVersion';
|
import SelectVersion from '@/components/select/SelectVersion';
|
||||||
import Checkbox from '@/components/ui/Checkbox';
|
import Checkbox from '@/components/ui/Checkbox';
|
||||||
|
@ -119,6 +119,12 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
||||||
<Overlay position='top-[-0.25rem] right-[-0.25rem] cc-icons'>
|
<Overlay position='top-[-0.25rem] right-[-0.25rem] cc-icons'>
|
||||||
{controller.isMutable ? (
|
{controller.isMutable ? (
|
||||||
<>
|
<>
|
||||||
|
<MiniButton
|
||||||
|
title={!controller.isContentEditable ? 'Откатить к версии' : 'Переключитесь на неактуальную версию'}
|
||||||
|
disabled={controller.isContentEditable}
|
||||||
|
onClick={() => controller.restoreVersion()}
|
||||||
|
icon={<IconUpload size='1.25rem' className='icon-red' />}
|
||||||
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title={controller.isContentEditable ? 'Создать версию' : 'Переключитесь на актуальную версию'}
|
title={controller.isContentEditable ? 'Создать версию' : 'Переключитесь на актуальную версию'}
|
||||||
disabled={!controller.isContentEditable}
|
disabled={!controller.isContentEditable}
|
||||||
|
|
|
@ -66,6 +66,7 @@ interface IRSEditContext {
|
||||||
|
|
||||||
viewVersion: (version?: number, newTab?: boolean) => void;
|
viewVersion: (version?: number, newTab?: boolean) => void;
|
||||||
createVersion: () => void;
|
createVersion: () => void;
|
||||||
|
restoreVersion: () => void;
|
||||||
editVersions: () => void;
|
editVersions: () => void;
|
||||||
|
|
||||||
moveUp: () => void;
|
moveUp: () => void;
|
||||||
|
@ -171,6 +172,26 @@ export const RSEditState = ({
|
||||||
[router, model]
|
[router, model]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const createVersion = useCallback(() => {
|
||||||
|
if (isModified && !promptUnsaved()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setShowCreateVersion(true);
|
||||||
|
}, [isModified]);
|
||||||
|
|
||||||
|
const restoreVersion = useCallback(() => {
|
||||||
|
if (
|
||||||
|
!model.versionID ||
|
||||||
|
!window.confirm('При восстановлении архивной версии актуальная схему будет заменена. Продолжить?')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
model.versionRestore(model.versionID, () => {
|
||||||
|
toast.success('Загрузка версии завершена');
|
||||||
|
viewVersion(undefined);
|
||||||
|
});
|
||||||
|
}, [model, viewVersion]);
|
||||||
|
|
||||||
const handleCreateCst = useCallback(
|
const handleCreateCst = useCallback(
|
||||||
(data: ICstCreateData) => {
|
(data: ICstCreateData) => {
|
||||||
if (!model.schema) {
|
if (!model.schema) {
|
||||||
|
@ -254,9 +275,14 @@ export const RSEditState = ({
|
||||||
if (!model.schema) {
|
if (!model.schema) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
model.versionDelete(versionID, () => toast.success('Версия удалена'));
|
model.versionDelete(versionID, () => {
|
||||||
|
toast.success('Версия удалена');
|
||||||
|
if (String(versionID) === model.versionID) {
|
||||||
|
viewVersion(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[model]
|
[model, viewVersion]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleUpdateVersion = useCallback(
|
const handleUpdateVersion = useCallback(
|
||||||
|
@ -505,7 +531,8 @@ export const RSEditState = ({
|
||||||
deselectAll: () => setSelected([]),
|
deselectAll: () => setSelected([]),
|
||||||
|
|
||||||
viewVersion,
|
viewVersion,
|
||||||
createVersion: () => setShowCreateVersion(true),
|
createVersion,
|
||||||
|
restoreVersion,
|
||||||
editVersions: () => setShowEditVersions(true),
|
editVersions: () => setShowEditVersions(true),
|
||||||
|
|
||||||
moveUp,
|
moveUp,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user